diff --git a/DEPS b/DEPS
index adf677d..ed2aca53 100644
--- a/DEPS
+++ b/DEPS
@@ -105,7 +105,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'b33f9ebe9057455593b8dea4032df86e622df048',
+  'skia_revision': '607be37e3d4ddbe2163c200d6e13bcee981f6bf7',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -117,7 +117,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'b8430dd749b044210ea53f54d5aaa8915430e8c5',
+  'angle_revision': 'a6d34af67286abac36ab142c5a5b55b59a231158',
   # 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.
@@ -165,7 +165,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': '27928c385ec671307c0734012fafe1edf23aefdc',
+  'catapult_revision': 'd30f10814bd09c01a756225499b652ca335d6785',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -552,7 +552,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'fb6f9736373855b218598ff92072ac41d3afa6c1',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '3387250e88bcab66560b91684c78c10b137d3f98',
       'condition': 'checkout_linux',
   },
 
@@ -577,7 +577,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '29b7b99e5d9fa6ae4d3ce6273b537cab2dc89b2f',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '82bb756217cd542205ee89d87bc0f17e2bce52b0',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -821,7 +821,7 @@
   },
 
   'src/third_party/libvpx/source/libvpx':
-    Var('chromium_git') + '/webm/libvpx.git' + '@' +  '2d79df49408873dd62a1b26cf8ed0e067c26dc6d',
+    Var('chromium_git') + '/webm/libvpx.git' + '@' +  '6fd9d0244c7d8941ce0004bcd2efce5d6676bef5',
 
   'src/third_party/libwebm/source':
     Var('chromium_git') + '/webm/libwebm.git' + '@' + '01c1d1d76f139345c442bfc8e61b4e1cba809059',
@@ -1047,7 +1047,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '21dbf06b5aa6c7dc8cf56314d4a3f96f57956c53',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'd54f5f5c763cc35514ff51fa6621aadec043fe47',
+    Var('webrtc_git') + '/src.git' + '@' + '8bf477cb2a8dd0bc70095dffad6ed7bcf37b2a4e',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 76faaea01..15b2b01 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -638,48 +638,9 @@
 
 
 _ANDROID_SPECIFIC_PYDEPS_FILES = [
-    'base/android/jni_generator/jni_generator.pydeps',
-    'base/android/jni_generator/jni_registration_generator.pydeps',
-    'build/android/gyp/aar.pydeps',
-    'build/android/gyp/aidl.pydeps',
-    'build/android/gyp/apkbuilder.pydeps',
-    'build/android/gyp/app_bundle_to_apks.pydeps',
-    'build/android/gyp/bytecode_processor.pydeps',
-    'build/android/gyp/compile_resources.pydeps',
-    'build/android/gyp/create_bundle_wrapper_script.pydeps',
-    'build/android/gyp/copy_ex.pydeps',
-    'build/android/gyp/create_app_bundle.pydeps',
-    'build/android/gyp/create_apk_operations_script.pydeps',
-    'build/android/gyp/create_dist_jar.pydeps',
-    'build/android/gyp/create_java_binary_script.pydeps',
-    'build/android/gyp/create_stack_script.pydeps',
-    'build/android/gyp/create_test_runner_script.pydeps',
-    'build/android/gyp/create_tool_wrapper.pydeps',
-    'build/android/gyp/desugar.pydeps',
-    'build/android/gyp/dex.pydeps',
-    'build/android/gyp/dist_aar.pydeps',
-    'build/android/gyp/emma_instr.pydeps',
-    'build/android/gyp/filter_zip.pydeps',
-    'build/android/gyp/gcc_preprocess.pydeps',
-    'build/android/gyp/generate_proguarded_module_jar.pydeps',
-    'build/android/gyp/ijar.pydeps',
-    'build/android/gyp/java_cpp_enum.pydeps',
-    'build/android/gyp/javac.pydeps',
-    'build/android/gyp/jinja_template.pydeps',
-    'build/android/gyp/lint.pydeps',
-    'build/android/gyp/main_dex_list.pydeps',
-    'build/android/gyp/merge_jar_info_files.pydeps',
-    'build/android/gyp/merge_manifest.pydeps',
-    'build/android/gyp/prepare_resources.pydeps',
-    'build/android/gyp/proguard.pydeps',
-    'build/android/gyp/write_build_config.pydeps',
-    'build/android/gyp/write_ordered_libraries.pydeps',
-    'build/android/incremental_install/generate_android_manifest.pydeps',
-    'build/android/incremental_install/write_installer_json.pydeps',
     'build/android/resource_sizes.pydeps',
     'build/android/test_runner.pydeps',
     'build/android/test_wrapper/logdog_wrapper.pydeps',
-    'build/protoc_java.pydeps',
     'build/secondary/third_party/android_platform/'
         'development/scripts/stack.pydeps',
     'net/tools/testserver/testserver.pydeps',
diff --git a/ash/accessibility/touch_exploration_controller.cc b/ash/accessibility/touch_exploration_controller.cc
index 2043d61..e5ed07f 100644
--- a/ash/accessibility/touch_exploration_controller.cc
+++ b/ash/accessibility/touch_exploration_controller.cc
@@ -138,13 +138,6 @@
     // this event under this new state.
   }
 
-  if (passthrough_timer_.IsRunning() &&
-      event.time_stamp() - most_recent_press_timestamp_ >
-          gesture_detector_config_.longpress_timeout) {
-    passthrough_timer_.Stop();
-    OnPassthroughTimerFired();
-  }
-
   const ui::EventType type = touch_event.type();
   const int touch_id = touch_event.pointer_details().id;
 
@@ -203,9 +196,6 @@
     if (VLOG_on_)
       VLOG(1) << "Leaving screen";
 
-    // Indicates to the user that they are leaving the screen.
-    delegate_->PlayExitScreenEarcon();
-
     if (current_touch_ids_.size() == 0) {
       SET_STATE(NO_FINGERS_DOWN);
       if (VLOG_on_) {
@@ -270,9 +260,6 @@
     case ONE_FINGER_PASSTHROUGH:
       status = InOneFingerPassthrough(touch_event_dip, rewritten_event);
       break;
-    case CORNER_PASSTHROUGH:
-      status = InCornerPassthrough(touch_event_dip, rewritten_event);
-      break;
     case WAIT_FOR_NO_FINGERS:
       status = InWaitForNoFingers(touch_event_dip, rewritten_event);
       break;
@@ -303,21 +290,6 @@
     return ui::EVENT_REWRITE_CONTINUE;
   }
 
-  // If the user enters the screen from the edge then send an earcon.
-  int edge = FindEdgesWithinInset(event.location(), kLeavingScreenEdge);
-  if (edge != NO_EDGE)
-    delegate_->PlayEnterScreenEarcon();
-
-  int location = FindEdgesWithinInset(event.location(), kSlopDistanceFromEdge);
-  // If the press was at a corner, the user might go into corner passthrough
-  // instead.
-  bool in_a_bottom_corner =
-      (BOTTOM_LEFT_CORNER == location) || (BOTTOM_RIGHT_CORNER == location);
-  if (in_a_bottom_corner) {
-    passthrough_timer_.Start(
-        FROM_HERE, gesture_detector_config_.longpress_timeout, this,
-        &TouchExplorationController::OnPassthroughTimerFired);
-  }
   initial_press_ = std::make_unique<ui::TouchEvent>(event);
   most_recent_press_timestamp_ = initial_press_->time_stamp();
   initial_presses_[event.pointer_details().id] = event.location();
@@ -331,33 +303,11 @@
     const ui::TouchEvent& event,
     std::unique_ptr<ui::Event>* rewritten_event) {
   const ui::EventType type = event.type();
-
-  int location = FindEdgesWithinInset(event.location(), kMaxDistanceFromEdge);
-  bool in_a_bottom_corner =
-      (location == BOTTOM_LEFT_CORNER) || (location == BOTTOM_RIGHT_CORNER);
-  // If the event is from the initial press and the location is no longer in the
-  // corner, then we are not waiting for a corner passthrough anymore.
-  if (event.pointer_details().id == initial_press_->pointer_details().id &&
-      !in_a_bottom_corner) {
-    if (passthrough_timer_.IsRunning()) {
-      passthrough_timer_.Stop();
-      // Since the long press timer has been running, it is possible that the
-      // tap timer has timed out before the long press timer has. If the tap
-      // timer timeout has elapsed, then fire the tap timer.
-      if (event.time_stamp() - most_recent_press_timestamp_ >
-          gesture_detector_config_.double_tap_timeout) {
-        OnTapTimerFired();
-      }
-    }
-  }
-
   if (type == ui::ET_TOUCH_PRESSED) {
     initial_presses_[event.pointer_details().id] = event.location();
     SET_STATE(TWO_FINGER_TAP);
     return ui::EVENT_REWRITE_DISCARD;
   } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
-    if (passthrough_timer_.IsRunning())
-      passthrough_timer_.Stop();
     if (current_touch_ids_.size() == 0 &&
         event.pointer_details().id == initial_press_->pointer_details().id) {
       MaybeSendSimulatedTapInLiftActivationBounds(event);
@@ -545,43 +495,6 @@
   return ui::EVENT_REWRITE_DISCARD;
 }
 
-ui::EventRewriteStatus TouchExplorationController::InCornerPassthrough(
-    const ui::TouchEvent& event,
-    std::unique_ptr<ui::Event>* rewritten_event) {
-  ui::EventType type = event.type();
-
-  // If the first finger has left the corner, then exit passthrough.
-  if (event.pointer_details().id == initial_press_->pointer_details().id) {
-    int edges = FindEdgesWithinInset(event.location(), kSlopDistanceFromEdge);
-    bool in_a_bottom_corner =
-        (edges == BOTTOM_LEFT_CORNER) || (edges == BOTTOM_RIGHT_CORNER);
-    if (type == ui::ET_TOUCH_MOVED && in_a_bottom_corner)
-      return ui::EVENT_REWRITE_DISCARD;
-
-    if (current_touch_ids_.size() == 0) {
-      SET_STATE(NO_FINGERS_DOWN);
-      return ui::EVENT_REWRITE_DISCARD;
-    }
-    SET_STATE(WAIT_FOR_NO_FINGERS);
-    return ui::EVENT_REWRITE_DISCARD;
-  }
-
-  // |event| locations are in DIP; see |RewriteEvent|. We need to dispatch
-  // screen coordinates.
-  gfx::PointF location_f(ConvertDIPToScreenInPixels(event.location_f()));
-  std::unique_ptr<ui::TouchEvent> new_event(new ui::TouchEvent(
-      type, gfx::Point(), event.time_stamp(), event.pointer_details()));
-  new_event->set_location_f(location_f);
-  new_event->set_root_location_f(location_f);
-  new_event->set_flags(event.flags());
-  *rewritten_event = std::move(new_event);
-
-  if (current_touch_ids_.size() == 0)
-    SET_STATE(NO_FINGERS_DOWN);
-
-  return ui::EVENT_REWRITE_REWRITTEN;
-}
-
 ui::EventRewriteStatus TouchExplorationController::InOneFingerPassthrough(
     const ui::TouchEvent& event,
     std::unique_ptr<ui::Event>* rewritten_event) {
@@ -842,8 +755,6 @@
       return;
     }
     case SINGLE_TAP_PRESSED:
-      if (passthrough_timer_.IsRunning())
-        return;
       FALLTHROUGH;
     case GESTURE_IN_PROGRESS:
       // If only one finger is down, go into touch exploration.
@@ -871,30 +782,6 @@
   anchor_point_state_ = ANCHOR_POINT_FROM_TOUCH_EXPLORATION;
 }
 
-void TouchExplorationController::OnPassthroughTimerFired() {
-  // The passthrough timer will only fire if if the user has held a finger in
-  // one of the passthrough corners for the duration of the passthrough timeout.
-
-  // Check that initial press isn't null. Also a check that if the initial
-  // corner press was released, then it should not be in corner passthrough.
-  if (!initial_press_ ||
-      touch_locations_.find(initial_press_->pointer_details().id) !=
-          touch_locations_.end()) {
-  }
-
-  gfx::Point location =
-      ToRoundedPoint(touch_locations_[initial_press_->pointer_details().id]);
-  int corner = FindEdgesWithinInset(location, kSlopDistanceFromEdge);
-  if (corner != BOTTOM_LEFT_CORNER && corner != BOTTOM_RIGHT_CORNER)
-    return;
-
-  if (sound_timer_.IsRunning())
-    sound_timer_.Stop();
-  delegate_->PlayPassthroughEarcon();
-  SET_STATE(CORNER_PASSTHROUGH);
-  return;
-}
-
 void TouchExplorationController::DispatchEvent(ui::Event* event) {
   SetTouchAccessibilityFlag(event);
   if (event->IsLocatedEvent()) {
@@ -1136,17 +1023,6 @@
   // backing native event.
   flags |= ui::EF_IS_SYNTHESIZED;
 
-  // TODO(dmazzoni) http://crbug.com/391008 - get rid of this hack.
-  // This is a short-term workaround for the limitation that we're using
-  // the ChromeVox content script to process touch exploration events, but
-  // ChromeVox needs a way to distinguish between a real mouse move and a
-  // mouse move generated from touch exploration, so we have touch exploration
-  // pretend that the command key was down (which becomes the "meta" key in
-  // JavaScript). We can remove this hack when the ChromeVox content script
-  // goes away and native accessibility code sends a touch exploration
-  // event to the new ChromeVox background page via the automation api.
-  flags |= ui::EF_COMMAND_DOWN;
-
   std::unique_ptr<ui::MouseEvent> event(new ui::MouseEvent(
       ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), Now(), flags, 0));
   event->set_location_f(location);
@@ -1179,7 +1055,6 @@
     case TOUCH_EXPLORATION:
     case TOUCH_EXPLORE_SECOND_PRESS:
     case ONE_FINGER_PASSTHROUGH:
-    case CORNER_PASSTHROUGH:
     case WAIT_FOR_NO_FINGERS:
       if (gesture_provider_.get())
         gesture_provider_.reset(NULL);
@@ -1257,8 +1132,6 @@
       return "GESTURE_IN_PROGRESS";
     case TOUCH_EXPLORE_SECOND_PRESS:
       return "TOUCH_EXPLORE_SECOND_PRESS";
-    case CORNER_PASSTHROUGH:
-      return "CORNER_PASSTHROUGH";
     case SLIDE_GESTURE:
       return "SLIDE_GESTURE";
     case ONE_FINGER_PASSTHROUGH:
diff --git a/ash/accessibility/touch_exploration_controller.h b/ash/accessibility/touch_exploration_controller.h
index 16af3d72..7b6228aa 100644
--- a/ash/accessibility/touch_exploration_controller.h
+++ b/ash/accessibility/touch_exploration_controller.h
@@ -236,9 +236,6 @@
   ui::EventRewriteStatus InTouchExploration(
       const ui::TouchEvent& event,
       std::unique_ptr<ui::Event>* rewritten_event);
-  ui::EventRewriteStatus InCornerPassthrough(
-      const ui::TouchEvent& event,
-      std::unique_ptr<ui::Event>* rewritten_event);
   ui::EventRewriteStatus InOneFingerPassthrough(
       const ui::TouchEvent& event,
       std::unique_ptr<ui::Event>* rewritten_event);
@@ -268,13 +265,6 @@
   void StartTapTimer();
   void OnTapTimerFired();
 
-  // This timer is started every timer we get the first press event and the
-  // finger is in the corner of the screen.
-  // It fires after the corner passthrough delay elapses. If the
-  // user is still in the corner by the time this timer fires, all subsequent
-  // fingers added on the screen will be passed through.
-  void OnPassthroughTimerFired();
-
   // Dispatch a new event outside of the event rewriting flow.
   void DispatchEvent(ui::Event* event);
 
@@ -403,11 +393,6 @@
     // all fingers.
     ONE_FINGER_PASSTHROUGH,
 
-    // If the user has pressed and held down the left corner past long press,
-    // then as long as they are holding the corner, all subsequent fingers
-    // registered will be in passthrough.
-    CORNER_PASSTHROUGH,
-
     // If the user added another finger in SINGLE_TAP_PRESSED, or if the user
     // has multiple fingers fingers down in any other state between
     // passthrough, touch exploration, and gestures, they must release
@@ -512,9 +497,6 @@
   // A timer that fires after the double-tap delay.
   base::OneShotTimer tap_timer_;
 
-  // A timer that fires to enter passthrough.
-  base::OneShotTimer passthrough_timer_;
-
   // A timer to fire an indicating sound when sliding to change volume.
   base::RepeatingTimer sound_timer_;
 
diff --git a/ash/accessibility/touch_exploration_controller_unittest.cc b/ash/accessibility/touch_exploration_controller_unittest.cc
index a1c222bb..fc013a51 100644
--- a/ash/accessibility/touch_exploration_controller_unittest.cc
+++ b/ash/accessibility/touch_exploration_controller_unittest.cc
@@ -132,12 +132,6 @@
     touch_exploration_controller_->OnTapTimerFired();
   }
 
-  void CallPassthroughTimerNowForTesting() {
-    DCHECK(touch_exploration_controller_->passthrough_timer_.IsRunning());
-    touch_exploration_controller_->passthrough_timer_.Stop();
-    touch_exploration_controller_->OnPassthroughTimerFired();
-  }
-
   void CallTapTimerNowIfRunningForTesting() {
     if (touch_exploration_controller_->tap_timer_.IsRunning()) {
       touch_exploration_controller_->tap_timer_.Stop();
@@ -164,10 +158,6 @@
     return touch_exploration_controller_->state_ ==
            touch_exploration_controller_->TWO_FINGER_TAP;
   }
-  bool IsInCornerPassthroughStateForTesting() const {
-    return touch_exploration_controller_->state_ ==
-           touch_exploration_controller_->CORNER_PASSTHROUGH;
-  }
 
   gfx::Rect BoundsOfRootWindowInDIPForTesting() const {
     return touch_exploration_controller_->root_window_->GetBoundsInScreen();
@@ -283,11 +273,6 @@
     touch_exploration_controller_->CallTapTimerNowForTesting();
   }
 
-  void AdvanceSimulatedTimePastPassthroughDelay() {
-    simulated_clock_.Advance(base::TimeDelta::FromMilliseconds(1000));
-    touch_exploration_controller_->CallPassthroughTimerNowForTesting();
-  }
-
   void AdvanceSimulatedTimePastPotentialTapDelay() {
     simulated_clock_.Advance(base::TimeDelta::FromMilliseconds(1000));
     touch_exploration_controller_->CallTapTimerNowIfRunningForTesting();
@@ -317,52 +302,6 @@
     EXPECT_TRUE(IsInTouchToMouseMode());
   }
 
-  // Checks that Corner Passthrough is working. Assumes that corner is the
-  // bottom left corner or the bottom right corner.
-  void AssertCornerPassthroughWorking(gfx::Point corner) {
-    ASSERT_EQ(0U, delegate_.NumPassthroughSounds());
-
-    ui::TouchEvent first_press(
-        ui::ET_TOUCH_PRESSED, corner, Now(),
-        ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 0));
-    generator_->Dispatch(&first_press);
-
-    AdvanceSimulatedTimePastPassthroughDelay();
-    EXPECT_FALSE(IsInGestureInProgressState());
-    EXPECT_FALSE(IsInSlideGestureState());
-    EXPECT_FALSE(IsInTouchToMouseMode());
-    EXPECT_TRUE(IsInCornerPassthroughState());
-
-    gfx::Rect window = BoundsOfRootWindowInDIP();
-    // The following events should be passed through.
-    gfx::Point passthrough(window.right() / 2, window.bottom() / 2);
-    ui::TouchEvent passthrough_press(
-        ui::ET_TOUCH_PRESSED, passthrough, Now(),
-        ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 1));
-    ASSERT_EQ(1U, delegate_.NumPassthroughSounds());
-    generator_->Dispatch(&passthrough_press);
-    generator_->ReleaseTouchId(1);
-    generator_->PressTouchId(1);
-    EXPECT_FALSE(IsInGestureInProgressState());
-    EXPECT_FALSE(IsInSlideGestureState());
-    EXPECT_TRUE(IsInCornerPassthroughState());
-
-    std::vector<ui::LocatedEvent*> captured_events = GetCapturedLocatedEvents();
-    ASSERT_EQ(3U, captured_events.size());
-    EXPECT_EQ(ui::ET_TOUCH_PRESSED, captured_events[0]->type());
-    EXPECT_EQ(ui::ET_TOUCH_RELEASED, captured_events[1]->type());
-    EXPECT_EQ(ui::ET_TOUCH_PRESSED, captured_events[2]->type());
-    generator_->ReleaseTouchId(1);
-    ClearCapturedEvents();
-
-    generator_->ReleaseTouchId(0);
-    captured_events = GetCapturedLocatedEvents();
-    ASSERT_EQ(0U, captured_events.size());
-    EXPECT_FALSE(IsInTouchToMouseMode());
-    EXPECT_FALSE(IsInCornerPassthroughState());
-    ClearCapturedEvents();
-  }
-
   bool IsInTouchToMouseMode() {
     aura::client::CursorClient* cursor_client =
         aura::client::GetCursorClient(root_window());
@@ -387,11 +326,6 @@
     return touch_exploration_controller_->IsInTwoFingerTapStateForTesting();
   }
 
-  bool IsInCornerPassthroughState() {
-    return touch_exploration_controller_
-        ->IsInCornerPassthroughStateForTesting();
-  }
-
   gfx::Rect BoundsOfRootWindowInDIP() {
     return touch_exploration_controller_->BoundsOfRootWindowInDIPForTesting();
   }
@@ -1898,106 +1832,6 @@
   EXPECT_FALSE(IsInTwoFingerTapState());
 }
 
-// Corner passthrough should turn on if the user first holds down on either the
-// right or left corner past a delay and then places a finger anywhere else on
-// the screen.
-TEST_F(TouchExplorationTest, ActivateLeftCornerPassthrough) {
-  SwitchTouchExplorationMode(true);
-
-  gfx::Rect window = BoundsOfRootWindowInDIP();
-  gfx::Point left_corner(10, window.bottom() - GetMaxDistanceFromEdge() / 2);
-  AssertCornerPassthroughWorking(left_corner);
-}
-
-TEST_F(TouchExplorationTest, ActivateRightCornerPassthrough) {
-  SwitchTouchExplorationMode(true);
-
-  gfx::Rect window = BoundsOfRootWindowInDIP();
-  gfx::Point right_corner(window.right() - GetMaxDistanceFromEdge() / 2,
-                          window.bottom() - GetMaxDistanceFromEdge() / 2);
-  AssertCornerPassthroughWorking(right_corner);
-}
-
-// Earcons should play if the user slides off the screen or enters the screen
-// from the edge.
-TEST_F(TouchExplorationTest, EnterEarconPlays) {
-  SwitchTouchExplorationMode(true);
-
-  gfx::Rect window = BoundsOfRootWindowInDIP();
-
-  gfx::Point upper_left_corner(0, 0);
-  gfx::Point upper_right_corner(window.right(), 0);
-  gfx::Point lower_left_corner(0, window.bottom());
-  gfx::Point lower_right_corner(window.right(), window.bottom());
-  gfx::Point left_edge(0, 30);
-  gfx::Point right_edge(window.right(), 30);
-  gfx::Point top_edge(30, 0);
-  gfx::Point bottom_edge(30, window.bottom());
-
-  std::vector<gfx::Point> locations;
-  locations.push_back(upper_left_corner);
-  locations.push_back(upper_right_corner);
-  locations.push_back(lower_left_corner);
-  locations.push_back(lower_right_corner);
-  locations.push_back(left_edge);
-  locations.push_back(right_edge);
-  locations.push_back(top_edge);
-  locations.push_back(bottom_edge);
-
-  for (std::vector<gfx::Point>::const_iterator point = locations.begin();
-       point != locations.end(); ++point) {
-    ui::TouchEvent touch_event(
-        ui::ET_TOUCH_PRESSED, *point, Now(),
-        ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 1));
-
-    generator_->Dispatch(&touch_event);
-    ASSERT_EQ(1U, delegate_.NumEnterScreenSounds());
-    generator_->ReleaseTouchId(1);
-    delegate_.ResetCountersToZero();
-  }
-}
-
-TEST_F(TouchExplorationTest, ExitEarconPlays) {
-  SwitchTouchExplorationMode(true);
-
-  // On the device, it cannot actually tell if the finger has left the screen or
-  // not. If the finger has left the screen, it reads it as a release that
-  // occurred very close to the edge of the screen even if the finger is still
-  // technically touching the moniter. To simulate this, a release that occurs
-  // close to the edge is dispatched.
-  gfx::Point initial_press(100, 200);
-  gfx::Rect window = BoundsOfRootWindowInDIP();
-
-  gfx::Point upper_left_corner(0, 0);
-  gfx::Point upper_right_corner(window.right(), 0);
-  gfx::Point lower_left_corner(0, window.bottom());
-  gfx::Point lower_right_corner(window.right(), window.bottom());
-  gfx::Point left_edge(0, 30);
-  gfx::Point right_edge(window.right(), 30);
-  gfx::Point top_edge(30, 0);
-  gfx::Point bottom_edge(30, window.bottom());
-
-  std::vector<gfx::Point> locations;
-  locations.push_back(upper_left_corner);
-  locations.push_back(upper_right_corner);
-  locations.push_back(lower_left_corner);
-  locations.push_back(lower_right_corner);
-  locations.push_back(left_edge);
-  locations.push_back(right_edge);
-  locations.push_back(top_edge);
-  locations.push_back(bottom_edge);
-
-  for (std::vector<gfx::Point>::const_iterator point = locations.begin();
-       point != locations.end(); ++point) {
-    generator_->PressTouch();
-    generator_->MoveTouch(initial_press);
-    generator_->MoveTouch(*point);
-    generator_->ReleaseTouch();
-    ASSERT_EQ(1U, delegate_.NumExitScreenSounds());
-    delegate_.ResetCountersToZero();
-  }
-}
-
 TEST_F(TouchExplorationTest, ExclusionArea) {
   SwitchTouchExplorationMode(true);
 
diff --git a/ash/app_list/views/app_list_view.cc b/ash/app_list/views/app_list_view.cc
index 0a5b63fc..b5219d36 100644
--- a/ash/app_list/views/app_list_view.cc
+++ b/ash/app_list/views/app_list_view.cc
@@ -24,9 +24,6 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/user_metrics.h"
 #include "base/strings/string_util.h"
-#include "mojo/public/cpp/bindings/type_converter.h"
-#include "services/ui/public/cpp/property_type_converters.h"
-#include "services/ui/public/interfaces/window_manager.mojom.h"
 #include "ui/accessibility/ax_node.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/accessibility/platform/aura_window_properties.h"
@@ -525,21 +522,7 @@
       views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
 
   app_list_overlay_view_params.name = "AppList";
-  if (parent) {
-    app_list_overlay_view_params.parent = parent;
-  } else {
-    // Under mash, the app list is owned by the browser process, which cannot
-    // directly access the ash window container hierarchy to set |parent|.
-    // TODO(jamescook): Remove this when app_list moves into //ash as |parent|
-    // will not be null.
-    app_list_overlay_view_params
-        .mus_properties[ui::mojom::WindowManager::kContainerId_InitProperty] =
-        mojo::ConvertTo<std::vector<uint8_t>>(parent_container_id);
-    app_list_overlay_view_params
-        .mus_properties[ui::mojom::WindowManager::kDisplayId_InitProperty] =
-        mojo::ConvertTo<std::vector<uint8_t>>(display_nearest_view.id());
-    app_list_overlay_view_params.bounds = local_bounds;
-  }
+  app_list_overlay_view_params.parent = parent;
   app_list_overlay_view_params.delegate = this;
   app_list_overlay_view_params.opacity =
       views::Widget::InitParams::TRANSLUCENT_WINDOW;
@@ -612,7 +595,7 @@
   }
 
   search_box_view_->ClearSearch();
-  search_box_view_->SetSearchBoxActive(false);
+  search_box_view_->SetSearchBoxActive(false, ui::ET_UNKNOWN);
 }
 
 void AppListView::StartDrag(const gfx::Point& location) {
diff --git a/ash/app_list/views/app_list_view_unittest.cc b/ash/app_list/views/app_list_view_unittest.cc
index 671f782..cfb1009 100644
--- a/ash/app_list/views/app_list_view_unittest.cc
+++ b/ash/app_list/views/app_list_view_unittest.cc
@@ -591,7 +591,8 @@
   forward_view_list.push_back(contents_view()
                                   ->search_result_answer_card_view_for_test()
                                   ->GetSearchAnswerContainerViewForTest());
-  SearchResultListView* list_view = contents_view()->search_result_list_view();
+  SearchResultListView* list_view =
+      contents_view()->search_result_list_view_for_test();
   for (int i = 0; i < kListResults; ++i)
     forward_view_list.push_back(list_view->GetResultViewAt(i));
   forward_view_list.push_back(search_box_view()->search_box());
@@ -743,7 +744,8 @@
   forward_view_list.push_back(contents_view()
                                   ->search_result_answer_card_view_for_test()
                                   ->GetSearchAnswerContainerViewForTest());
-  SearchResultListView* list_view = contents_view()->search_result_list_view();
+  SearchResultListView* list_view =
+      contents_view()->search_result_list_view_for_test();
   for (int i = 0; i < kListResults; ++i)
     forward_view_list.push_back(list_view->GetResultViewAt(i));
   forward_view_list.push_back(search_box_view()->search_box());
@@ -1029,7 +1031,8 @@
   search_box_view()->search_box()->InsertText(base::ASCIIToUTF16("test"));
   const int kListResults = 2;
   SetUpSearchResults(0, kListResults, false);
-  SearchResultListView* list_view = contents_view()->search_result_list_view();
+  SearchResultListView* list_view =
+      contents_view()->search_result_list_view_for_test();
   EXPECT_EQ(search_box_view()->search_box(), focused_view());
   EXPECT_EQ(list_view->GetResultViewAt(0),
             contents_view()->search_results_page_view()->first_result_view());
@@ -1072,7 +1075,8 @@
   search_box_view()->search_box()->InsertText(base::ASCIIToUTF16("test1"));
   const int kListResults = 2;
   SetUpSearchResults(0, kListResults, false);
-  SearchResultListView* list_view = contents_view()->search_result_list_view();
+  SearchResultListView* list_view =
+      contents_view()->search_result_list_view_for_test();
   SearchResultBaseView* first_result_view =
       contents_view()->search_results_page_view()->first_result_view();
   EXPECT_EQ(search_box_view()->search_box(), focused_view());
@@ -1156,11 +1160,11 @@
   EXPECT_FALSE(search_box_view()->search_box()->HasFocus());
 
   // Activate the search box.
-  search_box_view()->SetSearchBoxActive(true);
+  search_box_view()->SetSearchBoxActive(true, ui::ET_MOUSE_PRESSED);
   EXPECT_TRUE(search_box_view()->search_box()->HasFocus());
 
   // Deactivate the search box won't move focus away.
-  search_box_view()->SetSearchBoxActive(false);
+  search_box_view()->SetSearchBoxActive(false, ui::ET_MOUSE_PRESSED);
   EXPECT_TRUE(search_box_view()->search_box()->HasFocus());
 }
 
diff --git a/ash/app_list/views/contents_view.cc b/ash/app_list/views/contents_view.cc
index 02315411..37fc55d 100644
--- a/ash/app_list/views/contents_view.cc
+++ b/ash/app_list/views/contents_view.cc
@@ -428,7 +428,7 @@
       break;
     case ash::AppListState::kStateSearchResults:
       GetSearchBoxView()->ClearSearch();
-      GetSearchBoxView()->SetSearchBoxActive(false);
+      GetSearchBoxView()->SetSearchBoxActive(false, ui::ET_UNKNOWN);
       ShowSearchResults(false);
       break;
     case ash::AppListState::kStateCustomLauncherPageDeprecated:
diff --git a/ash/app_list/views/contents_view.h b/ash/app_list/views/contents_view.h
index dba44373..4ebf3b3 100644
--- a/ash/app_list/views/contents_view.h
+++ b/ash/app_list/views/contents_view.h
@@ -108,7 +108,7 @@
       const {
     return search_result_tile_item_list_view_;
   }
-  SearchResultListView* search_result_list_view() const {
+  SearchResultListView* search_result_list_view_for_test() const {
     return search_result_list_view_;
   }
   HorizontalPageContainer* horizontal_page_container() const {
diff --git a/ash/app_list/views/search_box_view.cc b/ash/app_list/views/search_box_view.cc
index 1beebba..f1184f7 100644
--- a/ash/app_list/views/search_box_view.cc
+++ b/ash/app_list/views/search_box_view.cc
@@ -24,6 +24,7 @@
 #include "ash/public/cpp/app_list/vector_icons/vector_icons.h"
 #include "ash/public/cpp/wallpaper_types.h"
 #include "base/macros.h"
+#include "base/metrics/histogram_macros.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/chromeos/search_box/search_box_constants.h"
@@ -65,6 +66,9 @@
 constexpr float kOpacityStartFraction = 0.1f;
 constexpr float kOpacityEndFraction = 0.6f;
 
+// Minimum amount of characters required to enable autocomplete.
+constexpr int kMinimumLengthToAutocomplete = 2;
+
 // Gets the box layout inset horizontal padding for the state of AppListModel.
 int GetBoxLayoutPaddingForState(ash::AppListState state) {
   if (state == ash::AppListState::kStateSearchResults)
@@ -204,6 +208,26 @@
   back->SetTooltipText(back_button_label);
 }
 
+void SearchBoxView::RecordSearchBoxActivationHistogram(
+    ui::EventType event_type) {
+  search_box::ActivationSource activation_type;
+  switch (event_type) {
+    case ui::ET_GESTURE_TAP:
+      activation_type = search_box::ActivationSource::kGestureTap;
+      break;
+    case ui::ET_MOUSE_PRESSED:
+      activation_type = search_box::ActivationSource::kMousePress;
+      break;
+    case ui::ET_KEY_PRESSED:
+      activation_type = search_box::ActivationSource::kKeyPress;
+      break;
+    default:
+      return;
+  }
+
+  UMA_HISTOGRAM_ENUMERATION("Apps.AppListSearchBoxActivated", activation_type);
+}
+
 void SearchBoxView::OnKeyEvent(ui::KeyEvent* event) {
   app_list_view_->RedirectKeyEventToSearchBox(event);
 
@@ -304,26 +328,30 @@
                      weak_ptr_factory_.GetWeakPtr()));
 }
 
-void SearchBoxView::ProcessAutocomplete(bool is_search_result_list_view_first) {
-  if (!is_search_result_list_view_first || last_key_pressed_ == ui::VKEY_BACK ||
-      search_model_->results()->item_count() == 0) {
-    // No first result exists, backspace was pressed, or no results exist.
+void SearchBoxView::ProcessAutocomplete() {
+  // Current non-autocompleted text.
+  const base::string16& user_typed_text =
+      search_box()->text().substr(0, highlight_range_.start());
+  if (last_key_pressed_ == ui::VKEY_BACK ||
+      search_model_->results()->item_count() == 0 ||
+      user_typed_text.length() < kMinimumLengthToAutocomplete) {
+    // Backspace was pressed, or no results exist, or current text is too short
+    // for a confident autocomplete suggestion.
     ClearAutocompleteText();
     return;
   }
 
-  const base::string16& current_text = search_box()->text();
   const base::string16& details =
       search_model_->results()->GetItemAt(0)->details();
   const base::string16& search_text =
       search_model_->results()->GetItemAt(0)->title();
-  if (base::StartsWith(details, current_text,
+  if (base::StartsWith(details, user_typed_text,
                        base::CompareCase::INSENSITIVE_ASCII)) {
     // Current text in the search_box matches the first result's url.
     SetAutocompleteText(details);
     return;
   }
-  if (base::StartsWith(search_text, current_text,
+  if (base::StartsWith(search_text, user_typed_text,
                        base::CompareCase::INSENSITIVE_ASCII)) {
     // Current text in the search_box matches the first result's search result
     // text.
@@ -399,17 +427,15 @@
   if (autocomplete_text.length() == current_text.length())
     return;
 
-  search_box()->SetText(autocomplete_text);
+  const base::string16& user_typed_text =
+      current_text.substr(0, highlight_range_.start());
+  const base::string16& highlighted_text =
+      autocomplete_text.substr(highlight_range_.start());
+  search_box()->SetText(user_typed_text + highlighted_text);
   highlight_range_.set_end(autocomplete_text.length());
   search_box()->SelectRange(highlight_range_);
 }
 
-void SearchBoxView::UpdateAutocompleteSelectionRange(uint32_t start,
-                                                     uint32_t end) {
-  highlight_range_.set_start(start);
-  highlight_range_.set_end(end);
-}
-
 bool SearchBoxView::HandleKeyEvent(views::Textfield* sender,
                                    const ui::KeyEvent& key_event) {
   if (search_box()->HasFocus() && is_search_box_active() &&
@@ -454,7 +480,7 @@
     }
 
     if (!is_search_box_active()) {
-      SetSearchBoxActive(true);
+      SetSearchBoxActive(true, key_event.type());
       return true;
     }
     return false;
@@ -481,9 +507,8 @@
   if (!features::IsZeroStateSuggestionsEnabled())
     return;
 
-  if (close_button() && sender == close_button()) {
-    SetSearchBoxActive(false);
-  }
+  if (close_button() && sender == close_button())
+    SetSearchBoxActive(false, ui::ET_UNKNOWN);
 }
 
 void SearchBoxView::HintTextChanged() {
diff --git a/ash/app_list/views/search_box_view.h b/ash/app_list/views/search_box_view.h
index 65aa8dc6..abefa2ab 100644
--- a/ash/app_list/views/search_box_view.h
+++ b/ash/app_list/views/search_box_view.h
@@ -47,6 +47,7 @@
   void UpdateSearchBoxBorder() override;
   void SetupCloseButton() override;
   void SetupBackButton() override;
+  void RecordSearchBoxActivationHistogram(ui::EventType event_type) override;
 
   // Overridden from views::View:
   void OnKeyEvent(ui::KeyEvent* event) override;
@@ -81,7 +82,7 @@
   void OnWallpaperColorsChanged();
 
   // Sets the autocomplete text if autocomplete conditions are met.
-  void ProcessAutocomplete(bool is_search_result_list_view_first);
+  void ProcessAutocomplete();
 
  private:
   // Gets the wallpaper prominent colors.
@@ -106,8 +107,6 @@
   // text to the autocomplete text and sets the text highlight.
   void SetAutocompleteText(const base::string16& autocomplete_text);
 
-  void UpdateAutocompleteSelectionRange(uint32_t start, uint32_t end);
-
   // Overridden from views::TextfieldController:
   void ContentsChanged(views::Textfield* sender,
                        const base::string16& new_contents) override;
diff --git a/ash/app_list/views/search_box_view_unittest.cc b/ash/app_list/views/search_box_view_unittest.cc
index 3a66526..9b85cba 100644
--- a/ash/app_list/views/search_box_view_unittest.cc
+++ b/ash/app_list/views/search_box_view_unittest.cc
@@ -93,7 +93,9 @@
     view_delegate_.SetSearchEngineIsGoogle(is_google);
   }
 
-  void SetSearchBoxActive(bool active) { view()->SetSearchBoxActive(active); }
+  void SetSearchBoxActive(bool active, ui::EventType type) {
+    view()->SetSearchBoxActive(active, type);
+  }
 
   int GetContentsViewKeyPressCountAndReset() {
     return counter_view_->GetCountAndReset();
@@ -156,7 +158,7 @@
 // Tests that the close button is still invisible after the search box is
 // activated.
 TEST_F(SearchBoxViewTest, CloseButtonInvisibleAfterSearchBoxActived) {
-  view()->SetSearchBoxActive(true);
+  SetSearchBoxActive(true, ui::ET_MOUSE_PRESSED);
   EXPECT_FALSE(view()->close_button()->visible());
 }
 
@@ -201,7 +203,7 @@
 // Tests that the black Google icon is used for an inactive Google search.
 TEST_F(SearchBoxViewTest, SearchBoxInactiveSearchBoxGoogle) {
   SetSearchEngineIsGoogle(true);
-  SetSearchBoxActive(false);
+  SetSearchBoxActive(false, ui::ET_UNKNOWN);
   const gfx::ImageSkia expected_icon =
       gfx::CreateVectorIcon(kIcGoogleBlackIcon, search_box::kSearchIconSize,
                             search_box::kDefaultSearchboxColor);
@@ -217,7 +219,7 @@
 // Tests that the colored Google icon is used for an active Google search.
 TEST_F(SearchBoxViewTest, SearchBoxActiveSearchEngineGoogle) {
   SetSearchEngineIsGoogle(true);
-  SetSearchBoxActive(true);
+  SetSearchBoxActive(true, ui::ET_MOUSE_PRESSED);
   const gfx::ImageSkia expected_icon =
       gfx::CreateVectorIcon(kIcGoogleColorIcon, search_box::kSearchIconSize,
                             search_box::kDefaultSearchboxColor);
@@ -233,7 +235,7 @@
 // Tests that the non-Google icon is used for an inactive non-Google search.
 TEST_F(SearchBoxViewTest, SearchBoxInactiveSearchEngineNotGoogle) {
   SetSearchEngineIsGoogle(false);
-  SetSearchBoxActive(false);
+  SetSearchBoxActive(false, ui::ET_UNKNOWN);
   const gfx::ImageSkia expected_icon = gfx::CreateVectorIcon(
       kIcSearchEngineNotGoogleIcon, search_box::kSearchIconSize,
       search_box::kDefaultSearchboxColor);
@@ -249,7 +251,7 @@
 // Tests that the non-Google icon is used for an active non-Google search.
 TEST_F(SearchBoxViewTest, SearchBoxActiveSearchEngineNotGoogle) {
   SetSearchEngineIsGoogle(false);
-  SetSearchBoxActive(true);
+  SetSearchBoxActive(true, ui::ET_UNKNOWN);
   const gfx::ImageSkia expected_icon = gfx::CreateVectorIcon(
       kIcSearchEngineNotGoogleIcon, search_box::kSearchIconSize,
       search_box::kDefaultSearchboxColor);
diff --git a/ash/app_list/views/search_result_page_view.cc b/ash/app_list/views/search_result_page_view.cc
index 2c34077..3683c5b7 100644
--- a/ash/app_list/views/search_result_page_view.cc
+++ b/ash/app_list/views/search_result_page_view.cc
@@ -280,11 +280,8 @@
 
   // Update SearchBoxView search box autocomplete as necessary based on new
   // first result view.
-  if (first_result_view_) {
-    AppListPage::contents_view()->GetSearchBoxView()->ProcessAutocomplete(
-        result_container_views_[0] ==
-        AppListPage::contents_view()->search_result_list_view());
-  }
+  if (first_result_view_)
+    AppListPage::contents_view()->GetSearchBoxView()->ProcessAutocomplete();
 
   // If one of the search result is focused, do not highlight the first search
   // result.
diff --git a/ash/app_list/views/search_result_page_view_unittest.cc b/ash/app_list/views/search_result_page_view_unittest.cc
index 43a774a..e271f953 100644
--- a/ash/app_list/views/search_result_page_view_unittest.cc
+++ b/ash/app_list/views/search_result_page_view_unittest.cc
@@ -78,7 +78,7 @@
     view_ = contents_view->search_results_page_view();
     tile_list_view_ =
         contents_view->search_result_tile_item_list_view_for_test();
-    list_view_ = contents_view->search_result_list_view();
+    list_view_ = contents_view->search_result_list_view_for_test();
   }
   void TearDown() override {
     app_list_view_->GetWidget()->Close();
diff --git a/ash/assistant/assistant_ui_controller.cc b/ash/assistant/assistant_ui_controller.cc
index 3a59ceb..107c80d 100644
--- a/ash/assistant/assistant_ui_controller.cc
+++ b/ash/assistant/assistant_ui_controller.cc
@@ -203,6 +203,10 @@
 
 void AssistantUiController::OnUiVisibilityChanged(bool visible,
                                                   AssistantSource source) {
+  Shell::Get()->voice_interaction_controller()->NotifyStatusChanged(
+      visible ? mojom::VoiceInteractionState::RUNNING
+              : mojom::VoiceInteractionState::STOPPED);
+
   if (visible)
     return;
 
diff --git a/ash/assistant/ui/main_stage/assistant_main_stage.cc b/ash/assistant/ui/main_stage/assistant_main_stage.cc
index 82df9462..0bab4b62 100644
--- a/ash/assistant/ui/main_stage/assistant_main_stage.cc
+++ b/ash/assistant/ui/main_stage/assistant_main_stage.cc
@@ -6,6 +6,7 @@
 
 #include "ash/assistant/assistant_controller.h"
 #include "ash/assistant/assistant_interaction_controller.h"
+#include "ash/assistant/assistant_ui_controller.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/suggestion_container_view.h"
@@ -25,6 +26,8 @@
 namespace {
 
 // Animation.
+// TODO(dmblack): Rename these constants to refer to the specific views they are
+// animating to avoid confusion.
 constexpr base::TimeDelta kAnimationExitFadeDuration =
     base::TimeDelta::FromMilliseconds(50);
 constexpr int kAnimationExitTranslationDip = -103;
@@ -41,6 +44,23 @@
 constexpr base::TimeDelta kAnimationTranslateUpDuration =
     base::TimeDelta::FromMilliseconds(333);
 
+// Pending query animation.
+constexpr base::TimeDelta kPendingQueryAnimationFadeInDuration =
+    base::TimeDelta::FromMilliseconds(433);
+
+// Suggestion container animation.
+constexpr int kSuggestionContainerAnimationTranslationDip = 22;
+constexpr base::TimeDelta kSuggestionContainerAnimationTranslationDelay =
+    base::TimeDelta::FromMilliseconds(66);
+constexpr base::TimeDelta kSuggestionContainerAnimationTranslationDuration =
+    base::TimeDelta::FromMilliseconds(416);
+constexpr base::TimeDelta kSuggestionContainerAnimationFadeInDelay =
+    base::TimeDelta::FromMilliseconds(149);
+constexpr base::TimeDelta kSuggestionContainerAnimationFadeInDuration =
+    base::TimeDelta::FromMilliseconds(250);
+constexpr base::TimeDelta kSuggestionContainerAnimationFadeOutDuration =
+    base::TimeDelta::FromMilliseconds(100);
+
 // StackLayout -----------------------------------------------------------------
 
 // A layout manager which lays out its views atop each other. This differs from
@@ -98,10 +118,18 @@
 AssistantMainStage::AssistantMainStage(
     AssistantController* assistant_controller)
     : assistant_controller_(assistant_controller),
-      committed_query_exit_animation_observer_(
+      active_query_exit_animation_observer_(
           std::make_unique<ui::CallbackLayerAnimationObserver>(
               /*animation_ended_callback=*/base::BindRepeating(
-                  &AssistantMainStage::OnCommittedQueryExitAnimationEnded,
+                  &AssistantMainStage::OnActiveQueryExitAnimationEnded,
+                  base::Unretained(this)))),
+      suggestion_container_animation_observer_(
+          std::make_unique<ui::CallbackLayerAnimationObserver>(
+              /*animation_started_callback=*/base::BindRepeating(
+                  &AssistantMainStage::OnSuggestionContainerAnimationStarted,
+                  base::Unretained(this)),
+              /*animation_ended_callback=*/base::BindRepeating(
+                  &AssistantMainStage::OnSuggestionContainerAnimationEnded,
                   base::Unretained(this)))) {
   InitLayout(assistant_controller);
 
@@ -109,9 +137,11 @@
   // AssistantController, so AssistantController is guaranteed to outlive the
   // AssistantMainStage.
   assistant_controller_->interaction_controller()->AddModelObserver(this);
+  assistant_controller_->ui_controller()->AddModelObserver(this);
 }
 
 AssistantMainStage::~AssistantMainStage() {
+  assistant_controller_->ui_controller()->RemoveModelObserver(this);
   assistant_controller_->interaction_controller()->RemoveModelObserver(this);
 }
 
@@ -124,17 +154,17 @@
 }
 
 void AssistantMainStage::OnViewBoundsChanged(views::View* view) {
-  if (view == committed_query_view_) {
-    UpdateCommittedQueryViewSpacer();
+  if (view == active_query_view_) {
+    UpdateActiveQueryViewSpacer();
+  } else if (view == committed_query_view_) {
+    UpdateQueryViewTransform(committed_query_view_);
   } else if (view == pending_query_view_) {
-    // The pending query should be bottom aligned in its parent until it is
-    // committed at which point it is animated to the top.
-    const int top_offset =
-        query_layout_container_->height() - pending_query_view_->height();
-
-    gfx::Transform transform;
-    transform.Translate(0, top_offset);
-    pending_query_view_->layer()->SetTransform(transform);
+    UpdateQueryViewTransform(pending_query_view_);
+  } else if (view == query_layout_container_) {
+    if (committed_query_view_)
+      UpdateQueryViewTransform(committed_query_view_);
+    if (pending_query_view_)
+      UpdateQueryViewTransform(pending_query_view_);
   }
 }
 
@@ -149,6 +179,12 @@
 void AssistantMainStage::InitLayout(AssistantController* assistant_controller) {
   SetLayoutManager(std::make_unique<views::FillLayout>());
 
+  // The children of AssistantMainStage will be animated on their own layers and
+  // we want them to be clipped by their parent layer.
+  SetPaintToLayer();
+  layer()->SetFillsBoundsOpaquely(false);
+  layer()->SetMasksToBounds(true);
+
   InitContentLayoutContainer(assistant_controller);
   InitQueryLayoutContainer(assistant_controller);
 }
@@ -165,12 +201,12 @@
       std::make_unique<views::BoxLayout>(
           views::BoxLayout::Orientation::kVertical));
 
-  // Committed query spacer.
-  // Note: This view reserves layout space for |committed_query_view_|,
+  // Active query spacer.
+  // Note: This view reserves layout space for |active_query_view_|,
   // dynamically mirroring its preferred size and visibility.
-  committed_query_view_spacer_ = new views::View();
-  committed_query_view_spacer_->AddObserver(this);
-  content_layout_container->AddChildView(committed_query_view_spacer_);
+  active_query_view_spacer_ = new views::View();
+  active_query_view_spacer_->AddObserver(this);
+  content_layout_container->AddChildView(active_query_view_spacer_);
 
   // UI element container.
   ui_element_container_ = new UiElementContainerView(assistant_controller);
@@ -199,6 +235,7 @@
   // necessary because |query_layout_container_| may not change size in response
   // to these events, thereby requiring an explicit layout pass.
   query_layout_container_ = new views::View();
+  query_layout_container_->AddObserver(this);
   query_layout_container_->set_can_process_events_within_subtree(false);
   query_layout_container_->SetLayoutManager(std::make_unique<StackLayout>());
 
@@ -206,22 +243,35 @@
 }
 
 void AssistantMainStage::OnCommittedQueryChanged(const AssistantQuery& query) {
-  // Clean up any previous committed query.
-  OnCommittedQueryCleared();
-
+  // The pending query has been committed. Update our pointers.
   committed_query_view_ = pending_query_view_;
   pending_query_view_ = nullptr;
 
   // Update the view.
   committed_query_view_->SetQuery(query);
 
-  // Upon query commit, the view for the query should move from the bottom of
-  // its parent to the top. This transition is animated in the motion spec.
+  // When the motion spec is disabled, we will immediately activate the
+  // committed query to prevent deviation from current UI behavior. When the
+  // motion spec is enabled, we activate the query upon receipt of the response.
+  if (!assistant::ui::kIsMotionSpecEnabled)
+    OnActivateQuery();
+}
+
+void AssistantMainStage::OnActivateQuery() {
+  // Clear the previously active query.
+  OnActiveQueryCleared();
+
+  active_query_view_ = committed_query_view_;
+  committed_query_view_ = nullptr;
+
+  // Upon response delivery, we consider a query active. The view for the query
+  // should move from the bottom of its parent to the top. This transition is
+  // animated when the motion spec is enabled.
   if (!assistant::ui::kIsMotionSpecEnabled) {
-    committed_query_view_->layer()->SetTransform(gfx::Transform());
+    active_query_view_->layer()->SetTransform(gfx::Transform());
   } else {
     using namespace assistant::util;
-    committed_query_view_->layer()->GetAnimator()->StartTogether(
+    active_query_view_->layer()->GetAnimator()->StartTogether(
         {// Animate transformation.
          CreateLayerAnimationSequence(
              // Pause...
@@ -244,30 +294,29 @@
              CreateOpacityElement(1.f, kAnimationFadeInDuration))});
   }
 
-  UpdateCommittedQueryViewSpacer();
+  UpdateActiveQueryViewSpacer();
   UpdateSuggestionContainer();
 }
 
-void AssistantMainStage::OnCommittedQueryCleared() {
-  if (!committed_query_view_)
+void AssistantMainStage::OnActiveQueryCleared() {
+  if (!active_query_view_)
     return;
 
   if (!assistant::ui::kIsMotionSpecEnabled) {
-    delete committed_query_view_;
-    committed_query_view_ = nullptr;
-    UpdateCommittedQueryViewSpacer();
+    delete active_query_view_;
+    active_query_view_ = nullptr;
     return;
   }
 
   using namespace assistant::util;
 
-  // The previous committed query view will translate off stage.
+  // The active query view will translate off stage.
   gfx::Transform transform;
   transform.Translate(0, kAnimationExitTranslationDip);
 
-  // Animate an exit of the previous committed query view.
+  // Animate the exit of the action query view.
   StartLayerAnimationSequencesTogether(
-      committed_query_view_->layer()->GetAnimator(),
+      active_query_view_->layer()->GetAnimator(),
       {// Animate transformation.
        CreateLayerAnimationSequence(
            CreateTransformElement(transform, kAnimationExitTranslateDuration,
@@ -276,28 +325,36 @@
        CreateLayerAnimationSequence(
            CreateOpacityElement(0.f, kAnimationExitFadeDuration))},
       // Observe the animation.
-      committed_query_exit_animation_observer_.get());
+      active_query_exit_animation_observer_.get());
 
   // Set the animation observer to active so that we receive callback events.
-  committed_query_exit_animation_observer_->SetActive();
+  active_query_exit_animation_observer_->SetActive();
 
   // Note that we have not yet deleted the query view but it will be cleaned up
   // when the animation observer callback runs.
-  committed_query_view_ = nullptr;
+  active_query_view_ = nullptr;
 }
 
-bool AssistantMainStage::OnCommittedQueryExitAnimationEnded(
+bool AssistantMainStage::OnActiveQueryExitAnimationEnded(
     const ui::CallbackLayerAnimationObserver& observer) {
-  // The exited committed query view will always be the first child of its
-  // parent view.
+  // The exited active query view will always be the first child of its parent.
   delete query_layout_container_->child_at(0);
-  UpdateCommittedQueryViewSpacer();
+  UpdateActiveQueryViewSpacer();
 
   // Return false to prevent the observer from destroying itself.
   return false;
 }
 
 void AssistantMainStage::OnPendingQueryChanged(const AssistantQuery& query) {
+  // It is possible for the user to pend multiple queries in rapid succession.
+  // When this happens, a new query can be pended before the previously
+  // committed query was answered and activated. When this occurs, we discard
+  // the view for the previously committed query as it has been aborted.
+  if (committed_query_view_) {
+    delete committed_query_view_;
+    committed_query_view_ = nullptr;
+  }
+
   if (!pending_query_view_) {
     pending_query_view_ = new AssistantQueryView();
     pending_query_view_->AddObserver(this);
@@ -308,6 +365,18 @@
 
     query_layout_container_->AddChildView(pending_query_view_);
 
+    if (assistant::ui::kIsMotionSpecEnabled) {
+      using namespace assistant::util;
+
+      // Starting from 0% opacity...
+      pending_query_view_->layer()->SetOpacity(0.f);
+
+      // ...animate the pending query view to 100% opacity.
+      pending_query_view_->layer()->GetAnimator()->StartAnimation(
+          CreateLayerAnimationSequence(
+              CreateOpacityElement(1.f, kPendingQueryAnimationFadeInDuration)));
+    }
+
     UpdateSuggestionContainer();
   }
 
@@ -320,24 +389,133 @@
     pending_query_view_ = nullptr;
   }
 
+  // If the pending query is cleared but a committed query exists, we don't
+  // need to update the suggestions container because the suggestion container
+  // visibility state should not have changed.
+  if (!committed_query_view_)
+    UpdateSuggestionContainer();
+}
+
+void AssistantMainStage::OnResponseChanged(const AssistantResponse& response) {
+  // If the motion spec is enabled, we only consider the query active once
+  // the response has been received. When the motion spec is disabled, we
+  // immediately activate the query as it is committed.
+  if (assistant::ui::kIsMotionSpecEnabled)
+    OnActivateQuery();
+}
+
+void AssistantMainStage::OnUiVisibilityChanged(bool visible,
+                                               AssistantSource source) {
+  if (visible)
+    return;
+
+  delete active_query_view_;
+  active_query_view_ = nullptr;
+
+  delete committed_query_view_;
+  committed_query_view_ = nullptr;
+
+  delete pending_query_view_;
+  pending_query_view_ = nullptr;
+
+  UpdateActiveQueryViewSpacer();
   UpdateSuggestionContainer();
 }
 
-void AssistantMainStage::UpdateCommittedQueryViewSpacer() {
-  // The spacer reserves room in the layout for the committed query view, so
-  // it should match its size.
-  committed_query_view_spacer_->SetPreferredSize(
-      committed_query_view_ ? committed_query_view_->size() : gfx::Size());
+void AssistantMainStage::UpdateActiveQueryViewSpacer() {
+  // The spacer reserves room in the layout for the active query view, so it
+  // needs to match its size.
+  active_query_view_spacer_->SetPreferredSize(
+      active_query_view_ ? active_query_view_->size() : gfx::Size());
 }
 
-// TODO(dmblack): Animate visibility changes.
+void AssistantMainStage::UpdateQueryViewTransform(views::View* query_view) {
+  // Unless activated, a view for a query should be bottom aligned in its
+  // parent. We calculate a top offset and apply a transformation to the query
+  // view to have that effect and animate the transformation back to identity
+  // when the query is activated on delivery of its response.
+  DCHECK_NE(query_view, active_query_view_);
+
+  const int top_offset =
+      query_layout_container_->height() - query_view->height();
+
+  gfx::Transform transform;
+  transform.Translate(0, top_offset);
+  query_view->layer()->SetTransform(transform);
+}
+
 void AssistantMainStage::UpdateSuggestionContainer() {
-  // The suggestion container is only visible when the pending query is not.
-  // When it is not visible, it should not process events.
-  bool visible = pending_query_view_ == nullptr;
-  suggestion_container_->layer()->SetOpacity(visible ? 1.f : 0.f);
-  suggestion_container_->set_can_process_events_within_subtree(visible ? true
-                                                                       : false);
+  // The suggestion container is only visible when the committed/pending query
+  // views are not. When it is not visible, it should not process events.
+  bool visible = !committed_query_view_ && !pending_query_view_;
+
+  if (!assistant::ui::kIsMotionSpecEnabled) {
+    suggestion_container_->layer()->SetOpacity(visible ? 1.f : 0.f);
+    suggestion_container_->set_can_process_events_within_subtree(
+        visible ? true : false);
+    return;
+  }
+
+  using namespace assistant::util;
+
+  if (visible) {
+    // The suggestion container will animate up into position so we need to set
+    // an initial offset transformation from which to animate.
+    gfx::Transform transform;
+    transform.Translate(0, kSuggestionContainerAnimationTranslationDip);
+    suggestion_container_->layer()->SetTransform(transform);
+
+    // Animate the entry of the suggestion container.
+    StartLayerAnimationSequencesTogether(
+        suggestion_container_->layer()->GetAnimator(),
+        {// Animate the translation with delay.
+         CreateLayerAnimationSequence(
+             ui::LayerAnimationElement::CreatePauseElement(
+                 ui::LayerAnimationElement::AnimatableProperty::TRANSFORM,
+                 kSuggestionContainerAnimationTranslationDelay),
+             CreateTransformElement(
+                 gfx::Transform(),
+                 kSuggestionContainerAnimationTranslationDuration,
+                 gfx::Tween::Type::FAST_OUT_SLOW_IN_2)),
+         // Animate the fade in with delay.
+         CreateLayerAnimationSequence(
+             ui::LayerAnimationElement::CreatePauseElement(
+                 ui::LayerAnimationElement::AnimatableProperty::OPACITY,
+                 kSuggestionContainerAnimationFadeInDelay),
+             CreateOpacityElement(
+                 1.f, kSuggestionContainerAnimationFadeInDuration))},
+        // Observer animation start/end events.
+        suggestion_container_animation_observer_.get());
+  } else {
+    // Animate the exit of the suggestion container.
+    StartLayerAnimationSequence(
+        suggestion_container_->layer()->GetAnimator(),
+        // Animate fade out.
+        CreateLayerAnimationSequence(CreateOpacityElement(
+            0.f, kSuggestionContainerAnimationFadeOutDuration)),
+        // Observe animation start/end events.
+        suggestion_container_animation_observer_.get());
+  }
+
+  // Set the observer to active so that we'll receive start/end events.
+  suggestion_container_animation_observer_->SetActive();
+}
+
+void AssistantMainStage::OnSuggestionContainerAnimationStarted(
+    const ui::CallbackLayerAnimationObserver& observer) {
+  // The suggestion container should not process events while animating.
+  suggestion_container_->set_can_process_events_within_subtree(false);
+}
+
+bool AssistantMainStage::OnSuggestionContainerAnimationEnded(
+    const ui::CallbackLayerAnimationObserver& observer) {
+  // The suggestion container should only process events when visible. It is
+  // only visible when there is no committed or pending query view.
+  suggestion_container_->set_can_process_events_within_subtree(
+      !committed_query_view_ && !pending_query_view_);
+
+  // Return false so that the observer does not destroy itself.
+  return false;
 }
 
 }  // namespace ash
diff --git a/ash/assistant/ui/main_stage/assistant_main_stage.h b/ash/assistant/ui/main_stage/assistant_main_stage.h
index f420799..32615823 100644
--- a/ash/assistant/ui/main_stage/assistant_main_stage.h
+++ b/ash/assistant/ui/main_stage/assistant_main_stage.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "ash/assistant/model/assistant_interaction_model_observer.h"
+#include "ash/assistant/model/assistant_ui_model_observer.h"
 #include "base/macros.h"
 #include "ui/views/view.h"
 #include "ui/views/view_observer.h"
@@ -28,7 +29,8 @@
 // affordances for the query, response, as well as suggestions.
 class AssistantMainStage : public views::View,
                            public views::ViewObserver,
-                           public AssistantInteractionModelObserver {
+                           public AssistantInteractionModelObserver,
+                           public AssistantUiModelObserver {
  public:
   explicit AssistantMainStage(AssistantController* assistant_controller);
   ~AssistantMainStage() override;
@@ -44,34 +46,49 @@
 
   // AssistantInteractionModelObserver:
   void OnCommittedQueryChanged(const AssistantQuery& query) override;
-  void OnCommittedQueryCleared() override;
   void OnPendingQueryChanged(const AssistantQuery& query) override;
   void OnPendingQueryCleared() override;
+  void OnResponseChanged(const AssistantResponse& response) override;
+
+  // AssistantUiModelObserver:
+  void OnUiVisibilityChanged(bool visible, AssistantSource source) override;
 
  private:
   void InitLayout(AssistantController* assistant_controller);
   void InitContentLayoutContainer(AssistantController* assistant_controller);
   void InitQueryLayoutContainer(AssistantController* assistant_controller);
 
-  void UpdateCommittedQueryViewSpacer();
+  void UpdateActiveQueryViewSpacer();
+  void UpdateQueryViewTransform(views::View* query_view);
   void UpdateSuggestionContainer();
 
-  bool OnCommittedQueryExitAnimationEnded(
+  void OnActivateQuery();
+  void OnActiveQueryCleared();
+  bool OnActiveQueryExitAnimationEnded(
+      const ui::CallbackLayerAnimationObserver& observer);
+
+  void OnSuggestionContainerAnimationStarted(
+      const ui::CallbackLayerAnimationObserver& observer);
+  bool OnSuggestionContainerAnimationEnded(
       const ui::CallbackLayerAnimationObserver& observer);
 
   AssistantController* const assistant_controller_;  // Owned by Shell.
 
-  views::View* committed_query_view_spacer_;       // Owned by view hierarchy.
+  views::View* active_query_view_spacer_;          // Owned by view hierarchy.
   views::View* query_layout_container_;            // Owned by view hierarchy.
   SuggestionContainerView* suggestion_container_;  // Owned by view hierarchy.
   UiElementContainerView* ui_element_container_;   // Owned by view hierarchy.
 
   // Owned by view hierarchy.
+  AssistantQueryView* active_query_view_ = nullptr;
   AssistantQueryView* committed_query_view_ = nullptr;
   AssistantQueryView* pending_query_view_ = nullptr;
 
   std::unique_ptr<ui::CallbackLayerAnimationObserver>
-      committed_query_exit_animation_observer_;
+      active_query_exit_animation_observer_;
+
+  std::unique_ptr<ui::CallbackLayerAnimationObserver>
+      suggestion_container_animation_observer_;
 
   DISALLOW_COPY_AND_ASSIGN(AssistantMainStage);
 };
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 4d86080..3fd1959 100644
--- a/ash/assistant/ui/main_stage/ui_element_container_view.cc
+++ b/ash/assistant/ui/main_stage/ui_element_container_view.cc
@@ -44,6 +44,9 @@
 
 // CardElementViewHolder -------------------------------------------------------
 
+// This class uses a child widget to host a view for a card element that has an
+// aura::Window. The child widget's layer becomes the root of the card's layer
+// hierarchy.
 class CardElementViewHolder : public views::NativeViewHost,
                               public views::ViewObserver {
  public:
diff --git a/ash/assistant/util/animation_util.cc b/ash/assistant/util/animation_util.cc
index ca8a45b..0f1cd39b 100644
--- a/ash/assistant/util/animation_util.cc
+++ b/ash/assistant/util/animation_util.cc
@@ -83,6 +83,15 @@
   return layer_animation_element;
 }
 
+void StartLayerAnimationSequence(
+    ::ui::LayerAnimator* layer_animator,
+    ::ui::LayerAnimationSequence* layer_animation_sequence,
+    ::ui::LayerAnimationObserver* observer) {
+  if (observer)
+    layer_animation_sequence->AddObserver(observer);
+  layer_animator->StartAnimation(layer_animation_sequence);
+}
+
 void StartLayerAnimationSequencesTogether(
     ::ui::LayerAnimator* layer_animator,
     const std::vector<ui::LayerAnimationSequence*>& layer_animation_sequences,
diff --git a/ash/assistant/util/animation_util.h b/ash/assistant/util/animation_util.h
index bed7a4f..4d4bf3a 100644
--- a/ash/assistant/util/animation_util.h
+++ b/ash/assistant/util/animation_util.h
@@ -78,6 +78,14 @@
     const base::TimeDelta& duration,
     const gfx::Tween::Type& tween = gfx::Tween::Type::LINEAR);
 
+// Starts the specified |layer_animation_sequence| on the given
+// |layer_animator|. If an optional |observer| is supplied, it will be added to
+// the sequence.
+void StartLayerAnimationSequence(
+    ::ui::LayerAnimator* layer_animator,
+    ::ui::LayerAnimationSequence* layer_animation_sequence,
+    ::ui::LayerAnimationObserver* observer = nullptr);
+
 // Starts the specified |layer_animation_sequences| together on the given
 // |layer_animator|. If an optional |observer| is supplied, it will be added
 // to each sequence in the animation set.
diff --git a/ash/components/shortcut_viewer/views/keyboard_shortcut_view.cc b/ash/components/shortcut_viewer/views/keyboard_shortcut_view.cc
index 9cbc5c7f..55c5e0f0 100644
--- a/ash/components/shortcut_viewer/views/keyboard_shortcut_view.cc
+++ b/ash/components/shortcut_viewer/views/keyboard_shortcut_view.cc
@@ -31,6 +31,7 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/chromeos/search_box/search_box_view_base.h"
+#include "ui/events/event_constants.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/gfx/presentation_feedback.h"
 #include "ui/views/background.h"
@@ -257,7 +258,7 @@
 
 void KeyboardShortcutView::BackButtonPressed() {
   search_box_view_->ClearSearch();
-  search_box_view_->SetSearchBoxActive(false);
+  search_box_view_->SetSearchBoxActive(false, ui::ET_UNKNOWN);
 }
 
 void KeyboardShortcutView::ActiveChanged(
diff --git a/ash/components/shortcut_viewer/views/ksv_search_box_view.cc b/ash/components/shortcut_viewer/views/ksv_search_box_view.cc
index 7b3a37f..e4c3631 100644
--- a/ash/components/shortcut_viewer/views/ksv_search_box_view.cc
+++ b/ash/components/shortcut_viewer/views/ksv_search_box_view.cc
@@ -65,7 +65,7 @@
   ClearSearch();
   // |VKEY_ESCAPE| will clear text and exit search mode directly.
   if (is_escape_key)
-    SetSearchBoxActive(false);
+    SetSearchBoxActive(false, event->type());
 }
 
 void KSVSearchBoxView::ButtonPressed(views::Button* sender,
@@ -89,7 +89,7 @@
   // TODO(wutao): Rename this function or create another function in base class.
   // It updates many things in addition to the border.
   if (!search_box()->HasFocus() && search_box()->text().empty())
-    SetSearchBoxActive(false);
+    SetSearchBoxActive(false, ui::ET_UNKNOWN);
 
   constexpr int kBorderThichness = 2;
   constexpr SkColor kActiveBorderColor = SkColorSetARGB(0x7F, 0x1A, 0x73, 0xE8);
diff --git a/ash/login/ui/lock_contents_view.cc b/ash/login/ui/lock_contents_view.cc
index 2fc8ba1..de8f6515 100644
--- a/ash/login/ui/lock_contents_view.cc
+++ b/ash/login/ui/lock_contents_view.cc
@@ -499,7 +499,18 @@
   opt_secondary_big_view_ = nullptr;
   users_list_ = nullptr;
   rotation_actions_.clear();
-  users_.clear();
+
+  // Build user state list. Preserve previous state if the user already exists.
+  std::vector<UserState> new_users;
+  for (const mojom::LoginUserInfoPtr& user : users) {
+    UserState* old_state = FindStateForUser(user->basic_user_info->account_id);
+    if (old_state)
+      new_users.push_back(std::move(*old_state));
+    else
+      new_users.push_back(UserState(user));
+  }
+
+  users_ = std::move(new_users);
 
   // If there are no users, show gaia signin if login, otherwise crash.
   if (users.empty()) {
@@ -510,10 +521,6 @@
     return;
   }
 
-  // Build user state list.
-  for (const mojom::LoginUserInfoPtr& user : users)
-    users_.push_back(UserState(user));
-
   auto box_layout =
       std::make_unique<views::BoxLayout>(views::BoxLayout::kHorizontal);
   main_layout_ = box_layout.get();
diff --git a/ash/login/ui/lock_contents_view_unittest.cc b/ash/login/ui/lock_contents_view_unittest.cc
index f7a9ed20..ec62e851 100644
--- a/ash/login/ui/lock_contents_view_unittest.cc
+++ b/ash/login/ui/lock_contents_view_unittest.cc
@@ -1740,4 +1740,28 @@
   base::RunLoop().RunUntilIdle();
 }
 
+TEST_F(LockContentsViewUnitTest, UsersChangedRetainsExistingState) {
+  auto* contents = new LockContentsView(
+      mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock,
+      data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
+  SetUserCount(2);
+  SetWidget(CreateWidgetWithContent(contents));
+
+  LockContentsView::TestApi test_api(contents);
+
+  AccountId primary_user = test_api.primary_big_view()
+                               ->GetCurrentUser()
+                               ->basic_user_info->account_id;
+  data_dispatcher()->SetPinEnabledForUser(primary_user, true);
+
+  // This user should be identical to the user we enabled PIN for.
+  SetUserCount(1);
+
+  EXPECT_TRUE(
+      LoginAuthUserView::TestApi(test_api.primary_big_view()->auth_user())
+          .pin_view()
+          ->visible());
+}
+
 }  // namespace ash
diff --git a/ash/wm/overview/scoped_transform_overview_window.cc b/ash/wm/overview/scoped_transform_overview_window.cc
index ef55db7..04902e3 100644
--- a/ash/wm/overview/scoped_transform_overview_window.cc
+++ b/ash/wm/overview/scoped_transform_overview_window.cc
@@ -314,22 +314,6 @@
   return bounds;
 }
 
-SkColor ScopedTransformOverviewWindow::GetTopColor() const {
-  for (auto* window : wm::GetTransientTreeIterator(window_)) {
-    // If there are regular windows in the transient ancestor tree, all those
-    // windows are shown in the same overview item and the header is not masked.
-    if (window != window_ &&
-        (window->type() == aura::client::WINDOW_TYPE_NORMAL ||
-         window->type() == aura::client::WINDOW_TYPE_PANEL)) {
-      return SK_ColorTRANSPARENT;
-    }
-  }
-
-  return window_->GetProperty(wm::GetWindowState(window_)->IsActive()
-                                  ? kFrameActiveColorKey
-                                  : kFrameInactiveColorKey);
-}
-
 int ScopedTransformOverviewWindow::GetTopInset() const {
   // Mirror window doesn't have insets.
   if (minimized_widget_)
diff --git a/ash/wm/overview/scoped_transform_overview_window.h b/ash/wm/overview/scoped_transform_overview_window.h
index 8b7e0a22f..2b3d70ad 100644
--- a/ash/wm/overview/scoped_transform_overview_window.h
+++ b/ash/wm/overview/scoped_transform_overview_window.h
@@ -13,7 +13,6 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
-#include "third_party/skia/include/core/SkColor.h"
 #include "ui/compositor/layer_animation_observer.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/size.h"
@@ -110,11 +109,6 @@
   // the original |window_|'s header to be hidden.
   gfx::Rect GetTransformedBounds() const;
 
-  // Returns the kFrameActiveColorKey or kFrameInactiveColorKey property of
-  // |window_| unless there are transient ancestors, in which case returns
-  // SK_ColorTRANSPARENT.
-  SkColor GetTopColor() const;
-
   // Returns the kTopViewInset property of |window_| unless there are transient
   // ancestors, in which case returns 0.
   int GetTopInset() const;
diff --git a/ash/wm/overview/window_grid.cc b/ash/wm/overview/window_grid.cc
index c77b50d..3bd630f 100644
--- a/ash/wm/overview/window_grid.cc
+++ b/ash/wm/overview/window_grid.cc
@@ -517,15 +517,6 @@
   }
 }
 
-void WindowGrid::WindowClosing(WindowSelectorItem* window) {
-  if (!selection_widget_ || SelectedWindow() != window)
-    return;
-  aura::Window* selection_widget_window = selection_widget_->GetNativeWindow();
-  ScopedOverviewAnimationSettings animation_settings_label(
-      OVERVIEW_ANIMATION_CLOSING_SELECTOR_ITEM, selection_widget_window);
-  selection_widget_->SetOpacity(0.f);
-}
-
 void WindowGrid::SetBoundsAndUpdatePositions(const gfx::Rect& bounds) {
   SetBoundsAndUpdatePositionsIgnoringWindow(bounds, nullptr);
 }
diff --git a/ash/wm/overview/window_grid.h b/ash/wm/overview/window_grid.h
index d5fa321..1e38c58 100644
--- a/ash/wm/overview/window_grid.h
+++ b/ash/wm/overview/window_grid.h
@@ -105,11 +105,6 @@
   // If |pattern| is empty, no item is dimmed.
   void FilterItems(const base::string16& pattern);
 
-  // Called when |window| is about to get closed. If the |window| is currently
-  // selected the implementation fades out |selection_widget_| to transparent
-  // opacity, effectively hiding the selector widget.
-  void WindowClosing(WindowSelectorItem* window);
-
   // Sets bounds for the window grid and positions all windows in the grid.
   void SetBoundsAndUpdatePositions(const gfx::Rect& bounds_in_screen);
   void SetBoundsAndUpdatePositionsIgnoringWindow(
diff --git a/ash/wm/overview/window_selector.cc b/ash/wm/overview/window_selector.cc
index b098f529..a0b7533 100644
--- a/ash/wm/overview/window_selector.cc
+++ b/ash/wm/overview/window_selector.cc
@@ -486,10 +486,6 @@
   wm::GetWindowState(window)->Activate();
 }
 
-void WindowSelector::WindowClosing(WindowSelectorItem* window) {
-  grid_list_[selected_grid_index_]->WindowClosing(window);
-}
-
 void WindowSelector::SetBoundsForWindowGridsInScreenIgnoringWindow(
     const gfx::Rect& bounds,
     WindowSelectorItem* ignored_item) {
diff --git a/ash/wm/overview/window_selector.h b/ash/wm/overview/window_selector.h
index 70263d5..2f126aa2 100644
--- a/ash/wm/overview/window_selector.h
+++ b/ash/wm/overview/window_selector.h
@@ -90,9 +90,6 @@
   // Activates |item's| window.
   void SelectWindow(WindowSelectorItem* item);
 
-  // Called when |window| is about to get closed.
-  void WindowClosing(WindowSelectorItem* window);
-
   // Called to set bounds for window grids. Used for split view.
   void SetBoundsForWindowGridsInScreenIgnoringWindow(
       const gfx::Rect& bounds,
diff --git a/ash/wm/overview/window_selector_item.cc b/ash/wm/overview/window_selector_item.cc
index 5e15fd43..5d9a4e9 100644
--- a/ash/wm/overview/window_selector_item.cc
+++ b/ash/wm/overview/window_selector_item.cc
@@ -70,9 +70,6 @@
 // Foreground label color.
 constexpr SkColor kLabelColor = SK_ColorWHITE;
 
-// Corner radius for the selection tiles.
-static int kLabelBackgroundRadius = 2;
-
 // Horizontal padding for the label, on both sides.
 constexpr int kHorizontalLabelPaddingDp = 12;
 
@@ -88,16 +85,6 @@
 // Opacity for the item header.
 constexpr float kHeaderOpacity = 0.1f;
 
-// Duration it takes for the header to shift from opaque header color to
-// |kLabelBackgroundColor|.
-constexpr int kSelectorColorSlideMilliseconds = 240;
-
-// Duration of background opacity transition for the selected label.
-constexpr int kSelectorFadeInMilliseconds = 350;
-
-// Duration of background opacity transition when exiting overview mode.
-constexpr int kExitFadeInMilliseconds = 30;
-
 // Duration of the header and close button fade in/out when a drag is
 // started/finished on a window selector item;
 constexpr int kDragAnimationMs = 167;
@@ -112,10 +99,6 @@
 
 constexpr int kCloseButtonInkDropInsetDp = 2;
 
-// Shift the close button by |kCloseButtonOffsetDp| so that its image aligns
-// with the overview window, but its hit bounds exceeds.
-constexpr int kCloseButtonOffsetDp = 8;
-
 // Close button color.
 constexpr SkColor kCloseButtonColor = SK_ColorWHITE;
 
@@ -326,198 +309,51 @@
          kCloseButtonInkDropInsetDp;
 }
 
-// A View having rounded top corners and a specified background color which is
-// only painted within the bounds defined by the rounded corners.
-// This class coordinates the transitions of the overview mode header when
-// entering the overview mode. Those animations are:
-// - Opacity animation. The header is initially same color as the original
-//   window's header. It starts as transparent and is faded in. When the full
-//   opacity is reached the original header is hidden (which is nearly
-//   imperceptable because this view obscures the original header) and a color
-//   animation starts.
-// - Color animation is used to change the color from the opaque color of the
-//   original window's header to semi-transparent color of the overview mode
-//   header (on entry to overview). It is also used on exit from overview to
-//   quickly change the color to a close opaque color in parallel with an
-//   opacity transition to mask the original header reappearing.
-class WindowSelectorItem::RoundedContainerView
-    : public views::View,
-      public gfx::AnimationDelegate,
-      public ui::ImplicitAnimationObserver {
- public:
-  RoundedContainerView(WindowSelectorItem* item,
-                       int corner_radius,
-                       SkColor background)
-      : item_(item),
-        corner_radius_(corner_radius),
-        initial_color_(background),
-        target_color_(background),
-        current_value_(0),
-        animation_(new gfx::SlideAnimation(this)) {
-    SetPaintToLayer();
-    layer()->SetFillsBoundsOpaquely(false);
-
-    // Do not animate in the title bar of windows which are maximized in
-    // tablet mode, as their title bars are already hidden.
-    should_animate_ = !UseTabletModeAnimations(item_->GetWindow());
-  }
-
-  ~RoundedContainerView() override { StopObservingImplicitAnimations(); }
-
-  void OnItemRestored() {
-    item_ = nullptr;
-  }
-
-  // Used by tests to set animation state.
-  gfx::SlideAnimation* animation() { return animation_.get(); }
-
-  void set_color(SkColor target_color) {
-    target_color_ = target_color;
-    if (should_animate_)
-      SchedulePaint();
-  }
-
-  bool should_animate() const { return should_animate_; }
-
-  // Starts a color animation using |tween_type|. The animation will change the
-  // color from |initial_color_| to |target_color_| over |duration| specified
-  // in milliseconds.
-  // This animation can start once the implicit layer fade-in opacity animation
-  // is completed. It is used to transition color from the opaque original
-  // window header color to |kLabelBackgroundColor| on entry into overview mode
-  // and from |kLabelBackgroundColor| back to the original window header color
-  // on exit from the overview mode.
-  void AnimateColor(gfx::Tween::Type tween_type, int duration) {
-    if (!should_animate_)
-      return;
-
-    animation_->SetSlideDuration(duration);
-    animation_->SetTweenType(tween_type);
-    animation_->Reset(0);
-    animation_->Show();
-
-    // Tests complete animations immediately. Emulate by invoking the callback.
-    if (ui::ScopedAnimationDurationScaleMode::duration_scale_mode() ==
-        ui::ScopedAnimationDurationScaleMode::ZERO_DURATION) {
-      AnimationEnded(animation_.get());
-    }
-  }
-
-  // Changes the view opacity by animating its background color. The animation
-  // will change the alpha value in |target_color_| from its current value to
-  // |opacity| * 255 but preserve the RGB values.
-  void AnimateBackgroundOpacity(float opacity) {
-    animation_->SetSlideDuration(kSelectorFadeInMilliseconds);
-    animation_->SetTweenType(gfx::Tween::EASE_OUT);
-    animation_->Reset(0);
-    animation_->Show();
-    target_color_ = SkColorSetA(target_color_, opacity * 255);
-  }
-
-  // views::View:
-  void OnPaint(gfx::Canvas* canvas) override {
-    views::View::OnPaint(canvas);
-    SkScalar radius = SkIntToScalar(corner_radius_);
-    const SkScalar kRadius[8] = {radius, radius, radius, radius, 0, 0, 0, 0};
-    SkPath path;
-    gfx::Rect bounds(size());
-    path.addRoundRect(gfx::RectToSkRect(bounds), kRadius);
-
-    cc::PaintFlags flags;
-    flags.setAntiAlias(true);
-    canvas->ClipPath(path, true);
-
-    SkColor target_color = should_animate_ ? initial_color_ : target_color_;
-    if (should_animate_ && (target_color_ != target_color)) {
-      target_color = color_utils::AlphaBlend(target_color_, initial_color_,
-                                             current_value_);
-    }
-    canvas->DrawColor(target_color);
-  }
-
-  const char* GetClassName() const override { return "RoundedContainerView"; }
-
- private:
-  // gfx::AnimationDelegate:
-  void AnimationEnded(const gfx::Animation* animation) override {
-    initial_color_ = target_color_;
-    // Tabbed browser windows show the overview mode header behind the window
-    // during the initial animation. Once the initial fade-in completes and the
-    // overview header is fully exposed update stacking to keep the label above
-    // the item which prevents input events from reaching the window.
-    if (item_)
-      item_->RestackItemWidget();
-  }
-
-  void AnimationProgressed(const gfx::Animation* animation) override {
-    current_value_ = animation_->CurrentValueBetween(0, 255);
-    SchedulePaint();
-  }
-
-  void AnimationCanceled(const gfx::Animation* animation) override {
-    initial_color_ = target_color_;
-    current_value_ = 255;
-    SchedulePaint();
-  }
-
-  // ui::ImplicitAnimationObserver:
-  void OnImplicitAnimationsCompleted() override {
-    // Return if the fade in animation of |item_->item_widget_| was aborted.
-    if (WasAnimationAbortedForProperty(
-            ui::LayerAnimationElement::AnimatableProperty::OPACITY)) {
-      return;
-    }
-    DCHECK(WasAnimationCompletedForProperty(
-        ui::LayerAnimationElement::AnimatableProperty::OPACITY));
-
-    // Otherwise, animate the color of this view.
-    AnimateColor(gfx::Tween::EASE_IN, kSelectorColorSlideMilliseconds);
-  }
-
-  WindowSelectorItem* item_;
-  int corner_radius_;
-  SkColor initial_color_;
-  SkColor target_color_;
-  int current_value_;
-  std::unique_ptr<gfx::SlideAnimation> animation_;
-  bool should_animate_ = true;
-
-  DISALLOW_COPY_AND_ASSIGN(RoundedContainerView);
-};
-
 // A Container View that has a ShieldButton to listen to events. The
 // ShieldButton covers most of the View except for the transparent gap between
-// the windows and is visually transparent. The ShieldButton owns a background
-// non-transparent view positioned at the ShieldButton top. The background view
-// in its turn owns an item text label and a close button.
-// The text label does not receive events, however the close button is higher in
-// Z-order than its parent and receives events forwarding them to the same
-// |listener| (i.e. WindowSelectorItem::ButtonPressed()).
+// the windows and is visually transparent. The text label does not receive
+// events, however the close button is higher in Z-order than its parent and
+// receives events forwarding them to the same |listener| (i.e.
+// WindowSelectorItem::ButtonPressed()).
 class WindowSelectorItem::CaptionContainerView : public views::View {
  public:
+  enum class HeaderVisibility {
+    kInvisible,
+    kCloseButtonInvisibleOnly,
+    kVisible,
+  };
+
   CaptionContainerView(ButtonListener* listener,
                        views::ImageView* image_view,
                        views::Label* title_label,
                        views::Label* cannot_snap_label,
-                       views::ImageButton* close_button,
-                       WindowSelectorItem::RoundedContainerView* background)
+                       views::ImageButton* close_button)
       : listener_button_(new ShieldButton(listener, title_label->text())),
-        background_(background),
         image_view_(image_view),
         title_label_(title_label),
         cannot_snap_label_(cannot_snap_label),
         close_button_(close_button) {
-    if (image_view_)
-      background_->AddChildView(image_view_);
-    background_->AddChildView(title_label_);
-    listener_button_->AddChildView(background_);
     AddChildView(listener_button_);
-    // Do not make |close_button_| a child of |background_| because
-    // |close_button_|'s hit radius should extend outside the bounds of
-    // |background_|.
-    close_button_->SetPaintToLayer();
-    close_button_->layer()->SetFillsBoundsOpaquely(false);
-    AddChildView(close_button_);
+
+    // Helper function to add a child view to a parent view and make it paint to
+    // layer.
+    auto add_child_with_layer = [](views::View* parent, views::View* child) {
+      child->SetPaintToLayer();
+      child->layer()->SetFillsBoundsOpaquely(false);
+      parent->AddChildView(child);
+    };
+
+    header_view_ = new views::View();
+    views::BoxLayout* layout =
+        header_view_->SetLayoutManager(std::make_unique<views::BoxLayout>(
+            views::BoxLayout::kHorizontal, gfx::Insets(),
+            kHorizontalLabelPaddingDp));
+    if (image_view_)
+      add_child_with_layer(header_view_, image_view_);
+    add_child_with_layer(header_view_, title_label_);
+    add_child_with_layer(header_view_, close_button_);
+    add_child_with_layer(this, header_view_);
+    layout->SetFlexForView(title_label_, 1);
 
     // Use |cannot_snap_container_| to specify the padding surrounding
     // |cannot_snap_label_| and to give the label rounded corners.
@@ -529,24 +365,24 @@
                     kSplitviewLabelHorizontalInsetDp)));
     cannot_snap_container_->AddChildView(cannot_snap_label_);
     cannot_snap_container_->set_can_process_events_within_subtree(false);
-    cannot_snap_container_->SetPaintToLayer();
-    cannot_snap_container_->layer()->SetFillsBoundsOpaquely(false);
+    add_child_with_layer(this, cannot_snap_container_);
     cannot_snap_container_->layer()->SetOpacity(0.f);
-    AddChildView(cannot_snap_container_);
   }
 
   ShieldButton* listener_button() { return listener_button_; }
 
   gfx::Rect backdrop_bounds() const { return backdrop_bounds_; }
 
-  void SetCloseButtonVisibility(bool visible) {
-    DCHECK(close_button_->layer());
-    AnimateLayerOpacity(close_button_->layer(), visible);
-  }
+  void SetHeaderVisibility(HeaderVisibility visibility) {
+    DCHECK(header_view_->layer());
 
-  void SetTitleLabelVisibility(bool visible) {
-    DCHECK(background_->layer());
-    AnimateLayerOpacity(background_->layer(), visible);
+    // Set the close button invisible if the rest of the header is to be shown.
+    // If the rest of the header is to be hidden, set the close visiblilty to
+    // true as |header_view_|'s opacity will be 0.f, hiding the close button.
+    close_button_->SetVisible(visibility !=
+                              HeaderVisibility::kCloseButtonInvisibleOnly);
+    const bool visible = visibility != HeaderVisibility::kInvisible;
+    AnimateLayerOpacity(header_view_->layer(), visible);
   }
 
   void SetCannotSnapLabelVisibility(bool visible) {
@@ -556,17 +392,14 @@
                 : SPLITVIEW_ANIMATION_SELECTOR_ITEM_FADE_OUT);
   }
 
+  views::View* header_view() { return header_view_; }
+
  protected:
   // views::View:
   void Layout() override {
-    // Position close button in the top right corner sized to its icon size and
-    // the label in the top left corner as tall as the button and extending to
-    // the button's left edge. Position the cannot snap label in the center of
-    // the window selector item.
-    // The rest of this container view serves as a shield to prevent input
-    // events from reaching the transformed window in overview.
+    // |listener_button_| serves as a shield to prevent input events from
+    // reaching the transformed window in overview.
     gfx::Rect bounds(GetLocalBounds());
-
     bounds.Inset(kWindowSelectorMargin, kWindowSelectorMargin);
     listener_button_->SetBoundsRect(bounds);
 
@@ -582,37 +415,20 @@
     backdrop_bounds_ = bounds;
     backdrop_bounds_.Inset(0, visible_height, 0, 0);
 
-    // Center the cannot snap label in the middle of the item, minus the title.
+    // Position the cannot snap label in the middle of the item, minus the
+    // title.
     gfx::Rect cannot_snap_bounds = GetLocalBounds();
     cannot_snap_bounds.Inset(0, visible_height, 0, 0);
     cannot_snap_bounds.ClampToCenteredSize(label_size);
     cannot_snap_container_->SetBoundsRect(cannot_snap_bounds);
 
-    gfx::Rect background_bounds(gfx::Rect(bounds.size()));
-    background_bounds.set_height(visible_height);
-    background_->SetBoundsRect(background_bounds);
-
-    bounds = background_bounds;
-    bounds.Inset(kHorizontalLabelPaddingDp +
-                     (image_view_ ? image_view_->size().width() : 0),
-                 0, kHorizontalLabelPaddingDp + visible_height, 0);
-    title_label_->SetBoundsRect(bounds);
-
-    if (image_view_) {
-      bounds.set_x(0);
-      bounds.set_width(image_view_->size().width());
-      image_view_->SetBoundsRect(bounds);
-    }
-
-    bounds = background_bounds;
-    // Align the close button so that the right edge of its image is aligned
-    // with the right edge of the overview window, but the right edge of its
-    // touch target exceeds it.
-    bounds.set_x(bounds.width() - visible_height + kCloseButtonOffsetDp +
-                 kWindowSelectorMargin);
-    bounds.set_y(kWindowSelectorMargin);
-    bounds.set_width(visible_height);
-    close_button_->SetBoundsRect(bounds);
+    // Position the header at the top. The left should be indented to match
+    // the transformed window, but not the right because the close button hit
+    // radius should extend past the transformed window's rightmost bounds.
+    gfx::Rect header_bounds = GetLocalBounds();
+    header_bounds.Inset(kWindowSelectorMargin, kWindowSelectorMargin, 0, 0);
+    header_bounds.set_height(visible_height);
+    header_view_->SetBoundsRect(header_bounds);
   }
 
   const char* GetClassName() const override { return "CaptionContainerView"; }
@@ -647,12 +463,13 @@
   }
 
   ShieldButton* listener_button_;
-  WindowSelectorItem::RoundedContainerView* background_;
   views::ImageView* image_view_;
   views::Label* title_label_;
   views::Label* cannot_snap_label_;
   RoundedRectView* cannot_snap_container_;
   views::ImageButton* close_button_;
+  // View which contains the icon, title and close button.
+  views::View* header_view_ = nullptr;
   gfx::Rect backdrop_bounds_;
 
   DISALLOW_COPY_AND_ASSIGN(CaptionContainerView);
@@ -702,10 +519,6 @@
   caption_container_view_->listener_button()->ResetListener();
   close_button_->ResetListener();
   transform_window_.RestoreWindow(reset_transform);
-  if (background_view_) {
-    background_view_->OnItemRestored();
-    background_view_ = nullptr;
-  }
   UpdateHeaderLayout(HeaderFadeInMode::kExit, GetExitOverviewAnimationType());
 }
 
@@ -730,10 +543,6 @@
                                                transform_window_.window());
     }
   }
-  if (background_view_) {
-    background_view_->OnItemRestored();
-    background_view_ = nullptr;
-  }
   // Fade out the item widget. This animation continues past the lifetime
   // of |this|.
   FadeOutWidgetOnExit(std::move(item_widget_),
@@ -743,13 +552,6 @@
 void WindowSelectorItem::PrepareForOverview() {
   transform_window_.PrepareForOverview();
   RestackItemWidget();
-  // The minimized widget was unavailable up to this point, but if a window
-  // is minimized, |item_widget_| should stack on top of it, unless the
-  // animation is in progress and will take care of that.
-  if (!background_view_->should_animate() &&
-      wm::GetWindowState(GetWindow())->IsMinimized()) {
-    RestackItemWidget();
-  }
   UpdateHeaderLayout(HeaderFadeInMode::kEnter,
                      OverviewAnimationType::OVERVIEW_ANIMATION_NONE);
 }
@@ -883,14 +685,15 @@
 }
 
 void WindowSelectorItem::OnSelectorItemDragStarted(WindowSelectorItem* item) {
-  caption_container_view_->SetCloseButtonVisibility(false);
-  if (item == this)
-    caption_container_view_->SetTitleLabelVisibility(false);
+  caption_container_view_->SetHeaderVisibility(
+      item == this
+          ? CaptionContainerView::HeaderVisibility::kInvisible
+          : CaptionContainerView::HeaderVisibility::kCloseButtonInvisibleOnly);
 }
 
 void WindowSelectorItem::OnSelectorItemDragEnded() {
-  caption_container_view_->SetCloseButtonVisibility(true);
-  caption_container_view_->SetTitleLabelVisibility(true);
+  caption_container_view_->SetHeaderVisibility(
+      CaptionContainerView::HeaderVisibility::kVisible);
 }
 
 ScopedTransformOverviewWindow::GridWindowFillMode
@@ -1146,12 +949,12 @@
              : OverviewAnimationType::OVERVIEW_ANIMATION_RESTORE_WINDOW_ZERO;
 }
 
-float WindowSelectorItem::GetCloseButtonOpacityForTesting() {
-  return close_button_->layer()->opacity();
+bool WindowSelectorItem::GetCloseButtonVisibilityForTesting() const {
+  return close_button_->visible();
 }
 
-float WindowSelectorItem::GetTitlebarOpacityForTesting() {
-  return background_view_->layer()->opacity();
+float WindowSelectorItem::GetTitlebarOpacityForTesting() const {
+  return caption_container_view_->header_view()->layer()->opacity();
 }
 
 gfx::Rect WindowSelectorItem::GetShadowBoundsForTesting() {
@@ -1184,11 +987,6 @@
 }
 
 void WindowSelectorItem::CreateWindowLabel(const base::string16& title) {
-  background_view_ = new RoundedContainerView(this, kLabelBackgroundRadius,
-                                              transform_window_.GetTopColor());
-  background_view_->set_color(SK_ColorTRANSPARENT);
-
-  // |background_view_| will get added as a child to CaptionContainerView.
   views::Widget::InitParams params_label;
   params_label.type = views::Widget::InitParams::TYPE_POPUP;
   params_label.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
@@ -1204,19 +1002,9 @@
   item_widget_->set_focus_on_creation(false);
   item_widget_->Init(params_label);
   aura::Window* widget_window = item_widget_->GetNativeWindow();
-  if (transform_window_.GetTopInset() || !background_view_->should_animate()) {
-    // For windows with headers the overview header fades in above the
-    // original window header. Windows that should not animate their headers
-    // (maximized tablet mode windows have no headers) should initially be
-    // stacked above as well.
-    widget_window->parent()->StackChildAbove(widget_window,
-                                             transform_window_.window());
-  } else {
-    // For tabbed windows the overview header slides from behind. The stacking
-    // is then corrected when the animation completes.
-    widget_window->parent()->StackChildBelow(widget_window,
-                                             transform_window_.window());
-  }
+  // Stack the widget above the transform window so that it can block events.
+  widget_window->parent()->StackChildAbove(widget_window,
+                                           transform_window_.window());
 
   // Create an image view the header icon. Tries to use the app icon, as it is
   // higher resolution. If it does not exist, use the window icon. If neither
@@ -1252,8 +1040,7 @@
   cannot_snap_label_view_->SetBackgroundColor(kSplitviewLabelBackgroundColor);
 
   caption_container_view_ = new CaptionContainerView(
-      this, image_view, label_view_, cannot_snap_label_view_, close_button_,
-      background_view_);
+      this, image_view, label_view_, cannot_snap_label_view_, close_button_);
   UpdateCannotSnapWarningVisibility();
 
   item_widget_->SetContentsView(caption_container_view_);
@@ -1288,28 +1075,9 @@
           ? -label_rect.height()
           : 0);
 
-  {
-    ui::ScopedLayerAnimationSettings layer_animation_settings(
-        item_widget_->GetLayer()->GetAnimator());
-    if (background_view_) {
-      if (mode == HeaderFadeInMode::kEnter) {
-        // Animate the color of |background_view_| once the fade in animation of
-        // |item_widget_| ends.
-        layer_animation_settings.AddObserver(background_view_);
-      } else if (mode == HeaderFadeInMode::kExit) {
-        // Make the header visible above the window. It will be faded out when
-        // the Shutdown() is called.
-        background_view_->AnimateColor(
-            gfx::Tween::EASE_OUT,
-            animation_type == OverviewAnimationType::OVERVIEW_ANIMATION_NONE
-                ? 0
-                : kExitFadeInMilliseconds);
-      }
-    }
-    if (!label_view_->visible()) {
-      label_view_->SetVisible(true);
-      SetupFadeInAfterLayout(item_widget_.get(), GetWindow());
-    }
+  if (!label_view_->visible()) {
+    label_view_->SetVisible(true);
+    SetupFadeInAfterLayout(item_widget_.get(), GetWindow());
   }
 
   aura::Window* widget_window = item_widget_->GetNativeWindow();
@@ -1354,10 +1122,6 @@
       GetWindow()->GetTitle());
 }
 
-gfx::SlideAnimation* WindowSelectorItem::GetBackgroundViewAnimation() {
-  return background_view_ ? background_view_->animation() : nullptr;
-}
-
 aura::Window* WindowSelectorItem::GetOverviewWindowForMinimizedStateForTest() {
   return transform_window_.GetOverviewWindowForMinimizedState();
 }
diff --git a/ash/wm/overview/window_selector_item.h b/ash/wm/overview/window_selector_item.h
index 9c2d1a0..0bac9cc 100644
--- a/ash/wm/overview/window_selector_item.h
+++ b/ash/wm/overview/window_selector_item.h
@@ -238,8 +238,8 @@
   bool animating_to_close() const { return animating_to_close_; }
   void set_animating_to_close(bool val) { animating_to_close_ = val; }
 
-  float GetCloseButtonOpacityForTesting();
-  float GetTitlebarOpacityForTesting();
+  bool GetCloseButtonVisibilityForTesting() const;
+  float GetTitlebarOpacityForTesting() const;
   gfx::Rect GetShadowBoundsForTesting();
 
  private:
@@ -335,30 +335,27 @@
   // Container view that owns a Button view covering the |transform_window_|.
   // That button serves as an event shield to receive all events such as clicks
   // targeting the |transform_window_| or the overview header above the window.
-  // The shield button owns |background_view_| which owns |label_view_|
+  // The shield button owns a header view which owns |label_view_|
   // and |close_button_|.
   CaptionContainerView* caption_container_view_ = nullptr;
 
-  // A View for the text label above the window owned by the |background_view_|.
+  // A View for the text label above the window owned by the a header view in
+  // |caption_container_view_|.
   views::Label* label_view_ = nullptr;
 
   // A View for the text label in the center of the window warning users that
   // this window cannot be snapped for splitview. Owned by a container in
-  // |background_view_|.
+  // |caption_container_view_|.
   views::Label* cannot_snap_label_view_ = nullptr;
 
-  // A close button for the window in this item owned by the |background_view_|.
+  // A close button for the window in this item owned by a header view in
+  // |caption_container_view_|.
   OverviewCloseButton* close_button_ = nullptr;
 
   // Pointer to the WindowSelector that owns the WindowGrid containing |this|.
   // Guaranteed to be non-null for the lifetime of |this|.
   WindowSelector* window_selector_;
 
-  // Pointer to a view that covers the original header and has rounded top
-  // corners. This view can have its color and opacity animated. It has a layer
-  // which is the only textured layer used by the |item_widget_|.
-  RoundedContainerView* background_view_ = nullptr;
-
   // Pointer to the WindowGrid that contains |this|. Guaranteed to be non-null
   // for the lifetime of |this|.
   WindowGrid* window_grid_;
diff --git a/ash/wm/overview/window_selector_unittest.cc b/ash/wm/overview/window_selector_unittest.cc
index e27c6cb..95003dcf 100644
--- a/ash/wm/overview/window_selector_unittest.cc
+++ b/ash/wm/overview/window_selector_unittest.cc
@@ -290,13 +290,6 @@
     return iter->get();
   }
 
-  gfx::SlideAnimation* GetBackgroundViewAnimationForWindow(
-      int grid_index,
-      aura::Window* window) {
-    return GetWindowItemForWindow(grid_index, window)
-        ->GetBackgroundViewAnimation();
-  }
-
   // Selects |window| in the active overview session by cycling through all
   // windows in overview until it is found. Returns true if |window| was found,
   // false otherwise.
@@ -843,7 +836,7 @@
   EXPECT_TRUE(minimized_widget->IsClosed());
 
   // All minimized windows are closed, so it should exit overview mode.
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(IsSelecting());
 }
 
@@ -1046,37 +1039,6 @@
   EXPECT_FALSE(window_state->ignored_by_shelf());
 }
 
-// Tests that it is safe to destroy a window while the overview header animation
-// is still active. See http://crbug.com/646350.
-TEST_F(WindowSelectorTest, SafeToDestroyWindowDuringAnimation) {
-  const gfx::Rect bounds(400, 400);
-  {
-    // Quickly enter and exit overview mode to activate header animations.
-    std::unique_ptr<aura::Window> window(CreateWindow(bounds));
-    ui::ScopedAnimationDurationScaleMode test_duration_mode(
-        ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
-    ToggleOverview();
-    EXPECT_TRUE(IsSelecting());
-
-    gfx::SlideAnimation* animation =
-        GetBackgroundViewAnimationForWindow(0, window.get());
-    ASSERT_TRUE(animation);
-    ToggleOverview();
-    EXPECT_FALSE(IsSelecting());
-    if (animation)
-      EXPECT_TRUE(animation->is_animating());
-
-    // Close the window while the overview header animation is active.
-    window.reset();
-
-    // Progress animation to the end - should not crash.
-    if (animation) {
-      animation->SetCurrentValue(1.0);
-      animation->Reset(1.0);
-    }
-  }
-}
-
 // Tests that a bounds change during overview is corrected for.
 TEST_F(WindowSelectorTest, BoundsChangeDuringOverview) {
   std::unique_ptr<aura::Window> window(CreateWindow(gfx::Rect(0, 0, 400, 400)));
@@ -1330,7 +1292,7 @@
 
   event_generator.MoveMouseBy(10, 10);
   event_generator.ReleaseLeftButton();
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
   EXPECT_EQ(gfx::Rect(30, 30, 100, 100), window->bounds());
 }
 
@@ -1709,7 +1671,7 @@
     tray->ShowDefaultView(BUBBLE_CREATE_NEW, false /* show_by_click */);
   }
 
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
 
   // This should not cancel overview mode.
   ASSERT_TRUE(IsSelecting());
@@ -2049,7 +2011,7 @@
   // Enter overview mode. Verify that the no windows indicator is visible on the
   // primary display but not on the other two.
   ToggleOverview();
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
   ASSERT_TRUE(window_selector());
   EXPECT_TRUE(grids()[0]->IsNoItemsIndicatorLabelVisibleForTesting());
   EXPECT_FALSE(grids()[1]->IsNoItemsIndicatorLabelVisibleForTesting());
@@ -2071,7 +2033,7 @@
   // Enter overview mode. Verify that the no windows indicator is not visible on
   // any display.
   ToggleOverview();
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
   ASSERT_TRUE(window_selector());
   EXPECT_EQ(3u, grids().size());
   EXPECT_FALSE(grids()[0]->IsNoItemsIndicatorLabelVisibleForTesting());
@@ -2088,7 +2050,7 @@
   // Close |item2|. Verify that we are still in overview mode because |window1|
   // is still open. The non primary root grids are empty however.
   item2->CloseWindow();
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(window_selector());
   EXPECT_EQ(3u, grids().size());
   EXPECT_FALSE(grids()[0]->IsNoItemsIndicatorLabelVisibleForTesting());
@@ -2101,7 +2063,7 @@
   // Close |item1|. Verify that since no windows are open, we exit overview
   // mode.
   item1->CloseWindow();
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(window_selector());
 }
 
@@ -2406,7 +2368,7 @@
   EXPECT_FALSE(window6->layer()->GetAnimator()->is_animating());
   EXPECT_FALSE(window7->layer()->GetAnimator()->is_animating());
   EXPECT_FALSE(window8->layer()->GetAnimator()->is_animating());
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
 
   // Click on |window1| to activate it and exit overview.
   // Should animate |window1|, |window2|, |window3| and |window5| because
@@ -2421,7 +2383,7 @@
   EXPECT_FALSE(window6->layer()->GetAnimator()->is_animating());
   EXPECT_FALSE(window7->layer()->GetAnimator()->is_animating());
   EXPECT_FALSE(window8->layer()->GetAnimator()->is_animating());
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
 
   // Case 2: Click on |window3| to activate it and exit overview.
   // Should animate |window1|, |window2|, |window3| and |window5|.
@@ -2443,7 +2405,7 @@
   test_duration_mode = std::make_unique<ui::ScopedAnimationDurationScaleMode>(
       ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
   ToggleOverview();
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
   test_duration_mode = std::make_unique<ui::ScopedAnimationDurationScaleMode>(
       ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
 
@@ -2456,7 +2418,7 @@
   EXPECT_FALSE(window6->layer()->GetAnimator()->is_animating());
   EXPECT_FALSE(window7->layer()->GetAnimator()->is_animating());
   EXPECT_FALSE(window8->layer()->GetAnimator()->is_animating());
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
 
   // Case 3: Click on maximized |window6| to activate it and exit overview.
   // Should animate |window6|, |window3| and |window5| because |window3| and
@@ -2479,7 +2441,7 @@
   test_duration_mode = std::make_unique<ui::ScopedAnimationDurationScaleMode>(
       ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
   ToggleOverview();
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
   test_duration_mode = std::make_unique<ui::ScopedAnimationDurationScaleMode>(
       ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
 
@@ -2492,7 +2454,7 @@
   EXPECT_TRUE(window6->layer()->GetAnimator()->is_animating());
   EXPECT_FALSE(window7->layer()->GetAnimator()->is_animating());
   EXPECT_FALSE(window8->layer()->GetAnimator()->is_animating());
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
 
   // Case 4: Click on |window8| to activate it and exit overview.
   // Should animate |window8|, |window1|, |window2|, |window3| and |window5|
@@ -2516,7 +2478,7 @@
   test_duration_mode = std::make_unique<ui::ScopedAnimationDurationScaleMode>(
       ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
   ToggleOverview();
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
   test_duration_mode = std::make_unique<ui::ScopedAnimationDurationScaleMode>(
       ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
 
@@ -2529,7 +2491,7 @@
   EXPECT_FALSE(window6->layer()->GetAnimator()->is_animating());
   EXPECT_FALSE(window7->layer()->GetAnimator()->is_animating());
   EXPECT_TRUE(window8->layer()->GetAnimator()->is_animating());
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
 }
 
 // Verify that the selector item can animate after the item is dragged and
@@ -2543,7 +2505,7 @@
   wm::ActivateWindow(window1.get());
 
   // The item dragging is only allowed in tablet mode.
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
   Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(true);
 
   ToggleOverview();
@@ -2552,7 +2514,7 @@
   ui::test::EventGenerator* generator = GetEventGenerator();
   generator->MoveMouseTo(item2->target_bounds().CenterPoint());
   generator->PressLeftButton();
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
 
   generator->MoveMouseTo(gfx::Point(200, 200));
   ui::ScopedAnimationDurationScaleMode test_duration_mode(
@@ -2560,7 +2522,7 @@
   generator->ReleaseLeftButton();
   EXPECT_TRUE(window2->layer()->GetAnimator()->IsAnimatingProperty(
       ui::LayerAnimationElement::AnimatableProperty::TRANSFORM));
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
 }
 
 // Verify that the window selector items titlebar and close button change
@@ -2572,7 +2534,7 @@
   std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
 
   // Dragging is only allowed in tablet mode.
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
   Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(true);
 
   ToggleOverview();
@@ -2584,22 +2546,22 @@
   ui::test::EventGenerator* generator = GetEventGenerator();
   generator->MoveMouseTo(item1->target_bounds().CenterPoint());
   generator->PressLeftButton();
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
   EXPECT_EQ(0.f, item1->GetTitlebarOpacityForTesting());
-  EXPECT_EQ(0.f, item1->GetCloseButtonOpacityForTesting());
+  EXPECT_TRUE(item1->GetCloseButtonVisibilityForTesting());
   EXPECT_EQ(1.f, item2->GetTitlebarOpacityForTesting());
-  EXPECT_EQ(0.f, item2->GetCloseButtonOpacityForTesting());
+  EXPECT_FALSE(item2->GetCloseButtonVisibilityForTesting());
 
   // Drag |item1| in a way so that |window1| does not get activated (drags
   // within a certain threshold count as clicks). Verify the close button and
   // titlebar is visible for all items.
   generator->MoveMouseTo(gfx::Point(200, 200));
   generator->ReleaseLeftButton();
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1.f, item1->GetTitlebarOpacityForTesting());
-  EXPECT_EQ(1.f, item1->GetCloseButtonOpacityForTesting());
+  EXPECT_TRUE(item1->GetCloseButtonVisibilityForTesting());
   EXPECT_EQ(1.f, item2->GetTitlebarOpacityForTesting());
-  EXPECT_EQ(1.f, item2->GetCloseButtonOpacityForTesting());
+  EXPECT_TRUE(item2->GetCloseButtonVisibilityForTesting());
 }
 
 // Tests that overview widgets are stacked in the correct order.
@@ -2615,7 +2577,7 @@
   DCHECK_EQ(parent, minimized->parent());
 
   // Dragging is only allowed in tablet mode.
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
   Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(true);
 
   ToggleOverview();
@@ -2694,7 +2656,7 @@
   std::unique_ptr<aura::Window> window3(CreateWindow(bounds));
 
   // Dragging is only allowed in tablet mode.
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
   Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(true);
 
   ToggleOverview();
@@ -2728,7 +2690,7 @@
 
   // Verify that after release the ordering is the same as before dragging.
   generator->ReleaseLeftButton();
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
   EXPECT_GT(IndexOf(widget3->GetNativeWindow(), parent),
             IndexOf(widget2->GetNativeWindow(), parent));
   EXPECT_GT(IndexOf(widget2->GetNativeWindow(), parent),
@@ -2749,7 +2711,7 @@
       CreateWindow(gfx::Rect(10, 10, 200, 200)));
 
   ToggleOverview();
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
   WindowSelectorItem* wide_item = GetWindowItemForWindow(0, wide.get());
   WindowSelectorItem* tall_item = GetWindowItemForWindow(0, tall.get());
   WindowSelectorItem* normal_item = GetWindowItemForWindow(0, normal.get());
@@ -2899,7 +2861,7 @@
   normal->SetProperty(aura::client::kTopViewInset, 0);
 
   ToggleOverview();
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
   WindowSelectorItem* wide_item = GetWindowItemForWindow(0, wide.get());
   WindowSelectorItem* tall_item = GetWindowItemForWindow(0, tall.get());
   WindowSelectorItem* normal_item = GetWindowItemForWindow(0, normal.get());
@@ -2927,7 +2889,7 @@
   // widgets when the overview windows are positioned with animations.
   SetGridBounds(grid, gfx::Rect(200, 400));
   grid->PositionWindows(true);
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(contains(wide_widget, wide_item));
   EXPECT_TRUE(contains(tall_widget, tall_item));
   EXPECT_TRUE(contains(normal_widget, normal_item));
@@ -2947,12 +2909,12 @@
   std::unique_ptr<aura::Window> window2 = CreateTestWindow();
 
   // Dragging is only allowed in tablet mode.
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
   Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(true);
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
 
   ToggleOverview();
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
   WindowSelectorItem* item1 = GetWindowItemForWindow(0, window1.get());
   WindowSelectorItem* item2 = GetWindowItemForWindow(0, window2.get());
 
@@ -3429,7 +3391,7 @@
   window_selector()->InitiateDrag(item, start);
   window_selector()->Drag(item, start + gfx::Vector2d(0, 180));
   window_selector()->CompleteDrag(item, start + gfx::Vector2d(0, 180));
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(window_selector());
 }
 
@@ -3470,7 +3432,7 @@
   window_selector()->InitiateDrag(item, start);
   window_selector()->Drag(item, start + gfx::Vector2d(0, 50));
   window_selector()->Fling(item, start, 0, 2500);
-  RunAllPendingInMessageLoop();
+  base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(window_selector());
 }
 
diff --git a/ash/wm/tablet_mode/tablet_mode_controller.cc b/ash/wm/tablet_mode/tablet_mode_controller.cc
index 53303f9d..f8080b6b 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller.cc
+++ b/ash/wm/tablet_mode/tablet_mode_controller.cc
@@ -283,7 +283,7 @@
   lid_is_closed_ = !open;
 
   if (!tablet_mode_switch_is_on_)
-    LeaveTabletMode(false);
+    AttemptLeaveTabletMode(false);
 }
 
 void TabletModeController::TabletModeEventReceived(
@@ -304,10 +304,10 @@
   // when |on|. However we wish to exit tablet mode at a smaller angle, so
   // when |on| is false we ignore if it is possible to calculate the lid angle.
   if (on && !IsTabletModeWindowManagerEnabled()) {
-    EnterTabletMode();
+    AttemptEnterTabletMode();
   } else if (!on && IsTabletModeWindowManagerEnabled() &&
              !can_detect_lid_angle_) {
-    LeaveTabletMode(false);
+    AttemptLeaveTabletMode(false);
   }
 }
 
@@ -391,13 +391,11 @@
   }
 
   // Toggle tablet mode on or off when corresponding thresholds are passed.
-  if (IsTabletModeWindowManagerEnabled() && is_angle_stable &&
-      lid_angle_ <= kExitTabletModeAngle) {
-    LeaveTabletMode(false);
-  } else if (!IsTabletModeWindowManagerEnabled() && !lid_is_closed_ &&
-             lid_angle_ >= kEnterTabletModeAngle &&
+  if (is_angle_stable && lid_angle_ <= kExitTabletModeAngle) {
+    AttemptLeaveTabletMode(false);
+  } else if (!lid_is_closed_ && lid_angle_ >= kEnterTabletModeAngle &&
              (is_angle_stable || CanUseUnstableLidAngle())) {
-    EnterTabletMode();
+    AttemptEnterTabletMode();
   }
 
   // Start reporting the lid angle if we aren't already doing so.
@@ -409,13 +407,13 @@
   }
 }
 
-void TabletModeController::EnterTabletMode() {
+void TabletModeController::AttemptEnterTabletMode() {
+  event_blocker_.reset();
+  event_blocker_ = CreateScopedDisableInternalMouseAndKeyboard();
+
   if (IsTabletModeWindowManagerEnabled())
     return;
 
-  DCHECK(!event_blocker_);
-  event_blocker_ = CreateScopedDisableInternalMouseAndKeyboard();
-
   should_enter_tablet_mode_ = true;
 
   if (has_external_mouse_)
@@ -424,8 +422,12 @@
   EnableTabletModeWindowManager(true);
 }
 
-void TabletModeController::LeaveTabletMode(bool called_by_device_update) {
-  event_blocker_.reset();
+void TabletModeController::AttemptLeaveTabletMode(
+    bool called_by_device_update) {
+  // Do not unlock internal keyboard if we enter clamshell by plugging in an
+  // external mouse.
+  if (!called_by_device_update)
+    event_blocker_.reset();
 
   if (!IsTabletModeWindowManagerEnabled())
     return;
@@ -451,21 +453,21 @@
 void TabletModeController::OnShellInitialized() {
   force_ui_mode_ = GetTabletMode();
   if (force_ui_mode_ == UiMode::TABLETMODE)
-    EnterTabletMode();
+    AttemptEnterTabletMode();
 }
 
 void TabletModeController::OnDisplayConfigurationChanged() {
   if (!display::Display::HasInternalDisplay() ||
       !Shell::Get()->display_manager()->IsActiveDisplayId(
           display::Display::InternalDisplayId())) {
-    LeaveTabletMode(false);
+    AttemptLeaveTabletMode(false);
   } else if (tablet_mode_switch_is_on_ && !IsTabletModeWindowManagerEnabled()) {
     // The internal display has returned, as we are exiting docked mode.
     // The device is still in tablet mode, so trigger tablet mode, as this
     // switch leads to the ignoring of accelerometer events. When the switch is
     // not set the next stable accelerometer readings will trigger maximize
     // mode.
-    EnterTabletMode();
+    AttemptEnterTabletMode();
   }
 }
 
@@ -529,17 +531,17 @@
 
   has_external_mouse_ = has_external_mouse;
 
-  // Exit tablet mode if we are already in tablet mode and
+  // Try to tablet mode if we are already in tablet mode and
   // |has_external_mouse| is true.
   if (has_external_mouse) {
-    LeaveTabletMode(true);
+    AttemptLeaveTabletMode(true);
     return;
   }
 
-  // Enter tablet mode if |has_external_mouse| is false and we
+  // Try to enter tablet mode if |has_external_mouse| is false and we
   // are in an orientation that should be tablet mode.
   if (should_enter_tablet_mode_)
-    EnterTabletMode();
+    AttemptEnterTabletMode();
 }
 
 void TabletModeController::OnChromeTerminating() {
diff --git a/ash/wm/tablet_mode/tablet_mode_controller.h b/ash/wm/tablet_mode/tablet_mode_controller.h
index d4f721b..49af844 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller.h
+++ b/ash/wm/tablet_mode/tablet_mode_controller.h
@@ -83,7 +83,7 @@
   // tablet mode becomes enabled.
   bool CanEnterTabletMode();
 
-  // TODO(jonross): Merge this with EnterTabletMode. Currently these are
+  // TODO(jonross): Merge this with AttemptEnterTabletMode. Currently these are
   // separate for several reasons: there is no internal display when running
   // unittests; the event blocker prevents keyboard input when running ChromeOS
   // on linux. http://crbug.com/362881
@@ -174,13 +174,12 @@
   // a certain range of time before using unstable angle.
   bool CanUseUnstableLidAngle() const;
 
-  // Enables TabletModeWindowManager, and determines the current state of
-  // rotation lock.
-  void EnterTabletMode();
+  // Attempts to enter tablet mode and locks the internal keyboard and touchpad.
+  void AttemptEnterTabletMode();
 
-  // Removes TabletModeWindowManager and resets the display rotation if there
-  // is no rotation lock.
-  void LeaveTabletMode(bool called_by_device_update);
+  // Attempts to exit tablet mode and unlocks the internal keyboard and touchpad
+  // if |called_by_device_update| is false.
+  void AttemptLeaveTabletMode(bool called_by_device_update);
 
   // Record UMA stats tracking TabletMode usage. If |type| is
   // TABLET_MODE_INTERVAL_INACTIVE, then record that TabletMode has been
diff --git a/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc b/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
index ae0e187..d788e84 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
+++ b/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
@@ -792,17 +792,21 @@
   // Start in tablet mode.
   OpenLidToAngle(300.0f);
   EXPECT_TRUE(IsTabletModeStarted());
+  EXPECT_TRUE(AreEventsBlocked());
 
-  // Attach external mouse and keyboard. Verify that tablet mode has ended.
+  // Attach external mouse and keyboard. Verify that tablet mode has ended, but
+  // events are still blocked because the keyboard is still facing the bottom.
   ui::InputDeviceClientTestApi().SetMouseDevices({ui::InputDevice(
       3, ui::InputDeviceType::INPUT_DEVICE_EXTERNAL, "mouse")});
   base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(IsTabletModeStarted());
+  EXPECT_TRUE(AreEventsBlocked());
 
   // Verify that after unplugging the mouse, tablet mode will resume.
   ui::InputDeviceClientTestApi().SetMouseDevices({});
   base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(IsTabletModeStarted());
+  EXPECT_TRUE(AreEventsBlocked());
 }
 
 }  // namespace ash
diff --git a/base/android/jni_generator/jni_generator.py b/base/android/jni_generator/jni_generator.py
index ee6a52bb..8615649 100755
--- a/base/android/jni_generator/jni_generator.py
+++ b/base/android/jni_generator/jni_generator.py
@@ -1347,6 +1347,7 @@
 See SampleForTests.java for more details.
   """
   option_parser = optparse.OptionParser(usage=usage)
+  build_utils.AddDepfileOption(option_parser)
 
   option_parser.add_option('-j', '--jar_file', dest='jar_file',
                            help='Extract the list of input files from'
@@ -1400,6 +1401,9 @@
     output_file = os.path.join(options.output_dir, root_name) + '_jni.h'
   GenerateJNIHeader(input_file, output_file, options)
 
+  if options.depfile:
+    build_utils.WriteDepfile(options.depfile, output_file)
+
 
 if __name__ == '__main__':
   sys.exit(main(sys.argv))
diff --git a/base/android/jni_generator/jni_generator.pydeps b/base/android/jni_generator/jni_generator.pydeps
deleted file mode 100644
index f3db670..0000000
--- a/base/android/jni_generator/jni_generator.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root base/android/jni_generator --output base/android/jni_generator/jni_generator.pydeps base/android/jni_generator/jni_generator.py
-../../../build/android/gyp/util/__init__.py
-../../../build/android/gyp/util/build_utils.py
-../../../build/android/gyp/util/md5_check.py
-../../../build/gn_helpers.py
-jni_generator.py
diff --git a/base/android/jni_generator/jni_registration_generator.py b/base/android/jni_generator/jni_registration_generator.py
index d4fb39a..dec56ee 100755
--- a/base/android/jni_generator/jni_registration_generator.py
+++ b/base/android/jni_generator/jni_registration_generator.py
@@ -333,8 +333,7 @@
 
   if args.depfile:
     build_utils.WriteDepfile(args.depfile, output_file,
-                             args.sources_files + java_file_paths,
-                             add_pydeps=False)
+                             args.sources_files + java_file_paths)
 
 
 if __name__ == '__main__':
diff --git a/base/android/jni_generator/jni_registration_generator.pydeps b/base/android/jni_generator/jni_registration_generator.pydeps
deleted file mode 100644
index 14b0ae22..0000000
--- a/base/android/jni_generator/jni_registration_generator.pydeps
+++ /dev/null
@@ -1,8 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root base/android/jni_generator --output base/android/jni_generator/jni_registration_generator.pydeps base/android/jni_generator/jni_registration_generator.py
-../../../build/android/gyp/util/__init__.py
-../../../build/android/gyp/util/build_utils.py
-../../../build/android/gyp/util/md5_check.py
-../../../build/gn_helpers.py
-jni_generator.py
-jni_registration_generator.py
diff --git a/base/containers/small_map.h b/base/containers/small_map.h
index 495332f..ac8d2988 100644
--- a/base/containers/small_map.h
+++ b/base/containers/small_map.h
@@ -7,6 +7,7 @@
 
 #include <stddef.h>
 
+#include <limits>
 #include <map>
 #include <new>
 #include <string>
@@ -15,11 +16,15 @@
 #include "base/containers/hash_tables.h"
 #include "base/logging.h"
 
+namespace {
+constexpr size_t kUsingFullMapSentinel = std::numeric_limits<size_t>::max();
+}  // namespace
+
 namespace base {
 
-// small_map is a container with a std::map-like interface. It starts out
-// backed by a unsorted array but switches to some other container type if it
-// grows beyond this fixed size.
+// small_map is a container with a std::map-like interface. It starts out backed
+// by an unsorted array but switches to some other container type if it grows
+// beyond this fixed size.
 //
 // Please see //base/containers/README.md for an overview of which container
 // to select.
@@ -45,9 +50,9 @@
 // array. std::unordered_map has a key_equal function which will be used.
 //
 // We define default overrides for the common map types to avoid this
-// double-compare, but you should be aware of this if you use your own
-// operator< for your map and supply yor own version of == to the small_map.
-// You can use regular operator== by just doing:
+// double-compare, but you should be aware of this if you use your own operator<
+// for your map and supply yor own version of == to the small_map. You can use
+// regular operator== by just doing:
 //
 //   base::small_map<std::map<MyKey, MyValue>, 4, std::equal_to<KyKey>>
 //
@@ -55,15 +60,15 @@
 // USAGE
 // -----
 //
-// NormalMap:  The map type to fall back to.  This also defines the key
-//             and value types for the small_map.
-// kArraySize:  The size of the initial array of results. This will be
-//              allocated with the small_map object rather than separately on
-//              the heap. Once the map grows beyond this size, the map type
-//              will be used instead.
-// EqualKey:  A functor which tests two keys for equality.  If the wrapped
-//            map type has a "key_equal" member (hash_map does), then that will
-//            be used by default. If the wrapped map type has a strict weak
+// NormalMap:  The map type to fall back to. This also defines the key and value
+//             types for the small_map.
+// kArraySize:  The size of the initial array of results. This will be allocated
+//              with the small_map object rather than separately on the heap.
+//              Once the map grows beyond this size, the map type will be used
+//              instead.
+// EqualKey:  A functor which tests two keys for equality. If the wrapped map
+//            type has a "key_equal" member (hash_map does), then that will be
+//            used by default. If the wrapped map type has a strict weak
 //            ordering "key_compare" (std::map does), that will be used to
 //            implement equality by default.
 // MapInit: A functor that takes a NormalMap* and uses it to initialize the map.
@@ -73,7 +78,7 @@
 //          NormalMap* argument with placement new, since after it runs we
 //          assume that the NormalMap has been initialized.
 //
-// example:
+// Example:
 //   base::small_map<std::map<string, int>> days;
 //   days["sunday"   ] = 0;
 //   days["monday"   ] = 1;
@@ -160,17 +165,15 @@
 }  // namespace internal
 
 template <typename NormalMap,
-          int kArraySize = 4,
+          size_t kArraySize = 4,
           typename EqualKey = typename internal::select_equal_key<
               NormalMap,
               internal::has_key_equal<NormalMap>::value>::equal_key,
           typename MapInit = internal::small_map_default_init<NormalMap>>
 class small_map {
-  // We cannot rely on the compiler to reject array of size 0.  In
-  // particular, gcc 2.95.3 does it but later versions allow 0-length
-  // arrays.  Therefore, we explicitly reject non-positive kArraySize
-  // here.
-  static_assert(kArraySize > 0, "default initial size should be positive");
+  static_assert(kArraySize > 0, "Initial size must be greater than 0");
+  static_assert(kArraySize != kUsingFullMapSentinel,
+                "Initial size out of range");
 
  public:
   typedef typename NormalMap::key_type key_type;
@@ -188,16 +191,18 @@
     // size_ and functor_ are initted in InitFrom()
     InitFrom(src);
   }
+
   void operator=(const small_map& src) {
     if (&src == this) return;
 
-    // This is not optimal. If src and dest are both using the small
-    // array, we could skip the teardown and reconstruct. One problem
-    // to be resolved is that the value_type itself is pair<const K,
-    // V>, and const K is not assignable.
+    // This is not optimal. If src and dest are both using the small array, we
+    // could skip the teardown and reconstruct. One problem to be resolved is
+    // that the value_type itself is pair<const K, V>, and const K is not
+    // assignable.
     Destroy();
     InitFrom(src);
   }
+
   ~small_map() { Destroy(); }
 
   class const_iterator;
@@ -210,55 +215,51 @@
     typedef typename NormalMap::iterator::pointer pointer;
     typedef typename NormalMap::iterator::reference reference;
 
-    inline iterator(): array_iter_(NULL) {}
+    inline iterator() : array_iter_(nullptr) {}
 
     inline iterator& operator++() {
-      if (array_iter_ != NULL) {
+      if (array_iter_ != nullptr) {
         ++array_iter_;
       } else {
-        ++hash_iter_;
+        ++map_iter_;
       }
       return *this;
     }
+
     inline iterator operator++(int /*unused*/) {
       iterator result(*this);
       ++(*this);
       return result;
     }
+
     inline iterator& operator--() {
-      if (array_iter_ != NULL) {
+      if (array_iter_ != nullptr) {
         --array_iter_;
       } else {
-        --hash_iter_;
+        --map_iter_;
       }
       return *this;
     }
+
     inline iterator operator--(int /*unused*/) {
       iterator result(*this);
       --(*this);
       return result;
     }
+
     inline value_type* operator->() const {
-      if (array_iter_ != NULL) {
-        return array_iter_;
-      } else {
-        return hash_iter_.operator->();
-      }
+      return array_iter_ ? array_iter_ : map_iter_.operator->();
     }
 
     inline value_type& operator*() const {
-      if (array_iter_ != NULL) {
-        return *array_iter_;
-      } else {
-        return *hash_iter_;
-      }
+      return array_iter_ ? *array_iter_ : *map_iter_;
     }
 
     inline bool operator==(const iterator& other) const {
-      if (array_iter_ != NULL) {
+      if (array_iter_ != nullptr) {
         return array_iter_ == other.array_iter_;
       } else {
-        return other.array_iter_ == NULL && hash_iter_ == other.hash_iter_;
+        return other.array_iter_ == nullptr && map_iter_ == other.map_iter_;
       }
     }
 
@@ -274,10 +275,10 @@
     friend class const_iterator;
     inline explicit iterator(value_type* init) : array_iter_(init) {}
     inline explicit iterator(const typename NormalMap::iterator& init)
-      : array_iter_(NULL), hash_iter_(init) {}
+        : array_iter_(nullptr), map_iter_(init) {}
 
     value_type* array_iter_;
-    typename NormalMap::iterator hash_iter_;
+    typename NormalMap::iterator map_iter_;
   };
 
   class const_iterator {
@@ -289,19 +290,22 @@
     typedef typename NormalMap::const_iterator::pointer pointer;
     typedef typename NormalMap::const_iterator::reference reference;
 
-    inline const_iterator(): array_iter_(NULL) {}
-    // Non-explicit ctor lets us convert regular iterators to const iterators
+    inline const_iterator() : array_iter_(nullptr) {}
+
+    // Non-explicit constructor lets us convert regular iterators to const
+    // iterators.
     inline const_iterator(const iterator& other)
-      : array_iter_(other.array_iter_), hash_iter_(other.hash_iter_) {}
+        : array_iter_(other.array_iter_), map_iter_(other.map_iter_) {}
 
     inline const_iterator& operator++() {
-      if (array_iter_ != NULL) {
+      if (array_iter_ != nullptr) {
         ++array_iter_;
       } else {
-        ++hash_iter_;
+        ++map_iter_;
       }
       return *this;
     }
+
     inline const_iterator operator++(int /*unused*/) {
       const_iterator result(*this);
       ++(*this);
@@ -309,13 +313,14 @@
     }
 
     inline const_iterator& operator--() {
-      if (array_iter_ != NULL) {
+      if (array_iter_ != nullptr) {
         --array_iter_;
       } else {
-        --hash_iter_;
+        --map_iter_;
       }
       return *this;
     }
+
     inline const_iterator operator--(int /*unused*/) {
       const_iterator result(*this);
       --(*this);
@@ -323,27 +328,18 @@
     }
 
     inline const value_type* operator->() const {
-      if (array_iter_ != NULL) {
-        return array_iter_;
-      } else {
-        return hash_iter_.operator->();
-      }
+      return array_iter_ ? array_iter_ : map_iter_.operator->();
     }
 
     inline const value_type& operator*() const {
-      if (array_iter_ != NULL) {
-        return *array_iter_;
-      } else {
-        return *hash_iter_;
-      }
+      return array_iter_ ? *array_iter_ : *map_iter_;
     }
 
     inline bool operator==(const const_iterator& other) const {
-      if (array_iter_ != NULL) {
+      if (array_iter_ != nullptr) {
         return array_iter_ == other.array_iter_;
-      } else {
-        return other.array_iter_ == NULL && hash_iter_ == other.hash_iter_;
       }
+      return other.array_iter_ == nullptr && map_iter_ == other.map_iter_;
     }
 
     inline bool operator!=(const const_iterator& other) const {
@@ -356,86 +352,92 @@
         : array_iter_(init) {}
     inline explicit const_iterator(
         const typename NormalMap::const_iterator& init)
-      : array_iter_(NULL), hash_iter_(init) {}
+        : array_iter_(nullptr), map_iter_(init) {}
 
     const value_type* array_iter_;
-    typename NormalMap::const_iterator hash_iter_;
+    typename NormalMap::const_iterator map_iter_;
   };
 
   iterator find(const key_type& key) {
     key_equal compare;
-    if (size_ >= 0) {
-      for (int i = 0; i < size_; i++) {
-        if (compare(array_[i].first, key)) {
-          return iterator(array_ + i);
-        }
-      }
-      return iterator(array_ + size_);
-    } else {
+
+    if (UsingFullMap()) {
       return iterator(map()->find(key));
     }
+
+    for (size_t i = 0; i < size_; ++i) {
+      if (compare(array_[i].first, key)) {
+        return iterator(array_ + i);
+      }
+    }
+    return iterator(array_ + size_);
   }
 
   const_iterator find(const key_type& key) const {
     key_equal compare;
-    if (size_ >= 0) {
-      for (int i = 0; i < size_; i++) {
-        if (compare(array_[i].first, key)) {
-          return const_iterator(array_ + i);
-        }
-      }
-      return const_iterator(array_ + size_);
-    } else {
+
+    if (UsingFullMap()) {
       return const_iterator(map()->find(key));
     }
+
+    for (size_t i = 0; i < size_; ++i) {
+      if (compare(array_[i].first, key)) {
+        return const_iterator(array_ + i);
+      }
+    }
+    return const_iterator(array_ + size_);
   }
 
   // Invalidates iterators.
   data_type& operator[](const key_type& key) {
     key_equal compare;
 
-    if (size_ >= 0) {
-      // operator[] searches backwards, favoring recently-added
-      // elements.
-      for (int i = size_-1; i >= 0; --i) {
-        if (compare(array_[i].first, key)) {
-          return array_[i].second;
-        }
-      }
-      if (size_ == kArraySize) {
-        ConvertToRealMap();
-        return map_[key];
-      } else {
-        new (&array_[size_]) value_type(key, data_type());
-        return array_[size_++].second;
-      }
-    } else {
+    if (UsingFullMap()) {
       return map_[key];
     }
+
+    // Search backwards to favor recently-added elements.
+    for (size_t i = size_; i > 0; --i) {
+      const size_t index = i - 1;
+      if (compare(array_[index].first, key)) {
+        return array_[index].second;
+      }
+    }
+
+    if (size_ == kArraySize) {
+      ConvertToRealMap();
+      return map_[key];
+    }
+
+    DCHECK(size_ < kArraySize);
+    new (&array_[size_]) value_type(key, data_type());
+    return array_[size_++].second;
   }
 
   // Invalidates iterators.
   std::pair<iterator, bool> insert(const value_type& x) {
     key_equal compare;
 
-    if (size_ >= 0) {
-      for (int i = 0; i < size_; i++) {
-        if (compare(array_[i].first, x.first)) {
-          return std::make_pair(iterator(array_ + i), false);
-        }
-      }
-      if (size_ == kArraySize) {
-        ConvertToRealMap();  // Invalidates all iterators!
-        std::pair<typename NormalMap::iterator, bool> ret = map_.insert(x);
-        return std::make_pair(iterator(ret.first), ret.second);
-      } else {
-        new (&array_[size_]) value_type(x);
-        return std::make_pair(iterator(array_ + size_++), true);
-      }
-    } else {
+    if (UsingFullMap()) {
       std::pair<typename NormalMap::iterator, bool> ret = map_.insert(x);
       return std::make_pair(iterator(ret.first), ret.second);
     }
+
+    for (size_t i = 0; i < size_; ++i) {
+      if (compare(array_[i].first, x.first)) {
+        return std::make_pair(iterator(array_ + i), false);
+      }
+    }
+
+    if (size_ == kArraySize) {
+      ConvertToRealMap();  // Invalidates all iterators!
+      std::pair<typename NormalMap::iterator, bool> ret = map_.insert(x);
+      return std::make_pair(iterator(ret.first), ret.second);
+    }
+
+    DCHECK(size_ < kArraySize);
+    new (&array_[size_]) value_type(x);
+    return std::make_pair(iterator(array_ + size_++), true);
   }
 
   // Invalidates iterators.
@@ -452,136 +454,122 @@
   std::pair<iterator, bool> emplace(Args&&... args) {
     key_equal compare;
 
-    if (size_ >= 0) {
-      value_type x(std::forward<Args>(args)...);
-      for (int i = 0; i < size_; i++) {
-        if (compare(array_[i].first, x.first)) {
-          return std::make_pair(iterator(array_ + i), false);
-        }
-      }
-      if (size_ == kArraySize) {
-        ConvertToRealMap();  // Invalidates all iterators!
-        std::pair<typename NormalMap::iterator, bool> ret =
-            map_.emplace(std::move(x));
-        return std::make_pair(iterator(ret.first), ret.second);
-      } else {
-        new (&array_[size_]) value_type(std::move(x));
-        return std::make_pair(iterator(array_ + size_++), true);
-      }
-    } else {
+    if (UsingFullMap()) {
       std::pair<typename NormalMap::iterator, bool> ret =
           map_.emplace(std::forward<Args>(args)...);
       return std::make_pair(iterator(ret.first), ret.second);
     }
+
+    value_type x(std::forward<Args>(args)...);
+    for (size_t i = 0; i < size_; ++i) {
+      if (compare(array_[i].first, x.first)) {
+        return std::make_pair(iterator(array_ + i), false);
+      }
+    }
+
+    if (size_ == kArraySize) {
+      ConvertToRealMap();  // Invalidates all iterators!
+      std::pair<typename NormalMap::iterator, bool> ret =
+          map_.emplace(std::move(x));
+      return std::make_pair(iterator(ret.first), ret.second);
+    }
+
+    DCHECK(size_ < kArraySize);
+    new (&array_[size_]) value_type(std::move(x));
+    return std::make_pair(iterator(array_ + size_++), true);
   }
 
   iterator begin() {
-    if (size_ >= 0) {
-      return iterator(array_);
-    } else {
-      return iterator(map_.begin());
-    }
+    return UsingFullMap() ? iterator(map_.begin()) : iterator(array_);
   }
+
   const_iterator begin() const {
-    if (size_ >= 0) {
-      return const_iterator(array_);
-    } else {
-      return const_iterator(map_.begin());
-    }
+    return UsingFullMap() ? const_iterator(map_.begin())
+                          : const_iterator(array_);
   }
 
   iterator end() {
-    if (size_ >= 0) {
-      return iterator(array_ + size_);
-    } else {
-      return iterator(map_.end());
-    }
+    return UsingFullMap() ? iterator(map_.end()) : iterator(array_ + size_);
   }
+
   const_iterator end() const {
-    if (size_ >= 0) {
-      return const_iterator(array_ + size_);
-    } else {
-      return const_iterator(map_.end());
-    }
+    return UsingFullMap() ? const_iterator(map_.end())
+                          : const_iterator(array_ + size_);
   }
 
   void clear() {
-    if (size_ >= 0) {
-      for (int i = 0; i < size_; i++) {
+    if (UsingFullMap()) {
+      map_.~NormalMap();
+    } else {
+      for (size_t i = 0; i < size_; ++i) {
         array_[i].~value_type();
       }
-    } else {
-      map_.~NormalMap();
     }
     size_ = 0;
   }
 
   // Invalidates iterators. Returns iterator following the last removed element.
   iterator erase(const iterator& position) {
-    if (size_ >= 0) {
-      int i = position.array_iter_ - array_;
-      array_[i].~value_type();
-      --size_;
-      if (i != size_) {
-        new (&array_[i]) value_type(std::move(array_[size_]));
-        array_[size_].~value_type();
-        return iterator(array_ + i);
-      }
-      return end();
+    if (UsingFullMap()) {
+      return iterator(map_.erase(position.map_iter_));
     }
-    return iterator(map_.erase(position.hash_iter_));
+
+    size_t i = position.array_iter_ - array_;
+    // TODO(crbug.com/817982): When we have a checked iterator, this CHECK might
+    // not be necessary.
+    CHECK_LE(i, size_);
+    array_[i].~value_type();
+    --size_;
+    if (i != size_) {
+      new (&array_[i]) value_type(std::move(array_[size_]));
+      array_[size_].~value_type();
+      return iterator(array_ + i);
+    }
+    return end();
   }
 
   size_t erase(const key_type& key) {
     iterator iter = find(key);
-    if (iter == end()) return 0u;
+    if (iter == end()) {
+      return 0;
+    }
     erase(iter);
-    return 1u;
+    return 1;
   }
 
   size_t count(const key_type& key) const {
     return (find(key) == end()) ? 0 : 1;
   }
 
-  size_t size() const {
-    if (size_ >= 0) {
-      return static_cast<size_t>(size_);
-    } else {
-      return map_.size();
-    }
-  }
+  size_t size() const { return UsingFullMap() ? map_.size() : size_; }
 
-  bool empty() const {
-    if (size_ >= 0) {
-      return (size_ == 0);
-    } else {
-      return map_.empty();
-    }
-  }
+  bool empty() const { return UsingFullMap() ? map_.empty() : size_ == 0; }
 
   // Returns true if we have fallen back to using the underlying map
   // representation.
-  bool UsingFullMap() const {
-    return size_ < 0;
-  }
+  bool UsingFullMap() const { return size_ == kUsingFullMapSentinel; }
 
   inline NormalMap* map() {
     CHECK(UsingFullMap());
     return &map_;
   }
+
   inline const NormalMap* map() const {
     CHECK(UsingFullMap());
     return &map_;
   }
 
  private:
-  int size_;  // negative = using hash_map
+  // When `size_ == kUsingFullMapSentinel`, we have switched storage strategies
+  // from `array_[kArraySize] to `NormalMap map_`. See ConvertToRealMap and
+  // UsingFullMap.
+  size_t size_;
 
   MapInit functor_;
 
-  // We want to call constructors and destructors manually, but we don't want to
-  // allocate and deallocate the memory used for them separately. Since array_
-  // and map_ are mutually exclusive, we'll put them in a union.
+  // We want to call constructors and destructors manually, but we don't want
+  // to allocate and deallocate the memory used for them separately. Since
+  // array_ and map_ are mutually exclusive, we'll put them in a union.
   union {
     value_type array_[kArraySize];
     NormalMap map_;
@@ -598,17 +586,17 @@
     } temp;
 
     // Move the current elements into a temporary array.
-    for (int i = 0; i < kArraySize; i++) {
+    for (size_t i = 0; i < kArraySize; ++i) {
       new (&temp.array[i]) value_type(std::move(array_[i]));
       array_[i].~value_type();
     }
 
     // Initialize the map.
-    size_ = -1;
+    size_ = kUsingFullMapSentinel;
     functor_(&map_);
 
     // Insert elements into it.
-    for (int i = 0; i < kArraySize; i++) {
+    for (size_t i = 0; i < kArraySize; ++i) {
       map_.insert(std::move(temp.array[i]));
       temp.array[i].~value_type();
     }
@@ -618,36 +606,38 @@
   void InitFrom(const small_map& src) {
     functor_ = src.functor_;
     size_ = src.size_;
-    if (src.size_ >= 0) {
-      for (int i = 0; i < size_; i++) {
-        new (&array_[i]) value_type(src.array_[i]);
-      }
-    } else {
+    if (src.UsingFullMap()) {
       functor_(&map_);
       map_ = src.map_;
+    } else {
+      for (size_t i = 0; i < size_; ++i) {
+        new (&array_[i]) value_type(src.array_[i]);
+      }
     }
   }
+
   void Destroy() {
-    if (size_ >= 0) {
-      for (int i = 0; i < size_; i++) {
+    if (UsingFullMap()) {
+      map_.~NormalMap();
+    } else {
+      for (size_t i = 0; i < size_; ++i) {
         array_[i].~value_type();
       }
-    } else {
-      map_.~NormalMap();
     }
   }
 };
 
 template <typename NormalMap,
-          int kArraySize,
+          size_t kArraySize,
           typename EqualKey,
           typename Functor>
 inline bool small_map<NormalMap, kArraySize, EqualKey, Functor>::iterator::
 operator==(const const_iterator& other) const {
   return other == *this;
 }
+
 template <typename NormalMap,
-          int kArraySize,
+          size_t kArraySize,
           typename EqualKey,
           typename Functor>
 inline bool small_map<NormalMap, kArraySize, EqualKey, Functor>::iterator::
diff --git a/base/test/BUILD.gn b/base/test/BUILD.gn
index ddede09..a06a02d 100644
--- a/base/test/BUILD.gn
+++ b/base/test/BUILD.gn
@@ -209,7 +209,6 @@
     ]
     if (current_toolchain == host_toolchain) {
       data_deps += [ ":do_generate_fontconfig_caches" ]
-      data += [ "$root_out_dir/fontconfig_caches/" ]
     }
   }
 
@@ -379,7 +378,8 @@
       ]
       args = []
       outputs = [
-        "$root_out_dir/fontconfig_caches/STAMP",
+        "$root_out_dir/fontconfig_caches/",
+        "$root_out_dir/test_fonts/.uuid",
       ]
     }
   }
diff --git a/base/test/generate_fontconfig_caches.cc b/base/test/generate_fontconfig_caches.cc
index f12eb481..bb940329 100644
--- a/base/test/generate_fontconfig_caches.cc
+++ b/base/test/generate_fontconfig_caches.cc
@@ -2,19 +2,53 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <sys/stat.h>
+#include <time.h>
+#include <utime.h>
+
 #include <string>
 
+#include "base/base_paths.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/path_service.h"
 #include "base/test/fontconfig_util_linux.h"
 
+// GIANT WARNING: The point of this file is to front-load construction of the
+// font cache [which takes 600ms] from test run time to compile time. This saves
+// 600ms on each test shard which uses the font cache into compile time. The
+// problem is that fontconfig cache construction is not intended to be
+// deterministic. This executable tries to set some external state to ensure
+// determinism. We have no way of guaranteeing that this produces correct
+// results, or even has the intended effect.
 int main(void) {
+  // fontconfig generates a random uuid and uses it to match font folders with
+  // the font cache. Rather than letting fontconfig generate a random uuid,
+  // which introduces build non-determinism, we place a fixed uuid in the font
+  // folder, which fontconfig will use to generate the cache.
+  base::FilePath dir_module;
+  base::PathService::Get(base::DIR_MODULE, &dir_module);
+  base::FilePath uuid_file_path =
+      dir_module.Append("test_fonts").Append(".uuid");
+  const char uuid[] = "df1acc8c-39d5-4a8b-8507-b1a7396ac3ac";
+  WriteFile(uuid_file_path, uuid, strlen(uuid));
+
+  // fontconfig writes the mtime of the test_fonts directory into the cache. It
+  // presumably checks this later to ensure that the cache is still up to date.
+  // We set the mtime to an arbitrary, fixed time in the past.
+  base::FilePath test_fonts_file_path = dir_module.Append("test_fonts");
+  struct stat old_times;
+  struct utimbuf new_times;
+
+  stat(test_fonts_file_path.value().c_str(), &old_times);
+  new_times.actime = old_times.st_atime;
+  // Use an arbitrary, fixed time.
+  new_times.modtime = 123456789;
+  utime(test_fonts_file_path.value().c_str(), &new_times);
+
   base::SetUpFontconfig();
   base::TearDownFontconfig();
 
-  base::FilePath dir_module;
-  CHECK(base::PathService::Get(base::DIR_MODULE, &dir_module));
   base::FilePath fontconfig_caches = dir_module.Append("fontconfig_caches");
   CHECK(base::DirectoryExists(fontconfig_caches));
   base::FilePath stamp = fontconfig_caches.Append("STAMP");
diff --git a/base/trace_event/tracing_agent.h b/base/trace_event/tracing_agent.h
index f818457..ee583700 100644
--- a/base/trace_event/tracing_agent.h
+++ b/base/trace_event/tracing_agent.h
@@ -22,8 +22,8 @@
 // tracing method that produces its own trace log should implement this
 // interface. All tracing agents must only be controlled by TracingController.
 // Some existing examples include TracingControllerImpl for Chrome trace events,
-// DebugDaemonClient for CrOs system trace, EtwTracingAgent for Windows system
-// trace and PowerTracingAgent for BattOr power trace.
+// DebugDaemonClient for CrOs system trace, and EtwTracingAgent for Windows
+// system.
 class BASE_EXPORT TracingAgent {
  public:
   using StartAgentTracingCallback =
diff --git a/build/android/gyp/aar.pydeps b/build/android/gyp/aar.pydeps
deleted file mode 100644
index e08c547..0000000
--- a/build/android/gyp/aar.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/aar.pydeps build/android/gyp/aar.py
-../../gn_helpers.py
-aar.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/aidl.py b/build/android/gyp/aidl.py
index 64ad290..1591f245 100755
--- a/build/android/gyp/aidl.py
+++ b/build/android/gyp/aidl.py
@@ -18,6 +18,7 @@
 
 def main(argv):
   option_parser = optparse.OptionParser()
+  build_utils.AddDepfileOption(option_parser)
   option_parser.add_option('--aidl-path', help='Path to the aidl binary.')
   option_parser.add_option('--imports', help='Files to import.')
   option_parser.add_option('--includes',
@@ -53,6 +54,9 @@
               pkg_name.replace('.', '/'), os.path.basename(path))
           build_utils.AddToZipHermetic(srcjar, arcname, data=data)
 
+  if options.depfile:
+    build_utils.WriteDepfile(options.depfile, options.srcjar)
+
 
 if __name__ == '__main__':
   sys.exit(main(sys.argv))
diff --git a/build/android/gyp/aidl.pydeps b/build/android/gyp/aidl.pydeps
deleted file mode 100644
index 2dbce376..0000000
--- a/build/android/gyp/aidl.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/aidl.pydeps build/android/gyp/aidl.py
-../../gn_helpers.py
-aidl.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/apkbuilder.pydeps b/build/android/gyp/apkbuilder.pydeps
deleted file mode 100644
index 3ae0331..0000000
--- a/build/android/gyp/apkbuilder.pydeps
+++ /dev/null
@@ -1,8 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/apkbuilder.pydeps build/android/gyp/apkbuilder.py
-../../gn_helpers.py
-apkbuilder.py
-finalize_apk.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/app_bundle_to_apks.pydeps b/build/android/gyp/app_bundle_to_apks.pydeps
deleted file mode 100644
index 49c2892..0000000
--- a/build/android/gyp/app_bundle_to_apks.pydeps
+++ /dev/null
@@ -1,8 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/app_bundle_to_apks.pydeps build/android/gyp/app_bundle_to_apks.py
-../../gn_helpers.py
-app_bundle_to_apks.py
-bundletool.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/bytecode_processor.pydeps b/build/android/gyp/bytecode_processor.pydeps
deleted file mode 100644
index d8ff396..0000000
--- a/build/android/gyp/bytecode_processor.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/bytecode_processor.pydeps build/android/gyp/bytecode_processor.py
-../../gn_helpers.py
-bytecode_processor.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/compile_resources.py b/build/android/gyp/compile_resources.py
index cda03e60..412c6f74 100755
--- a/build/android/gyp/compile_resources.py
+++ b/build/android/gyp/compile_resources.py
@@ -671,8 +671,7 @@
       input_paths=input_paths,
       input_strings=input_strings,
       output_paths=output_paths,
-      depfile_deps=options.dependencies_res_zips + options.extra_r_text_files,
-      add_pydeps=False)
+      depfile_deps=options.dependencies_res_zips + options.extra_r_text_files)
 
 
 if __name__ == '__main__':
diff --git a/build/android/gyp/compile_resources.pydeps b/build/android/gyp/compile_resources.pydeps
deleted file mode 100644
index 2ffcb52..0000000
--- a/build/android/gyp/compile_resources.pydeps
+++ /dev/null
@@ -1,29 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/compile_resources.pydeps build/android/gyp/compile_resources.py
-../../../third_party/jinja2/__init__.py
-../../../third_party/jinja2/_compat.py
-../../../third_party/jinja2/bccache.py
-../../../third_party/jinja2/compiler.py
-../../../third_party/jinja2/defaults.py
-../../../third_party/jinja2/environment.py
-../../../third_party/jinja2/exceptions.py
-../../../third_party/jinja2/filters.py
-../../../third_party/jinja2/idtracking.py
-../../../third_party/jinja2/lexer.py
-../../../third_party/jinja2/loaders.py
-../../../third_party/jinja2/nodes.py
-../../../third_party/jinja2/optimizer.py
-../../../third_party/jinja2/parser.py
-../../../third_party/jinja2/runtime.py
-../../../third_party/jinja2/tests.py
-../../../third_party/jinja2/utils.py
-../../../third_party/jinja2/visitor.py
-../../../third_party/markupsafe/__init__.py
-../../../third_party/markupsafe/_compat.py
-../../../third_party/markupsafe/_native.py
-../../gn_helpers.py
-compile_resources.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
-util/resource_utils.py
diff --git a/build/android/gyp/copy_ex.pydeps b/build/android/gyp/copy_ex.pydeps
deleted file mode 100644
index e0fb31e..0000000
--- a/build/android/gyp/copy_ex.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/copy_ex.pydeps build/android/gyp/copy_ex.py
-../../gn_helpers.py
-copy_ex.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/create_apk_operations_script.pydeps b/build/android/gyp/create_apk_operations_script.pydeps
deleted file mode 100644
index 9d4dcb8..0000000
--- a/build/android/gyp/create_apk_operations_script.pydeps
+++ /dev/null
@@ -1,3 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/create_apk_operations_script.pydeps build/android/gyp/create_apk_operations_script.py
-create_apk_operations_script.py
diff --git a/build/android/gyp/create_app_bundle.pydeps b/build/android/gyp/create_app_bundle.pydeps
deleted file mode 100644
index fef04fa..0000000
--- a/build/android/gyp/create_app_bundle.pydeps
+++ /dev/null
@@ -1,30 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/create_app_bundle.pydeps build/android/gyp/create_app_bundle.py
-../../../third_party/jinja2/__init__.py
-../../../third_party/jinja2/_compat.py
-../../../third_party/jinja2/bccache.py
-../../../third_party/jinja2/compiler.py
-../../../third_party/jinja2/defaults.py
-../../../third_party/jinja2/environment.py
-../../../third_party/jinja2/exceptions.py
-../../../third_party/jinja2/filters.py
-../../../third_party/jinja2/idtracking.py
-../../../third_party/jinja2/lexer.py
-../../../third_party/jinja2/loaders.py
-../../../third_party/jinja2/nodes.py
-../../../third_party/jinja2/optimizer.py
-../../../third_party/jinja2/parser.py
-../../../third_party/jinja2/runtime.py
-../../../third_party/jinja2/tests.py
-../../../third_party/jinja2/utils.py
-../../../third_party/jinja2/visitor.py
-../../../third_party/markupsafe/__init__.py
-../../../third_party/markupsafe/_compat.py
-../../../third_party/markupsafe/_native.py
-../../gn_helpers.py
-bundletool.py
-create_app_bundle.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
-util/resource_utils.py
diff --git a/build/android/gyp/create_bundle_wrapper_script.pydeps b/build/android/gyp/create_bundle_wrapper_script.pydeps
deleted file mode 100644
index 5c87801..0000000
--- a/build/android/gyp/create_bundle_wrapper_script.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/create_bundle_wrapper_script.pydeps build/android/gyp/create_bundle_wrapper_script.py
-../../gn_helpers.py
-create_bundle_wrapper_script.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/create_dist_jar.py b/build/android/gyp/create_dist_jar.py
index 7f78935a..2e06478 100755
--- a/build/android/gyp/create_dist_jar.py
+++ b/build/android/gyp/create_dist_jar.py
@@ -24,8 +24,7 @@
   build_utils.MergeZips(options.output, input_jars)
 
   if options.depfile:
-    build_utils.WriteDepfile(options.depfile, options.output, input_jars,
-                             add_pydeps=False)
+    build_utils.WriteDepfile(options.depfile, options.output, input_jars)
 
 
 if __name__ == '__main__':
diff --git a/build/android/gyp/create_dist_jar.pydeps b/build/android/gyp/create_dist_jar.pydeps
deleted file mode 100644
index f4224d7..0000000
--- a/build/android/gyp/create_dist_jar.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/create_dist_jar.pydeps build/android/gyp/create_dist_jar.py
-../../gn_helpers.py
-create_dist_jar.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/create_java_binary_script.pydeps b/build/android/gyp/create_java_binary_script.pydeps
deleted file mode 100644
index 96d79bf..0000000
--- a/build/android/gyp/create_java_binary_script.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/create_java_binary_script.pydeps build/android/gyp/create_java_binary_script.py
-../../gn_helpers.py
-create_java_binary_script.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/create_stack_script.pydeps b/build/android/gyp/create_stack_script.pydeps
deleted file mode 100644
index 7bddb156..0000000
--- a/build/android/gyp/create_stack_script.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/create_stack_script.pydeps build/android/gyp/create_stack_script.py
-../../gn_helpers.py
-create_stack_script.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/create_test_runner_script.pydeps b/build/android/gyp/create_test_runner_script.pydeps
deleted file mode 100644
index 4b8876b..0000000
--- a/build/android/gyp/create_test_runner_script.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/create_test_runner_script.pydeps build/android/gyp/create_test_runner_script.py
-../../gn_helpers.py
-create_test_runner_script.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/create_tool_wrapper.pydeps b/build/android/gyp/create_tool_wrapper.pydeps
deleted file mode 100644
index 75b8326e..0000000
--- a/build/android/gyp/create_tool_wrapper.pydeps
+++ /dev/null
@@ -1,3 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/create_tool_wrapper.pydeps build/android/gyp/create_tool_wrapper.py
-create_tool_wrapper.py
diff --git a/build/android/gyp/desugar.pydeps b/build/android/gyp/desugar.pydeps
deleted file mode 100644
index a40f3aa7..0000000
--- a/build/android/gyp/desugar.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/desugar.pydeps build/android/gyp/desugar.py
-../../gn_helpers.py
-desugar.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/dex.pydeps b/build/android/gyp/dex.pydeps
deleted file mode 100644
index 0e18d02..0000000
--- a/build/android/gyp/dex.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/dex.pydeps build/android/gyp/dex.py
-../../gn_helpers.py
-dex.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/dist_aar.py b/build/android/gyp/dist_aar.py
index ed823f18..6dc38c4 100755
--- a/build/android/gyp/dist_aar.py
+++ b/build/android/gyp/dist_aar.py
@@ -124,8 +124,7 @@
   if options.depfile:
     all_inputs = (options.jars + options.dependencies_res_zips +
                   options.r_text_files + options.proguard_configs)
-    build_utils.WriteDepfile(options.depfile, options.output, all_inputs,
-                             add_pydeps=False)
+    build_utils.WriteDepfile(options.depfile, options.output, all_inputs)
 
 
 if __name__ == '__main__':
diff --git a/build/android/gyp/dist_aar.pydeps b/build/android/gyp/dist_aar.pydeps
deleted file mode 100644
index da5ea8d..0000000
--- a/build/android/gyp/dist_aar.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/dist_aar.pydeps build/android/gyp/dist_aar.py
-../../gn_helpers.py
-dist_aar.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/emma_instr.pydeps b/build/android/gyp/emma_instr.pydeps
deleted file mode 100644
index 88f752a0..0000000
--- a/build/android/gyp/emma_instr.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/emma_instr.pydeps build/android/gyp/emma_instr.py
-../../gn_helpers.py
-emma_instr.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/filter_zip.pydeps b/build/android/gyp/filter_zip.pydeps
deleted file mode 100644
index 67c989c..0000000
--- a/build/android/gyp/filter_zip.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/filter_zip.pydeps build/android/gyp/filter_zip.py
-../../gn_helpers.py
-filter_zip.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/gcc_preprocess.py b/build/android/gyp/gcc_preprocess.py
index 8b3444c2..8c5c404 100755
--- a/build/android/gyp/gcc_preprocess.py
+++ b/build/android/gyp/gcc_preprocess.py
@@ -47,7 +47,7 @@
   DoGcc(options)
 
   if options.depfile:
-    build_utils.WriteDepfile(options.depfile, options.output, add_pydeps=False)
+    build_utils.WriteDepfile(options.depfile, options.output)
 
 
 if __name__ == '__main__':
diff --git a/build/android/gyp/gcc_preprocess.pydeps b/build/android/gyp/gcc_preprocess.pydeps
deleted file mode 100644
index 64e776b..0000000
--- a/build/android/gyp/gcc_preprocess.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/gcc_preprocess.pydeps build/android/gyp/gcc_preprocess.py
-../../gn_helpers.py
-gcc_preprocess.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/generate_proguarded_module_jar.pydeps b/build/android/gyp/generate_proguarded_module_jar.pydeps
deleted file mode 100644
index 6d52b4e..0000000
--- a/build/android/gyp/generate_proguarded_module_jar.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/generate_proguarded_module_jar.pydeps build/android/gyp/generate_proguarded_module_jar.py
-../../gn_helpers.py
-generate_proguarded_module_jar.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/ijar.pydeps b/build/android/gyp/ijar.pydeps
deleted file mode 100644
index ca10697c..0000000
--- a/build/android/gyp/ijar.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/ijar.pydeps build/android/gyp/ijar.py
-../../gn_helpers.py
-ijar.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/java_cpp_enum.py b/build/android/gyp/java_cpp_enum.py
index e7374410..afb4843 100755
--- a/build/android/gyp/java_cpp_enum.py
+++ b/build/android/gyp/java_cpp_enum.py
@@ -447,7 +447,7 @@
         build_utils.AddToZipHermetic(srcjar, output_path, data=data)
 
   if options.depfile:
-    build_utils.WriteDepfile(options.depfile, options.srcjar, add_pydeps=False)
+    build_utils.WriteDepfile(options.depfile, options.srcjar)
 
 
 if __name__ == '__main__':
diff --git a/build/android/gyp/java_cpp_enum.pydeps b/build/android/gyp/java_cpp_enum.pydeps
deleted file mode 100644
index 32c8de5..0000000
--- a/build/android/gyp/java_cpp_enum.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/java_cpp_enum.pydeps build/android/gyp/java_cpp_enum.py
-../../gn_helpers.py
-java_cpp_enum.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/javac.py b/build/android/gyp/javac.py
index fec8198..53d3f99 100755
--- a/build/android/gyp/javac.py
+++ b/build/android/gyp/javac.py
@@ -597,8 +597,7 @@
       input_strings=javac_cmd + classpath,
       output_paths=output_paths,
       force=force,
-      pass_changes=True,
-      add_pydeps=False)
+      pass_changes=True)
 
 
 if __name__ == '__main__':
diff --git a/build/android/gyp/javac.pydeps b/build/android/gyp/javac.pydeps
deleted file mode 100644
index a9d257b..0000000
--- a/build/android/gyp/javac.pydeps
+++ /dev/null
@@ -1,15 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/javac.pydeps build/android/gyp/javac.py
-../../../third_party/colorama/src/colorama/__init__.py
-../../../third_party/colorama/src/colorama/ansi.py
-../../../third_party/colorama/src/colorama/ansitowin32.py
-../../../third_party/colorama/src/colorama/initialise.py
-../../../third_party/colorama/src/colorama/win32.py
-../../../third_party/colorama/src/colorama/winterm.py
-../../gn_helpers.py
-jar.py
-javac.py
-util/__init__.py
-util/build_utils.py
-util/jar_info_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/jinja_template.py b/build/android/gyp/jinja_template.py
index bc51af1..434d281 100755
--- a/build/android/gyp/jinja_template.py
+++ b/build/android/gyp/jinja_template.py
@@ -124,8 +124,7 @@
   parser.add_argument('--variables', help='Variables to be made available in '
                       'the template processing environment, as a GYP list '
                       '(e.g. --variables "channel=beta mstone=39")', default='')
-  parser.add_argument('--no-checks', action='store_true',
-                      help='Disable input checks.')
+  build_utils.AddDepfileOption(parser)
   options = parser.parse_args()
 
   inputs = build_utils.ParseGnList(options.inputs)
@@ -147,13 +146,15 @@
     _ProcessFiles(processor, inputs, options.inputs_base_dir,
                   options.outputs_zip)
 
-  if not options.no_checks:
+  if options.depfile:
+    output = options.output or options.outputs_zip
     all_inputs = set(processor.GetLoadedTemplates())
     all_inputs.difference_update(inputs)
     all_inputs.difference_update(includes)
     if all_inputs:
       raise Exception('Found files not listed via --includes:\n' +
                       '\n'.join(sorted(all_inputs)))
+    build_utils.WriteDepfile(options.depfile, output)
 
 
 if __name__ == '__main__':
diff --git a/build/android/gyp/jinja_template.pydeps b/build/android/gyp/jinja_template.pydeps
deleted file mode 100644
index a2a3817..0000000
--- a/build/android/gyp/jinja_template.pydeps
+++ /dev/null
@@ -1,41 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/jinja_template.pydeps build/android/gyp/jinja_template.py
-../../../third_party/catapult/devil/devil/__init__.py
-../../../third_party/catapult/devil/devil/android/__init__.py
-../../../third_party/catapult/devil/devil/android/constants/__init__.py
-../../../third_party/catapult/devil/devil/android/constants/chrome.py
-../../../third_party/catapult/devil/devil/android/sdk/__init__.py
-../../../third_party/catapult/devil/devil/android/sdk/keyevent.py
-../../../third_party/catapult/devil/devil/android/sdk/version_codes.py
-../../../third_party/catapult/devil/devil/constants/__init__.py
-../../../third_party/catapult/devil/devil/constants/exit_codes.py
-../../../third_party/jinja2/__init__.py
-../../../third_party/jinja2/_compat.py
-../../../third_party/jinja2/bccache.py
-../../../third_party/jinja2/compiler.py
-../../../third_party/jinja2/defaults.py
-../../../third_party/jinja2/environment.py
-../../../third_party/jinja2/exceptions.py
-../../../third_party/jinja2/filters.py
-../../../third_party/jinja2/idtracking.py
-../../../third_party/jinja2/lexer.py
-../../../third_party/jinja2/loaders.py
-../../../third_party/jinja2/nodes.py
-../../../third_party/jinja2/optimizer.py
-../../../third_party/jinja2/parser.py
-../../../third_party/jinja2/runtime.py
-../../../third_party/jinja2/tests.py
-../../../third_party/jinja2/utils.py
-../../../third_party/jinja2/visitor.py
-../../../third_party/markupsafe/__init__.py
-../../../third_party/markupsafe/_compat.py
-../../../third_party/markupsafe/_native.py
-../../gn_helpers.py
-../pylib/__init__.py
-../pylib/constants/__init__.py
-../pylib/constants/host_paths.py
-jinja_template.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
-util/resource_utils.py
diff --git a/build/android/gyp/lint.pydeps b/build/android/gyp/lint.pydeps
deleted file mode 100644
index a8616e4..0000000
--- a/build/android/gyp/lint.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/lint.pydeps build/android/gyp/lint.py
-../../gn_helpers.py
-lint.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/main_dex_list.pydeps b/build/android/gyp/main_dex_list.pydeps
deleted file mode 100644
index 8c482df..0000000
--- a/build/android/gyp/main_dex_list.pydeps
+++ /dev/null
@@ -1,8 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/main_dex_list.pydeps build/android/gyp/main_dex_list.py
-../../gn_helpers.py
-main_dex_list.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
-util/proguard_util.py
diff --git a/build/android/gyp/merge_jar_info_files.pydeps b/build/android/gyp/merge_jar_info_files.pydeps
deleted file mode 100644
index 710091c..0000000
--- a/build/android/gyp/merge_jar_info_files.pydeps
+++ /dev/null
@@ -1,8 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/merge_jar_info_files.pydeps build/android/gyp/merge_jar_info_files.py
-../../gn_helpers.py
-merge_jar_info_files.py
-util/__init__.py
-util/build_utils.py
-util/jar_info_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/merge_manifest.pydeps b/build/android/gyp/merge_manifest.pydeps
deleted file mode 100644
index 37901962c..0000000
--- a/build/android/gyp/merge_manifest.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/merge_manifest.pydeps build/android/gyp/merge_manifest.py
-../../gn_helpers.py
-merge_manifest.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
diff --git a/build/android/gyp/prepare_resources.py b/build/android/gyp/prepare_resources.py
index ae7f53d0..6237c7d 100755
--- a/build/android/gyp/prepare_resources.py
+++ b/build/android/gyp/prepare_resources.py
@@ -297,8 +297,7 @@
       input_paths=input_paths,
       input_strings=input_strings,
       output_paths=output_paths,
-      depfile_deps=depfile_deps,
-      add_pydeps=False)
+      depfile_deps=depfile_deps)
 
 
 if __name__ == '__main__':
diff --git a/build/android/gyp/prepare_resources.pydeps b/build/android/gyp/prepare_resources.pydeps
deleted file mode 100644
index 0e9ccfbe..0000000
--- a/build/android/gyp/prepare_resources.pydeps
+++ /dev/null
@@ -1,30 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/prepare_resources.pydeps build/android/gyp/prepare_resources.py
-../../../third_party/jinja2/__init__.py
-../../../third_party/jinja2/_compat.py
-../../../third_party/jinja2/bccache.py
-../../../third_party/jinja2/compiler.py
-../../../third_party/jinja2/defaults.py
-../../../third_party/jinja2/environment.py
-../../../third_party/jinja2/exceptions.py
-../../../third_party/jinja2/filters.py
-../../../third_party/jinja2/idtracking.py
-../../../third_party/jinja2/lexer.py
-../../../third_party/jinja2/loaders.py
-../../../third_party/jinja2/nodes.py
-../../../third_party/jinja2/optimizer.py
-../../../third_party/jinja2/parser.py
-../../../third_party/jinja2/runtime.py
-../../../third_party/jinja2/tests.py
-../../../third_party/jinja2/utils.py
-../../../third_party/jinja2/visitor.py
-../../../third_party/markupsafe/__init__.py
-../../../third_party/markupsafe/_compat.py
-../../../third_party/markupsafe/_native.py
-../../gn_helpers.py
-generate_v14_compatible_resources.py
-prepare_resources.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
-util/resource_utils.py
diff --git a/build/android/gyp/proguard.pydeps b/build/android/gyp/proguard.pydeps
deleted file mode 100644
index 6db3d7d..0000000
--- a/build/android/gyp/proguard.pydeps
+++ /dev/null
@@ -1,8 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/proguard.pydeps build/android/gyp/proguard.py
-../../gn_helpers.py
-proguard.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
-util/proguard_util.py
diff --git a/build/android/gyp/write_build_config.pydeps b/build/android/gyp/write_build_config.pydeps
deleted file mode 100644
index e317c47..0000000
--- a/build/android/gyp/write_build_config.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/write_build_config.pydeps build/android/gyp/write_build_config.py
-../../gn_helpers.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
-write_build_config.py
diff --git a/build/android/gyp/write_ordered_libraries.py b/build/android/gyp/write_ordered_libraries.py
index 92c82bb..3b9a3374 100755
--- a/build/android/gyp/write_ordered_libraries.py
+++ b/build/android/gyp/write_ordered_libraries.py
@@ -109,8 +109,7 @@
       only_if_changed=True)
 
   if options.depfile:
-    build_utils.WriteDepfile(options.depfile, options.output, libraries,
-                             add_pydeps=False)
+    build_utils.WriteDepfile(options.depfile, options.output, libraries)
 
 
 if __name__ == '__main__':
diff --git a/build/android/gyp/write_ordered_libraries.pydeps b/build/android/gyp/write_ordered_libraries.pydeps
deleted file mode 100644
index c2ed1fee..0000000
--- a/build/android/gyp/write_ordered_libraries.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/write_ordered_libraries.pydeps build/android/gyp/write_ordered_libraries.py
-../../gn_helpers.py
-util/__init__.py
-util/build_utils.py
-util/md5_check.py
-write_ordered_libraries.py
diff --git a/build/android/incremental_install/generate_android_manifest.pydeps b/build/android/incremental_install/generate_android_manifest.pydeps
deleted file mode 100644
index de92fafa..0000000
--- a/build/android/incremental_install/generate_android_manifest.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/incremental_install --output build/android/incremental_install/generate_android_manifest.pydeps build/android/incremental_install/generate_android_manifest.py
-../../gn_helpers.py
-../gyp/util/__init__.py
-../gyp/util/build_utils.py
-../gyp/util/md5_check.py
-generate_android_manifest.py
diff --git a/build/android/incremental_install/write_installer_json.py b/build/android/incremental_install/write_installer_json.py
index 75bd6d1..b7e4e77 100755
--- a/build/android/incremental_install/write_installer_json.py
+++ b/build/android/incremental_install/write_installer_json.py
@@ -19,6 +19,7 @@
 def _ParseArgs(args):
   args = build_utils.ExpandFileArgs(args)
   parser = argparse.ArgumentParser()
+  build_utils.AddDepfileOption(parser)
   parser.add_argument('--output-path',
                       help='Output path for .json file.',
                       required=True)
@@ -75,6 +76,9 @@
   with build_utils.AtomicOutput(options.output_path) as f:
     json.dump(data, f, indent=2, sort_keys=True)
 
+  if options.depfile:
+    build_utils.WriteDepfile(options.depfile, options.output_path)
+
 
 if __name__ == '__main__':
   main(sys.argv[1:])
diff --git a/build/android/incremental_install/write_installer_json.pydeps b/build/android/incremental_install/write_installer_json.pydeps
deleted file mode 100644
index 851e6c5..0000000
--- a/build/android/incremental_install/write_installer_json.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/incremental_install --output build/android/incremental_install/write_installer_json.pydeps build/android/incremental_install/write_installer_json.py
-../../gn_helpers.py
-../gyp/util/__init__.py
-../gyp/util/build_utils.py
-../gyp/util/md5_check.py
-write_installer_json.py
diff --git a/build/check_gn_headers_whitelist.txt b/build/check_gn_headers_whitelist.txt
index b655bfd..d570a03f 100644
--- a/build/check_gn_headers_whitelist.txt
+++ b/build/check_gn_headers_whitelist.txt
@@ -60,6 +60,7 @@
 chrome/browser/notifications/displayed_notifications_dispatch_callback.h
 chrome/browser/permissions/permission_queue_controller.h
 chrome/browser/prefs/active_profile_pref_service.h
+chrome/browser/rlz/chrome_rlz_tracker_delegate.h
 chrome/browser/ui/android/content_settings/subresource_filter_infobar_delegate.h
 chrome/browser/ui/app_icon_loader_delegate.h
 chrome/browser/ui/app_list/app_list_syncable_service_factory.h
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index a50c25f..2317a6e1 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -6,8 +6,8 @@
 # Some projects (e.g. V8) do not have non-build directories DEPS'ed in.
 import("//build_overrides/build.gni")
 import("//build/config/android/config.gni")
+import("//build/config/compute_inputs_for_analyze.gni")
 import("//build/config/dcheck_always_on.gni")
-import("//build/config/python.gni")
 import("//build/config/sanitizers/sanitizers.gni")
 
 assert(is_android)
@@ -45,6 +45,14 @@
 
 _default_proguard_jar_path = "//third_party/proguard/lib/proguard.jar"
 
+# List of .py files required when scripts import build_utils.py.
+# Use this for "inputs" for actions that rely on build_utils.py.
+build_utils_py = [
+  "//build/android/gyp/util/build_utils.py",
+  "//build/android/gyp/util/md5_check.py",
+  "//build/gn_helpers.py",
+]
+
 # Write the target's .build_config file. This is a json file that contains a
 # dictionary of information about how to build this target (things that
 # require knowledge about this target's dependencies and cannot be calculated
@@ -83,7 +91,7 @@
     sources = []
   }
 
-  action_with_pydeps(target_name) {
+  action(target_name) {
     forward_variables_from(invoker,
                            [
                              "deps",
@@ -98,7 +106,7 @@
 
     script = "//build/android/gyp/write_build_config.py"
     depfile = "$target_gen_dir/$target_name.d"
-    inputs = []
+    inputs = build_utils_py
     outputs = [
       invoker.build_config,
     ]
@@ -430,7 +438,7 @@
 #
 template("copy_ex") {
   set_sources_assignment_filter([])
-  action_with_pydeps(target_name) {
+  action(target_name) {
     forward_variables_from(invoker,
                            [
                              "data",
@@ -443,8 +451,9 @@
     if (defined(invoker.sources)) {
       sources += invoker.sources
     }
+    inputs = build_utils_py
     if (defined(invoker.inputs)) {
-      inputs = invoker.inputs
+      inputs += invoker.inputs
     }
 
     script = "//build/android/gyp/copy_ex.py"
@@ -510,7 +519,7 @@
     }
   }
 
-  action_with_pydeps(target_name) {
+  action(target_name) {
     forward_variables_from(invoker,
                            [
                              "data_deps",
@@ -524,6 +533,7 @@
     }
 
     script = "//build/android/gyp/create_test_runner_script.py"
+    inputs = build_utils_py
 
     data_deps += [
       "//build/android:test_runner_py",
@@ -715,12 +725,15 @@
 }
 
 template("stack_script") {
-  action_with_pydeps(target_name) {
+  forward_variables_from(invoker, [ "testonly" ])
+
+  _stack_target_name = invoker.stack_target_name
+
+  action(target_name) {
     forward_variables_from(invoker,
                            [
                              "data_deps",
                              "deps",
-                             "testonly",
                            ])
     if (!defined(deps)) {
       deps = []
@@ -733,8 +746,8 @@
         [ "//third_party/android_platform/development/scripts:stack_py" ]
 
     script = "//build/android/gyp/create_stack_script.py"
+    inputs = build_utils_py
 
-    _stack_target_name = invoker.stack_target_name
     _stack_script = "//third_party/android_platform/development/scripts/stack"
 
     _generated_script = "$root_build_dir/bin/stack_${_stack_target_name}"
@@ -770,7 +783,7 @@
   android_default_aapt2_path = "$android_sdk_build_tools/aapt2"
 
   template("android_lint") {
-    action_with_pydeps(target_name) {
+    action(target_name) {
       forward_variables_from(invoker,
                              [
                                "deps",
@@ -798,10 +811,10 @@
 
       script = "//build/android/gyp/lint.py"
       depfile = "$target_gen_dir/$target_name.d"
-      inputs = [
-        _platform_xml_path,
-        _suppressions_file,
-      ]
+      inputs = build_utils_py + [
+                 _platform_xml_path,
+                 _suppressions_file,
+               ]
 
       outputs = [
         _result_path,
@@ -878,7 +891,7 @@
   }
 
   template("proguard") {
-    action_with_pydeps(target_name) {
+    action(target_name) {
       set_sources_assignment_filter([])
       forward_variables_from(invoker,
                              [
@@ -899,10 +912,11 @@
         _proguard_jar_path = invoker.proguard_jar_path
       }
 
-      inputs = [
-        _proguard_jar_path,
-        invoker.build_config,
-      ]
+      inputs = build_utils_py + [
+                 "//build/android/gyp/util/proguard_util.py",
+                 _proguard_jar_path,
+                 invoker.build_config,
+               ]
       if (defined(invoker.inputs)) {
         inputs += invoker.inputs
       }
@@ -952,7 +966,7 @@
   #     class path when the script will invoke javac.
   #
   template("java_binary_script") {
-    action_with_pydeps(target_name) {
+    action(target_name) {
       forward_variables_from(invoker,
                              [
                                "deps",
@@ -964,9 +978,7 @@
       _script_name = invoker.script_name
 
       script = "//build/android/gyp/create_java_binary_script.py"
-      inputs = [
-        _build_config,
-      ]
+      inputs = build_utils_py + [ _build_config ]
       _java_script = "$root_build_dir/bin/$_script_name"
       outputs = [
         _java_script,
@@ -1011,7 +1023,7 @@
     if (_enable_multidex) {
       _main_dex_list_path = invoker.output + ".main_dex_list"
       _main_dex_list_target_name = "${target_name}__main_dex_list"
-      action_with_pydeps(_main_dex_list_target_name) {
+      action(_main_dex_list_target_name) {
         forward_variables_from(invoker,
                                [
                                  "deps",
@@ -1034,12 +1046,13 @@
 
         _shrinked_android = "$android_sdk_build_tools/lib/shrinkedAndroid.jar"
         _dx = "$android_sdk_build_tools/lib/dx.jar"
-        inputs = [
-          main_dex_rules,
-          _dx,
-          _proguard_jar_path,
-          _shrinked_android,
-        ]
+        inputs = build_utils_py + [
+                   "//build/android/gyp/util/proguard_util.py",
+                   main_dex_rules,
+                   _dx,
+                   _proguard_jar_path,
+                   _shrinked_android,
+                 ]
 
         outputs = [
           _main_dex_list_path,
@@ -1086,7 +1099,7 @@
     }
 
     assert(defined(invoker.output))
-    action_with_pydeps(target_name) {
+    action(target_name) {
       forward_variables_from(invoker,
                              [
                                "deps",
@@ -1094,7 +1107,7 @@
                              ])
       script = "//build/android/gyp/dex.py"
       depfile = "$target_gen_dir/$target_name.d"
-      inputs = []
+      inputs = build_utils_py
       outputs = [
         invoker.output,
       ]
@@ -1142,7 +1155,7 @@
   }
 
   template("emma_instr") {
-    action_with_pydeps(target_name) {
+    action(target_name) {
       forward_variables_from(invoker,
                              [
                                "deps",
@@ -1155,7 +1168,7 @@
       _emma_jar = "${android_sdk_root}/tools/lib/emma.jar"
 
       script = "//build/android/gyp/emma_instr.py"
-      inputs = invoker.java_files + [
+      inputs = build_utils_py + invoker.java_files + [
                  _emma_jar,
                  invoker.input_jar_path,
                ]
@@ -1263,7 +1276,7 @@
       _java_bytecode_rewriter_output_jar =
           "$target_out_dir/$target_name-bytecode-rewritten.jar"
 
-      action_with_pydeps(_java_bytecode_rewriter_target) {
+      action(_java_bytecode_rewriter_target) {
         script = "//build/android/gyp/bytecode_processor.py"
         _bytecode_rewriter_script =
             "$root_build_dir/bin/helper/java_bytecode_rewriter"
@@ -1314,7 +1327,7 @@
       _desugar_input_jar = _previous_output_jar
       _desugar_output_jar = "$target_out_dir/$target_name-desugar.jar"
 
-      action_with_pydeps(_desugar_target) {
+      action(_desugar_target) {
         script = "//build/android/gyp/desugar.py"
         deps = _deps
         if (defined(invoker.deps)) {
@@ -1322,11 +1335,11 @@
         }
         _desugar_jar = "//third_party/bazel/desugar/Desugar.jar"
 
-        inputs = [
-          _build_config,
-          _desugar_input_jar,
-          _desugar_jar,
-        ]
+        inputs = build_utils_py + [
+                   _build_config,
+                   _desugar_input_jar,
+                   _desugar_jar,
+                 ]
         outputs = [
           _desugar_output_jar,
         ]
@@ -1352,16 +1365,16 @@
       _filter_input_jar = _previous_output_jar
       _filter_output_jar = "$target_out_dir/$target_name-filtered.jar"
 
-      action_with_pydeps(_filter_target) {
+      action(_filter_target) {
         script = "//build/android/gyp/filter_zip.py"
         deps = _deps
         if (defined(invoker.deps)) {
           deps += invoker.deps
         }
-        inputs = [
-          _build_config,
-          _filter_input_jar,
-        ]
+        inputs = build_utils_py + [
+                   _build_config,
+                   _filter_input_jar,
+                 ]
         outputs = [
           _filter_output_jar,
         ]
@@ -1443,7 +1456,7 @@
   }
 
   template("merge_manifests") {
-    action_with_pydeps(target_name) {
+    action(target_name) {
       forward_variables_from(invoker,
                              [
                                "deps",
@@ -1452,10 +1465,10 @@
       script = "//build/android/gyp/merge_manifest.py"
       depfile = "$target_gen_dir/$target_name.d"
 
-      inputs = [
-        invoker.build_config,
-        invoker.input_manifest,
-      ]
+      inputs = build_utils_py + [
+                 invoker.build_config,
+                 invoker.input_manifest,
+               ]
 
       outputs = [
         invoker.output_manifest,
@@ -1477,6 +1490,19 @@
     }
   }
 
+  if (compute_inputs_for_analyze) {
+    _prepare_resources_py =
+        exec_script("//build/print_python_deps.py",
+                    [
+                      rebase_path("//build/android/gyp/prepare_resources.py"),
+                      "--no-header",
+                      "--gn-paths",
+                      "--root",
+                      rebase_path("//", root_build_dir),
+                    ],
+                    "list lines")
+  }
+
   # This template is used to parse a set of resource directories and
   # create the R.txt, .srcjar and .resources.zip for it.
   #
@@ -1532,7 +1558,7 @@
     if (defined(invoker.srcjar_path)) {
       _srcjar_path = invoker.srcjar_path
     }
-    action_with_pydeps(target_name) {
+    action(target_name) {
       set_sources_assignment_filter([])
       forward_variables_from(invoker,
                              [
@@ -1573,6 +1599,9 @@
         invoker.build_config,
         _android_aapt_path,
       ]
+      if (compute_inputs_for_analyze) {
+        inputs += _prepare_resources_py
+      }
 
       _rebased_all_resource_dirs =
           rebase_path(_all_resource_dirs, root_build_dir)
@@ -1657,6 +1686,19 @@
     }
   }
 
+  if (compute_inputs_for_analyze) {
+    _compile_resources_py =
+        exec_script("//build/print_python_deps.py",
+                    [
+                      rebase_path("//build/android/gyp/compile_resources.py"),
+                      "--no-header",
+                      "--gn-paths",
+                      "--root",
+                      rebase_path("//", root_build_dir),
+                    ],
+                    "list lines")
+  }
+
   # A template that is used to compile all resources needed by a binary
   # (e.g. an android_apk or a junit_binary) into an intermediate .ar_
   # archive. It can also generate an associated .srcjar that contains the
@@ -1759,7 +1801,7 @@
     #       _2.d for the optional processed compiled resources.
     #       _3.d for the proto-compiled resources.
 
-    action_with_pydeps(_compile_resources_target_name) {
+    action(_compile_resources_target_name) {
       set_sources_assignment_filter([])
       forward_variables_from(invoker,
                              [
@@ -1782,6 +1824,9 @@
         invoker.build_config,
         _android_aapt_path,
       ]
+      if (compute_inputs_for_analyze) {
+        inputs += _compile_resources_py
+      }
 
       _rebased_build_config = rebase_path(invoker.build_config, root_build_dir)
 
@@ -2003,16 +2048,17 @@
     _output = invoker.output
     _build_config = invoker.apk_build_config
     _rebased_build_config = rebase_path(_build_config, root_build_dir)
-    action_with_pydeps(target_name) {
+    action(target_name) {
       forward_variables_from(invoker,
                              [
                                "testonly",
                                "deps",
                              ])
       script = "//build/android/gyp/merge_jar_info_files.py"
-      inputs = [
-        _build_config,
-      ]
+      inputs = build_utils_py + [
+                 _build_config,
+                 "//build/android/gyp/util/jar_info_utils.py",
+               ]
       outputs = [
         _output,
       ]
@@ -2058,7 +2104,7 @@
   #   uncompress_shared_libraries: (optional, default false) Whether to store
   #     native libraries inside the APK uncompressed and page-aligned.
   template("package_apk") {
-    action_with_pydeps(target_name) {
+    action(target_name) {
       forward_variables_from(invoker,
                              [
                                "deps",
@@ -2083,7 +2129,8 @@
         "//tools/android/md5sum",
       ]  # Used when deploying APKs
 
-      inputs = invoker.native_libs + [
+      inputs = build_utils_py + invoker.native_libs + [
+                 "//build/android/gyp/finalize_apk.py",
                  invoker.keystore_path,
                  invoker.packaged_resources_path,
                  _apksigner,
@@ -2285,14 +2332,16 @@
       _rebased_build_config =
           rebase_path(invoker.assets_build_config, root_build_dir)
 
-      action_with_pydeps(_incremental_compile_resources_target_name) {
+      action(_incremental_compile_resources_target_name) {
         deps = _incremental_deps
         script =
             "//build/android/incremental_install/generate_android_manifest.py"
-        inputs = [
-          _android_manifest,
-          invoker.packaged_resources_path,
-        ]
+        inputs = build_utils_py + [
+                   # Save on a depfile by listing only .py dep here.
+                   "//build/android/gyp/util/build_utils.py",
+                   _android_manifest,
+                   invoker.packaged_resources_path,
+                 ]
         outputs = [
           # Output the non-compiled manifest for easy debugging (as opposed to
           # generating to a temp file).
@@ -2357,6 +2406,18 @@
     }
   }
 
+  if (compute_inputs_for_analyze) {
+    _javac_py = exec_script("//build/print_python_deps.py",
+                            [
+                              rebase_path("//build/android/gyp/javac.py"),
+                              "--no-header",
+                              "--gn-paths",
+                              "--root",
+                              rebase_path("//", root_build_dir),
+                            ],
+                            "list lines")
+  }
+
   # Compile Java source files into a .jar file, potentially using an
   # annotation processor, and/or the errorprone compiler.
   #
@@ -2465,7 +2526,7 @@
       _javac_args = invoker.javac_args
     }
 
-    action_with_pydeps(target_name) {
+    action(target_name) {
       script = "//build/android/gyp/javac.py"
       depfile = "$target_gen_dir/$target_name.d"
       deps = _srcjar_deps
@@ -2482,6 +2543,9 @@
       if (invoker.java_files != []) {
         inputs += [ invoker.java_sources_file ]
       }
+      if (compute_inputs_for_analyze) {
+        inputs += _javac_py
+      }
 
       _rebased_build_config = rebase_path(_build_config, root_build_dir)
       _rebased_javac_jar_path =
@@ -2554,7 +2618,7 @@
   #   output_jar: Path to output .ijar.
   #
   template("generate_interface_jar") {
-    action_with_pydeps(target_name) {
+    action(target_name) {
       _ijar_target = "//third_party/ijar:ijar($host_toolchain)"
       _ijar_executable = get_label_info(_ijar_target, "root_out_dir") + "/ijar"
       forward_variables_from(invoker,
@@ -2572,10 +2636,10 @@
       if (defined(invoker.deps)) {
         deps += invoker.deps
       }
-      inputs = [
-        invoker.input_jar,
-        _ijar_executable,
-      ]
+      inputs = build_utils_py + [
+                 invoker.input_jar,
+                 _ijar_executable,
+               ]
       if (defined(invoker.inputs)) {
         inputs += invoker.inputs
       }
@@ -3210,7 +3274,7 @@
   _build_config = invoker.build_config
   _rebased_build_config = rebase_path(_build_config, root_build_dir)
 
-  action_with_pydeps(target_name) {
+  action(target_name) {
     forward_variables_from(invoker,
                            [
                              "testonly",
@@ -3227,9 +3291,7 @@
     #       dependencies like extra native libraries are all pulled from the
     #       .build_config through @FileArg() references (see below) and
     #       will be listed in the generated depfile instead.
-    inputs = [
-      _build_config,
-    ]
+    inputs = build_utils_py + [ _build_config ]
     outputs = [
       invoker.module_zip_path,
     ]
@@ -3275,9 +3337,10 @@
 template("generate_proguarded_module_jar") {
   _rebased_build_config = rebase_path(invoker.build_config, root_build_dir)
 
-  action_with_pydeps(target_name) {
+  action(target_name) {
     forward_variables_from(invoker, [ "deps" ])
     script = "//build/android/gyp/generate_proguarded_module_jar.py"
+    inputs = build_utils_py
     outputs = [
       invoker.output_jar,
     ]
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index 6844713..cfec1fb 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -8,8 +8,8 @@
 import("//build/config/android/internal_rules.gni")
 import("//build/config/clang/clang.gni")
 import("//build/config/compiler/compiler.gni")
+import("//build/config/compute_inputs_for_analyze.gni")
 import("//build/config/dcheck_always_on.gni")
-import("//build/config/python.gni")
 import("//build/config/zip.gni")
 import("//build/toolchain/toolchain.gni")
 
@@ -76,7 +76,7 @@
   _find_deps_target_name = "${target_name}__find_library_dependencies"
 
   # TODO(agrieve): Extract dependent libs from GN rather than readelf.
-  action_with_pydeps(_find_deps_target_name) {
+  action(_find_deps_target_name) {
     deps = invoker.deps + [ ":$_runtime_deps_target_name" ]
     script = "//build/android/gyp/write_ordered_libraries.py"
     depfile = "$target_gen_dir/$target_name.d"
@@ -157,7 +157,7 @@
 #     target = "//pkg/foo.py"
 #   }
 template("wrapper_script") {
-  action_with_pydeps(target_name) {
+  action(target_name) {
     _name = get_path_info(invoker.target, "name")
     _output = "$root_out_dir/bin/$_name"
 
@@ -233,14 +233,17 @@
     }
 
     _foreach_target_name = "${target_name}__jni_gen"
-    action_foreach_with_pydeps(_foreach_target_name) {
+    action_foreach(_foreach_target_name) {
       script = "//base/android/jni_generator/jni_generator.py"
+      depfile = "$target_gen_dir/$target_name.{{source_name_part}}.d"
       sources = invoker.sources
       outputs = [
         "${_jni_output_dir}/{{source_name_part}}_jni.h",
       ]
 
       args = [
+        "--depfile",
+        rebase_path(depfile, root_build_dir),
         "--input_file={{source}}",
         "--ptr_type=long",
         "--output_dir",
@@ -335,9 +338,10 @@
       _classname = get_path_info(_class, "name")
       _jni_target_name = "${target_name}__jni_${_classname}"
       _jni_actions += [ ":$_jni_target_name" ]
-      action_with_pydeps(_jni_target_name) {
+      action(_jni_target_name) {
         # The sources aren't compiled so don't check their dependencies.
         check_includes = false
+        depfile = "$target_gen_dir/$target_name.d"
         script = "//base/android/jni_generator/jni_generator.py"
         inputs = [
           _jar_file,
@@ -347,6 +351,8 @@
         ]
 
         args = [
+          "--depfile",
+          rebase_path(depfile, root_build_dir),
           "--jar_file",
           rebase_path(_jar_file, root_build_dir),
           "--input_file",
@@ -407,7 +413,7 @@
   #     ]
   #   }
   template("generate_jni_registration") {
-    action_with_pydeps(target_name) {
+    action(target_name) {
       forward_variables_from(invoker, [ "testonly" ])
       _build_config = get_label_info(invoker.target, "target_gen_dir") + "/" +
                       get_label_info(invoker.target, "name") + ".build_config"
@@ -488,7 +494,7 @@
     _base_gen_dir = "${target_gen_dir}/${target_name}/java_cpp_template"
     _package_path = invoker.package_path
 
-    action_foreach_with_pydeps(_apply_gcc_target_name) {
+    action_foreach(_apply_gcc_target_name) {
       forward_variables_from(invoker,
                              [
                                "deps",
@@ -562,7 +568,7 @@
   #   }
   template("java_cpp_enum") {
     set_sources_assignment_filter([])
-    action_with_pydeps(target_name) {
+    action(target_name) {
       forward_variables_from(invoker,
                              [
                                "sources",
@@ -590,6 +596,19 @@
     }
   }
 
+  if (compute_inputs_for_analyze) {
+    _jinja_template_pydeps =
+        exec_script("//build/print_python_deps.py",
+                    [
+                      rebase_path("//build/android/gyp/jinja_template.py"),
+                      "--no-header",
+                      "--gn-paths",
+                      "--root",
+                      rebase_path("//", root_build_dir),
+                    ],
+                    "list lines")
+  }
+
   # Declare a target for processing a Jinja template.
   #
   # Variables
@@ -605,20 +624,26 @@
   #     output = "$target_gen_dir/AndroidManifest.xml"
   #   }
   template("jinja_template") {
-    action_with_pydeps(target_name) {
+    forward_variables_from(invoker, [ "testonly" ])
+
+    action(target_name) {
       forward_variables_from(invoker,
                              [
                                "visibility",
                                "deps",
-                               "testonly",
                              ])
+
       inputs = [
         invoker.input,
       ]
       if (defined(invoker.includes)) {
         inputs += invoker.includes
       }
+      if (compute_inputs_for_analyze) {
+        inputs += _jinja_template_pydeps
+      }
       script = "//build/android/gyp/jinja_template.py"
+      depfile = "$target_gen_dir/$target_name.d"
 
       outputs = [
         invoker.output,
@@ -631,6 +656,8 @@
         rebase_path(invoker.input, root_build_dir),
         "--output",
         rebase_path(invoker.output, root_build_dir),
+        "--depfile",
+        rebase_path(depfile, root_build_dir),
       ]
       if (defined(invoker.includes)) {
         _rebased_includes = rebase_path(invoker.includes, root_build_dir)
@@ -726,6 +753,8 @@
   #     variables = ["color=red"]
   #   }
   template("jinja_template_resources") {
+    forward_variables_from(invoker, [ "testonly" ])
+
     # JUnit tests use resource zip files. These must not be put in gen/
     # directory or they will not be available to tester bots.
     _resources_zip_rebased_path = rebase_path(target_gen_dir, root_gen_dir)
@@ -733,14 +762,11 @@
 
     _generating_target_name = "${target_name}__template"
 
-    action_with_pydeps(_generating_target_name) {
-      forward_variables_from(invoker,
-                             [
-                               "deps",
-                               "testonly",
-                             ])
+    action(_generating_target_name) {
+      forward_variables_from(invoker, [ "deps" ])
       inputs = invoker.resources
       script = "//build/android/gyp/jinja_template.py"
+      depfile = "$target_gen_dir/$target_name.d"
 
       outputs = [
         _resources_zip,
@@ -753,6 +779,8 @@
         rebase_path(invoker.res_dir, root_build_dir),
         "--outputs-zip",
         rebase_path(_resources_zip, root_build_dir),
+        "--depfile",
+        rebase_path(depfile, root_build_dir),
       ]
       if (defined(invoker.variables)) {
         variables = invoker.variables
@@ -761,11 +789,7 @@
     }
 
     android_generated_resources(target_name) {
-      forward_variables_from(invoker,
-                             [
-                               "deps",
-                               "testonly",
-                             ])
+      forward_variables_from(invoker, [ "deps" ])
       generating_target_name = ":$_generating_target_name"
       generated_resources_zip = _resources_zip
     }
@@ -1513,7 +1537,7 @@
         }
       }
     } else {
-      action_with_pydeps(_jar_target_name) {
+      action(_jar_target_name) {
         forward_variables_from(invoker, [ "data" ])
         script = "//build/android/gyp/create_dist_jar.py"
         depfile = "$target_gen_dir/$target_name.d"
@@ -1621,7 +1645,7 @@
 
     _rebased_build_config = rebase_path(_build_config, root_build_dir)
 
-    action_with_pydeps(target_name) {
+    action(target_name) {
       forward_variables_from(invoker, [ "data" ])
       depfile = "$target_gen_dir/$target_name.d"
       deps = _deps
@@ -2124,6 +2148,13 @@
       version_code = _version_code
       version_name = _version_name
 
+      # Subtle: required to avoid GN build errors. "testonly" cannot be added
+      # to the forward_variables_from() above because it was already forwarded
+      # at the start of android_apk(). And if the assignment below is not
+      # performed, GN will complain that some test-only targets depend
+      # on non-test-only ones.
+      testonly = defined(testonly) && testonly
+
       if (defined(invoker.post_process_package_resources_script)) {
         post_process_script = invoker.post_process_package_resources_script
       }
@@ -2172,7 +2203,7 @@
 
       # TODO(agrieve): Make GN write runtime deps in dependency order so as to
       # not need this manual sorting step.
-      action_with_pydeps(_ordered_libraries_target) {
+      action(_ordered_libraries_target) {
         script = "//build/android/gyp/write_ordered_libraries.py"
         deps = [
           ":$_build_config_target",
@@ -2589,8 +2620,9 @@
     }
 
     _write_installer_json_rule_name = "${_template_name}__incremental_json"
-    action_with_pydeps(_write_installer_json_rule_name) {
+    action(_write_installer_json_rule_name) {
       script = "//build/android/incremental_install/write_installer_json.py"
+      depfile = "$target_gen_dir/$target_name.d"
       deps = [
         _native_libs_file_arg_dep,
       ]
@@ -2603,12 +2635,14 @@
           rebase_path(_final_apk_path_no_ext, root_build_dir)
       _rebased_incremental_install_json_path =
           rebase_path(_incremental_install_json_path, root_build_dir)
+      _rebased_depfile = rebase_path(depfile, root_build_dir)
       _dex_arg_key = "${_rebased_build_config}:final_dex:dependency_dex_files"
       args = [
         "--apk-path=${_rebased_apk_path_no_ext}_incremental.apk",
         "--output-path=$_rebased_incremental_install_json_path",
         "--dex-file=$_rebased_lib_dex_path",
         "--dex-file-list=@FileArg($_dex_arg_key)",
+        "--depfile=$_rebased_depfile",
       ]
       if (_proguard_enabled) {
         args += [ "--show-proguard-warning" ]
@@ -2634,7 +2668,7 @@
     # Generate apk operation related script.
     if (!defined(invoker.create_apk_script) || invoker.create_apk_script) {
       _apk_operations_target_name = "${target_name}__apk_operations"
-      action_with_pydeps(_apk_operations_target_name) {
+      action(_apk_operations_target_name) {
         _generated_script = "$root_build_dir/bin/${invoker.target_name}"
         script = "//build/android/gyp/create_apk_operations_script.py"
         outputs = [
@@ -3209,7 +3243,7 @@
   #     ]
   #   }
   template("android_aidl") {
-    action_with_pydeps(target_name) {
+    action(target_name) {
       set_sources_assignment_filter([])
       forward_variables_from(invoker, [ "testonly" ])
 
@@ -3227,11 +3261,14 @@
 
       inputs = [ _aidl_path ] + _imports
 
+      depfile = "${target_gen_dir}/${target_name}.d"
       outputs = [
         _srcjar_path,
       ]
       _rebased_imports = rebase_path(_imports, root_build_dir)
       args = [
+        "--depfile",
+        rebase_path(depfile, root_build_dir),
         "--aidl-path",
         rebase_path(_aidl_path, root_build_dir),
         "--imports=$_rebased_imports",
@@ -3304,7 +3341,7 @@
     _proto_path = invoker.proto_path
     _template_name = target_name
 
-    action_with_pydeps("${_template_name}__protoc_java") {
+    action("${_template_name}__protoc_java") {
       _srcjar_path = "$target_gen_dir/$target_name.srcjar"
       script = "//build/protoc_java.py"
 
@@ -3417,7 +3454,7 @@
                " Use ignore_native_libraries = true to silence this error.")
     assert(_scanned_files.has_classes_jar || _scanned_files.subjars == [])
 
-    action_with_pydeps(_unpack_target_name) {
+    action(_unpack_target_name) {
       script = "//build/android/gyp/aar.py"  # Unzips the AAR
       args = [
         "extract",
@@ -3818,10 +3855,18 @@
       _bundle_keystore_name = _keystore_name
     }
 
+    # NOTE: Keep this consistent with the imports of create_app_bundle.py
+    _create_app_bundle_py_imports =
+        build_utils_py + [
+          "//build/android/gyp/util/resource_utils.py",
+          "//build/android/gyp/bundletool.py",
+        ]
+
     _bundle_target_name = "${target_name}__bundle"
-    action_with_pydeps(_bundle_target_name) {
+    action(_bundle_target_name) {
       script = "//build/android/gyp/create_app_bundle.py"
-      inputs = _all_module_zip_paths + _all_module_build_configs
+      inputs = _all_module_zip_paths + _all_module_build_configs +
+               _create_app_bundle_py_imports
       outputs = [
         _bundle_path,
       ]
@@ -3867,12 +3912,12 @@
     _rebased_base_module_build_config =
         rebase_path(_base_module_build_config, root_build_dir)
 
-    action_with_pydeps("${target_name}__wrapper_script") {
+    action("${target_name}__wrapper_script") {
       script = "//build/android/gyp/create_bundle_wrapper_script.py"
-      inputs = [
-        "//build/android/gyp/bundletool.py",
-        _base_module_build_config,
-      ]
+      inputs = build_utils_py + [
+                 "//build/android/gyp/bundletool.py",
+                 _base_module_build_config,
+               ]
       outputs = [
         _bundle_wrapper_script_path,
       ]
diff --git a/build/config/python.gni b/build/config/python.gni
index 4e71e87..e24025f6 100644
--- a/build/config/python.gni
+++ b/build/config/python.gni
@@ -59,108 +59,3 @@
     }
   }
 }
-
-# A template used for actions that execute a Python script, which has an
-# associated .pydeps file. In other words:
-#
-# - This is very similar to just an action(), except that |script| must point
-#   to a Python script (e.g. "//build/.../foo.py") that has a corresponding
-#   .pydeps file in the source tree (e.g. "//build/.../foo.pydeps").
-#
-# - The .pydeps file contains a list of python dependencies (imports really)
-#   and is generated _manually_ by using a command like:
-#
-#     build/print_python_deps.py --inplace build/android/gyp/foo.py
-#
-template("action_with_pydeps") {
-  # Read the .pydeps file now. Note that this is done everytime this
-  # template is called, but benchmarking doesn't show any impact on overall
-  # 'gn gen' speed anyway.
-  _pydeps_file = invoker.script + "deps"
-  _pydeps_raw = read_file(_pydeps_file, "list lines")
-
-  # Filter out comments.
-  # This is a bit convoluted to preserve the value of sources if defined.
-  _old_sources = []
-  if (defined(sources)) {
-    _old_sources = sources
-  }
-  set_sources_assignment_filter([ "#*" ])
-  sources = _pydeps_raw
-  _pydeps = sources
-  set_sources_assignment_filter([])
-  sources = _old_sources
-
-  action(target_name) {
-    # Forward all variables. Ensure that testonly and visibility are forwarded
-    # explicitly, since this performs recursive scope lookups, which is
-    # required to ensure their definition from scopes above the caller are
-    # properly handled. All other variables are forwarded with "*", which
-    # doesn't perform recursive lookups at all. See https://crbug.com/862232
-    forward_variables_from(invoker,
-                           [
-                             "testonly",
-                             "visibility",
-                           ])
-    forward_variables_from(invoker,
-                           "*",
-                           [
-                             "testonly",
-                             "visibility",
-                           ])
-
-    if (!defined(inputs)) {
-      inputs = []
-    }
-
-    # Dependencies are listed relative to the script directory, but inputs
-    # expects paths that are relative to the current BUILD.gn
-    _script_dir = get_path_info(script, "dir")
-    inputs += rebase_path(_pydeps, ".", _script_dir)
-  }
-}
-
-template("action_foreach_with_pydeps") {
-  _pydeps_file = invoker.script + "deps"
-  _pydeps_raw = read_file(_pydeps_file, "list lines")
-
-  # Filter out comments.
-  # This is a bit convoluted to preserve the value of sources if defined.
-  _old_sources = []
-  if (defined(sources)) {
-    _old_sources = sources
-  }
-  set_sources_assignment_filter([ "#*" ])
-  sources = _pydeps_raw
-  _pydeps = sources
-  set_sources_assignment_filter([])
-  sources = _old_sources
-
-  action_foreach(target_name) {
-    # Forward all variables. Ensure that testonly and visibility are forwarded
-    # explicitly, since this performs recursive scope lookups, which is
-    # required to ensure their definition from scopes above the caller are
-    # properly handled. All other variables are forwarded with "*", which
-    # doesn't perform recursive lookups at all. See https://crbug.com/862232
-    forward_variables_from(invoker,
-                           [
-                             "testonly",
-                             "visibility",
-                           ])
-    forward_variables_from(invoker,
-                           "*",
-                           [
-                             "testonly",
-                             "visibility",
-                           ])
-
-    if (!defined(inputs)) {
-      inputs = []
-    }
-
-    # Dependencies are listed relative to the script directory, but inputs
-    # expects paths that are relative to the current BUILD.gn
-    _script_dir = get_path_info(script, "dir")
-    inputs += rebase_path(_pydeps, ".", _script_dir)
-  }
-}
diff --git a/build/fuchsia/target.py b/build/fuchsia/target.py
index 910bdfd..00c4451 100644
--- a/build/fuchsia/target.py
+++ b/build/fuchsia/target.py
@@ -167,9 +167,10 @@
           return True
         time.sleep(_ATTACH_RETRY_INTERVAL)
     finally:
+      # Redirect logs to /dev/null. run_package.py will use SSH+dlog to get
+      # logs from the machine to console.
       if self._system_logs_reader:
-        self._system_logs_reader.Close()
-        self._system_logs_reader = None
+        self._system_logs_reader.RedirectTo(open('/dev/null', 'w'));
 
     logging.error('Timeout limit reached.')
 
diff --git a/build/print_python_deps.py b/build/print_python_deps.py
index 8ded01e..2459a28e 100755
--- a/build/print_python_deps.py
+++ b/build/print_python_deps.py
@@ -78,10 +78,6 @@
                       help='Directory to make paths relative to.')
   parser.add_argument('--output',
                       help='Write output to a file rather than stdout.')
-  parser.add_argument('--inplace', action='store_true',
-                      help='Write output to a file with the same path as the '
-                      'module, but with a .pydeps extension. Also sets the '
-                      'root to the module\'s directory.')
   parser.add_argument('--no-header', action='store_true',
                       help='Do not write the "# Generated by" header.')
   parser.add_argument('--gn-paths', action='store_true',
@@ -96,14 +92,6 @@
   sys.path[0] = os.path.dirname(options.module)
   imp.load_source('NAME', options.module)
 
-  if options.inplace:
-    if options.output:
-      parser.error('Cannot use --inplace and --output at the same time!')
-    if not options.module.endswith('.py'):
-      parser.error('Input module path should end with .py suffix!')
-    options.output = options.module + 'deps'
-    options.root = os.path.dirname(options.module)
-
   paths_set = _ComputePythonDependencies()
   for path in options.whitelists:
     paths_set.update(os.path.abspath(p) for p in _FindPythonInDirectory(path))
diff --git a/build/protoc_java.py b/build/protoc_java.py
index 5227bf9..2addb82 100755
--- a/build/protoc_java.py
+++ b/build/protoc_java.py
@@ -76,8 +76,7 @@
   if options.depfile:
     assert options.srcjar
     deps = args + [options.protoc]
-    build_utils.WriteDepfile(options.depfile, options.srcjar, deps,
-                             add_pydeps=False)
+    build_utils.WriteDepfile(options.depfile, options.srcjar, deps)
 
   if options.stamp:
     build_utils.Touch(options.stamp)
diff --git a/build/protoc_java.pydeps b/build/protoc_java.pydeps
deleted file mode 100644
index a26622b..0000000
--- a/build/protoc_java.pydeps
+++ /dev/null
@@ -1,7 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build --output build/protoc_java.pydeps build/protoc_java.py
-android/gyp/util/__init__.py
-android/gyp/util/build_utils.py
-android/gyp/util/md5_check.py
-gn_helpers.py
-protoc_java.py
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index c3ffc55..0bd8aa2 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -742,6 +742,7 @@
         "javatests/src/org/chromium/chrome/browser/vr/rules/ChromeTabbedActivityVrTestRule.java",
         "javatests/src/org/chromium/chrome/browser/vr/rules/CustomTabActivityVrTestRule.java",
         "javatests/src/org/chromium/chrome/browser/vr/rules/HeadTrackingMode.java",
+        "javatests/src/org/chromium/chrome/browser/vr/rules/VrSettingsFile.java",
         "javatests/src/org/chromium/chrome/browser/vr/rules/VrTestRule.java",
         "javatests/src/org/chromium/chrome/browser/vr/rules/WebappActivityVrTestRule.java",
         "javatests/src/org/chromium/chrome/browser/vr/util/HeadTrackingUtils.java",
@@ -749,6 +750,7 @@
         "javatests/src/org/chromium/chrome/browser/vr/util/NfcSimUtils.java",
         "javatests/src/org/chromium/chrome/browser/vr/util/VrBrowserTransitionUtils.java",
         "javatests/src/org/chromium/chrome/browser/vr/util/VrInfoBarUtils.java",
+        "javatests/src/org/chromium/chrome/browser/vr/util/VrSettingsServiceUtils.java",
         "javatests/src/org/chromium/chrome/browser/vr/util/VrShellDelegateUtils.java",
         "javatests/src/org/chromium/chrome/browser/vr/util/VrTestRuleUtils.java",
         "javatests/src/org/chromium/chrome/browser/vr/util/VrTransitionUtils.java",
diff --git a/chrome/android/java/res/layout/download_home_toolbar.xml b/chrome/android/java/res/layout/download_home_toolbar.xml
index 096912d9..df8c587 100644
--- a/chrome/android/java/res/layout/download_home_toolbar.xml
+++ b/chrome/android/java/res/layout/download_home_toolbar.xml
@@ -21,15 +21,6 @@
             style="@style/BlackHeadline2"
             android:text="@string/menu_downloads"/>
 
-        <TextView
-            android:id="@+id/download_storage_summary"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:singleLine="true"
-            android:textAppearance="@style/BlackDisabledText2"
-            android:maxLines="1"
-            android:paddingBottom="8dp"/>
-
     </LinearLayout>
 
 </org.chromium.chrome.browser.download.home.toolbar.DownloadHomeToolbar>
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/banners/AppBannerUiDelegateAndroid.java b/chrome/android/java/src/org/chromium/chrome/browser/banners/AppBannerUiDelegateAndroid.java
index 68418f2..62ddc7ec 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/banners/AppBannerUiDelegateAndroid.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/banners/AppBannerUiDelegateAndroid.java
@@ -31,6 +31,8 @@
 
     private AddToHomescreenDialog mDialog;
 
+    private boolean mAddedToHomescreen;
+
     private AppBannerUiDelegateAndroid(long nativePtr, Tab tab) {
         mNativePointer = nativePtr;
         mTab = tab;
@@ -38,24 +40,25 @@
 
     @Override
     public void addToHomescreen(String title) {
+        mAddedToHomescreen = true;
         // The title is ignored for app banners as we respect the developer-provided title.
         nativeAddToHomescreen(mNativePointer);
     }
 
     @Override
-    public void onDialogCancelled() {
-        nativeOnUiCancelled(mNativePointer);
-    }
-
-    @Override
     public void onNativeAppDetailsRequested() {
         nativeShowNativeAppDetails(mNativePointer);
     }
 
     @Override
     public void onDialogDismissed() {
+        if (!mAddedToHomescreen) {
+            nativeOnUiCancelled(mNativePointer);
+        }
+
         mDialog = null;
         mInstallerDelegate = null;
+        mAddedToHomescreen = false;
     }
 
     @Override
@@ -91,6 +94,7 @@
         }
         mInstallerDelegate = null;
         mNativePointer = 0;
+        mAddedToHomescreen = false;
     }
 
     @CalledByNative
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListCoordinator.java
index 87a4c4b..903f7c31 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListCoordinator.java
@@ -13,6 +13,7 @@
 import org.chromium.chrome.browser.download.home.filter.FilterCoordinator;
 import org.chromium.chrome.browser.download.home.filter.Filters.FilterType;
 import org.chromium.chrome.browser.download.home.list.ListItem.ViewListItem;
+import org.chromium.chrome.browser.download.home.storage.StorageCoordinator;
 import org.chromium.chrome.browser.widget.selection.SelectionDelegate;
 import org.chromium.components.offline_items_collection.OfflineContentProvider;
 import org.chromium.components.offline_items_collection.OfflineItem;
@@ -44,6 +45,7 @@
         void canDelete(List<OfflineItem> items, Callback<Boolean> callback);
     }
 
+    private final StorageCoordinator mStorageCoordinator;
     private final FilterCoordinator mFilterCoordinator;
     private final EmptyCoordinator mEmptyCoordinator;
     private final DateOrderedListMediator mMediator;
@@ -75,13 +77,18 @@
         mEmptyCoordinator =
                 new EmptyCoordinator(context, prefetchProvider, mMediator.getEmptySource());
 
+        mStorageCoordinator = new StorageCoordinator(context, mMediator.getFilterSource());
+
         mFilterCoordinator =
                 new FilterCoordinator(context, prefetchProvider, mMediator.getFilterSource());
         mFilterCoordinator.addObserver(mMediator::onFilterTypeSelected);
         mFilterCoordinator.addObserver(filterObserver);
         mFilterCoordinator.addObserver(mEmptyCoordinator);
 
-        decoratedModel.setHeader(new ViewListItem(Long.MAX_VALUE, mFilterCoordinator.getView()));
+        decoratedModel.addHeader(
+                new ViewListItem(Long.MAX_VALUE - 1L, mStorageCoordinator.getView()));
+        decoratedModel.addHeader(
+                new ViewListItem(Long.MAX_VALUE - 2L, mFilterCoordinator.getView()));
     }
 
     /** Tears down this coordinator. */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DecoratedListItemModel.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DecoratedListItemModel.java
index f26b6e7..9f1e884 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DecoratedListItemModel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DecoratedListItemModel.java
@@ -13,6 +13,9 @@
 import org.chromium.chrome.browser.modelutil.PropertyModel;
 import org.chromium.chrome.browser.modelutil.SimpleList;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * A wrapper class that adds decoration {@link ListItem}s to a {@link ListItemModel}.
  * TODO(bauerb): Replace this with InnerNode (once it has been migrated to the UI architecture)
@@ -21,8 +24,7 @@
         extends ListObservableImpl<Void> implements ListObserver<Void>, SimpleList<ListItem> {
     private final ListItemModel mModel;
 
-    private ViewListItem mHeaderItem;
-    private ViewListItem mEmptyViewItem;
+    private final List<ViewListItem> mHeaderItems = new ArrayList<>();
 
     /** Creates a {@link DecoratedListItemModel} instance that wraos {@code model}. */
     public DecoratedListItemModel(ListItemModel model) {
@@ -35,57 +37,22 @@
         return mModel.getProperties();
     }
 
-    /** Adds {@code item} as a header for the list.  Clears the header if it is {@code null}. */
-    public void setHeader(ViewListItem item) {
-        if (mHeaderItem == item) return;
-
-        ViewListItem oldHeaderItem = mHeaderItem;
-        mHeaderItem = item;
-
-        if (oldHeaderItem != null && item == null) {
-            notifyItemRemoved(0);
-        } else if (oldHeaderItem == null && item != null) {
-            notifyItemInserted(0);
-        } else {
-            notifyItemRangeChanged(0, 1);
-        }
-    }
-
-    /**
-     * Adds {@code item} as a empty view for the list.  Clears the empty view if it is {@code
-     * null}. Empty view must be the second item in the list after the header. If there is no
-     * header, it must be the first item in the list.
-     */
-    public void setEmptyView(ViewListItem item) {
-        if (mEmptyViewItem == item) return;
-
-        int index = mHeaderItem == null ? 0 : 1;
-
-        ViewListItem oldEmptyView = mEmptyViewItem;
-        mEmptyViewItem = item;
-
-        if (oldEmptyView != null && item == null) {
-            notifyItemRemoved(index);
-        } else if (oldEmptyView == null && item != null) {
-            notifyItemInserted(index);
-        } else {
-            notifyItemRangeChanged(index, 1);
-        }
+    /** Adds {@code item} as a header for the list. */
+    public void addHeader(ViewListItem item) {
+        int index = mHeaderItems.size();
+        mHeaderItems.add(item);
+        notifyItemInserted(index);
     }
 
     // SimpleList implementation.
     @Override
     public int size() {
-        return mModel.size() + (mHeaderItem == null ? 0 : 1) + (mEmptyViewItem == null ? 0 : 1);
+        return mModel.size() + mHeaderItems.size();
     }
 
     @Override
     public ListItem get(int index) {
-        if (mHeaderItem != null || mEmptyViewItem != null) {
-            if (index == 0) return mHeaderItem != null ? mHeaderItem : mEmptyViewItem;
-            if (index == 1 && mEmptyViewItem != null) return mEmptyViewItem;
-        }
-
+        if (index < mHeaderItems.size()) return mHeaderItems.get(index);
         return mModel.get(convertIndexForSource(index));
     }
 
@@ -108,12 +75,10 @@
     }
 
     private int convertIndexForSource(int index) {
-        int offset = (mHeaderItem == null ? 0 : 1) + (mEmptyViewItem == null ? 0 : 1);
-        return index - offset;
+        return index - mHeaderItems.size();
     }
 
     private int convertIndexFromSource(int index) {
-        int offset = (mHeaderItem == null ? 0 : 1) + (mEmptyViewItem == null ? 0 : 1);
-        return index + offset;
+        return index + mHeaderItems.size();
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListPropertyViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListPropertyViewBinder.java
index 9b6e411..00bcf87 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListPropertyViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListPropertyViewBinder.java
@@ -36,7 +36,7 @@
                 || propertyKey == ListProperties.PROVIDER_VISUALS
                 || propertyKey == ListProperties.CALLBACK_SELECTION
                 || propertyKey == ListProperties.SELECTION_MODE_ACTIVE) {
-            view.getAdapter().notifyItemChanged(0, view.getAdapter().getItemCount());
+            view.getAdapter().notifyItemRangeChanged(0, view.getAdapter().getItemCount());
         }
     }
 }
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/CustomViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/CustomViewHolder.java
index 782897c3..b07df5c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/CustomViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/CustomViewHolder.java
@@ -6,6 +6,7 @@
 
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewParent;
 import android.widget.FrameLayout;
 
 import org.chromium.chrome.browser.download.home.list.ListItem;
@@ -31,6 +32,9 @@
             return;
         }
 
+        ViewParent parent = viewItem.customView.getParent();
+        if (parent instanceof ViewGroup) ((ViewGroup) parent).removeView(viewItem.customView);
+
         viewGroup.removeAllViews();
         viewGroup.addView(viewItem.customView,
                 new ViewGroup.LayoutParams(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/storage/StorageCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/storage/StorageCoordinator.java
new file mode 100644
index 0000000..3dc06fe1
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/storage/StorageCoordinator.java
@@ -0,0 +1,57 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.download.home.storage;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.download.home.filter.OfflineItemFilterSource;
+import org.chromium.chrome.browser.modelutil.PropertyKey;
+import org.chromium.chrome.browser.modelutil.PropertyModel;
+import org.chromium.chrome.browser.modelutil.PropertyModel.ObjectPropertyKey;
+import org.chromium.chrome.browser.modelutil.PropertyModelChangeProcessor;
+
+/**
+ * The coordinator responsible for creating the storage summary view in download home.
+ */
+public class StorageCoordinator {
+    private final PropertyModel mModel;
+    private final TextView mView;
+    private final StorageSummaryProvider mProvider;
+
+    public StorageCoordinator(Context context, OfflineItemFilterSource filterSource) {
+        mModel = new PropertyModel(StorageProperties.ALL_KEYS);
+        mView = (TextView) LayoutInflater.from(context).inflate(
+                R.layout.download_storage_summary, null);
+        mModel.addObserver(new PropertyModelChangeProcessor<>(mModel, mView, this ::bind));
+        mProvider = new StorageSummaryProvider(context, this ::onStorageInfoUpdated, filterSource);
+    }
+
+    /** @return The {@link View} to be shown that contains the storage information. */
+    public View getView() {
+        return mView;
+    }
+
+    private void onStorageInfoUpdated(String storageInfoText) {
+        mModel.setValue(StorageProperties.STORAGE_INFO_TEXT, storageInfoText);
+    }
+
+    private void bind(PropertyModel model, TextView view, PropertyKey propertyKey) {
+        if (propertyKey == StorageProperties.STORAGE_INFO_TEXT) {
+            view.setText(model.getValue(StorageProperties.STORAGE_INFO_TEXT));
+        }
+    }
+
+    /** The properties needed to render the download home storage summary view. */
+    private static class StorageProperties {
+        /** The storage summary text to show in the content area. */
+        public static final ObjectPropertyKey<String> STORAGE_INFO_TEXT = new ObjectPropertyKey<>();
+
+        public static final PropertyKey[] ALL_KEYS = new PropertyKey[] {STORAGE_INFO_TEXT};
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/storage/StorageSummaryProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/storage/StorageSummaryProvider.java
new file mode 100644
index 0000000..11eb8dfd
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/storage/StorageSummaryProvider.java
@@ -0,0 +1,98 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.download.home.storage;
+
+import android.content.Context;
+import android.os.Environment;
+import android.support.annotation.Nullable;
+
+import org.chromium.base.AsyncTask;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.download.DirectoryOption;
+import org.chromium.chrome.browser.download.DownloadUtils;
+import org.chromium.chrome.browser.download.home.filter.OfflineItemFilterObserver;
+import org.chromium.chrome.browser.download.home.filter.OfflineItemFilterSource;
+import org.chromium.components.offline_items_collection.OfflineItem;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * Provides the storage summary text to be shown inside the download home.
+ * TODO(shaktisahu): Rename this class to StorageSummaryMediator and have it manipulate the model
+ * directly once migration to new download home is complete.
+ */
+public class StorageSummaryProvider implements OfflineItemFilterObserver {
+    /** A delegate for updating the UI about the storage information. */
+    public interface Delegate { void onStorageInfoChanged(String storageInfo); }
+
+    private final Context mContext;
+    private final Delegate mDelegate;
+
+    private DirectoryOption mDirectoryOption;
+
+    /**
+     * Asynchronous task to query the default download directory option on primary storage.
+     * Pass one String parameter as the name of the directory option.
+     */
+    private static class DefaultDirectoryTask extends AsyncTask<DirectoryOption> {
+        @Override
+        protected DirectoryOption doInBackground() {
+            File defaultDownloadDir =
+                    Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
+            DirectoryOption directoryOption =
+                    new DirectoryOption("", defaultDownloadDir.getAbsolutePath(),
+                            defaultDownloadDir.getUsableSpace(), defaultDownloadDir.getTotalSpace(),
+                            DirectoryOption.DownloadLocationDirectoryType.DEFAULT);
+            return directoryOption;
+        }
+    }
+
+    public StorageSummaryProvider(
+            Context context, Delegate delegate, @Nullable OfflineItemFilterSource filterSource) {
+        mContext = context;
+        mDelegate = delegate;
+
+        if (filterSource != null) filterSource.addObserver(this);
+        computeStorage();
+    }
+
+    // OfflineItemFilterObserver implementation.
+    @Override
+    public void onItemsAdded(Collection<OfflineItem> items) {
+        computeStorage();
+    }
+
+    @Override
+    public void onItemsRemoved(Collection<OfflineItem> items) {
+        computeStorage();
+    }
+
+    @Override
+    public void onItemUpdated(OfflineItem oldItem, OfflineItem item) {}
+
+    private void computeStorage() {
+        DefaultDirectoryTask task = new DefaultDirectoryTask() {
+            @Override
+            protected void onPostExecute(DirectoryOption directoryOption) {
+                mDirectoryOption = directoryOption;
+                update();
+            }
+        };
+        task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+    }
+
+    private void update() {
+        if (mDirectoryOption == null) return;
+
+        // Build the storage summary string.
+        long usedSpace = mDirectoryOption.totalSpace - mDirectoryOption.availableSpace;
+        if (usedSpace < 0) usedSpace = 0;
+        String storageSummary = mContext.getString(R.string.download_manager_ui_space_using,
+                DownloadUtils.getStringForBytes(mContext, usedSpace),
+                DownloadUtils.getStringForBytes(mContext, mDirectoryOption.totalSpace));
+        mDelegate.onStorageInfoChanged(storageSummary);
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/toolbar/DownloadHomeToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/toolbar/DownloadHomeToolbar.java
index 687d579..23b7c79 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/toolbar/DownloadHomeToolbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/toolbar/DownloadHomeToolbar.java
@@ -7,12 +7,10 @@
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.View;
-import android.widget.TextView;
 
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.download.home.list.ListItem;
-import org.chromium.chrome.browser.download.ui.StorageSummary;
 import org.chromium.chrome.browser.widget.selection.SelectableListToolbar;
 
 import java.util.List;
@@ -21,7 +19,6 @@
  * Handles toolbar functionality for the download home.
  */
 public class DownloadHomeToolbar extends SelectableListToolbar<ListItem> {
-    private StorageSummary mStorageSummary;
     private View mTitleBar;
 
     public DownloadHomeToolbar(Context context, AttributeSet attrs) {
@@ -33,9 +30,6 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mTitleBar = findViewById(R.id.title_bar);
-
-        TextView storageSummaryView = (TextView) findViewById(R.id.download_storage_summary);
-        mStorageSummary = new StorageSummary(storageSummaryView);
     }
 
     /**
@@ -85,4 +79,4 @@
         super.hideSearchView();
         mTitleBar.setVisibility(VISIBLE);
     }
-}
\ No newline at end of file
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadHistoryAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadHistoryAdapter.java
index 0f5e70f..193e1e3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadHistoryAdapter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadHistoryAdapter.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.download.ui;
 
 import android.content.ComponentName;
+import android.content.Context;
 import android.support.annotation.Nullable;
 import android.support.v7.widget.RecyclerView.ViewHolder;
 import android.text.TextUtils;
@@ -24,6 +25,7 @@
 import org.chromium.chrome.browser.download.DownloadManagerService.DownloadObserver;
 import org.chromium.chrome.browser.download.DownloadSharedPreferenceHelper;
 import org.chromium.chrome.browser.download.DownloadUtils;
+import org.chromium.chrome.browser.download.home.storage.StorageSummaryProvider;
 import org.chromium.chrome.browser.download.ui.BackendProvider.DownloadDelegate;
 import org.chromium.chrome.browser.download.ui.DownloadHistoryItemWrapper.DownloadItemWrapper;
 import org.chromium.chrome.browser.download.ui.DownloadHistoryItemWrapper.OfflineItemWrapper;
@@ -208,7 +210,7 @@
     private String mSearchQuery = EMPTY_QUERY;
     // TODO(xingliu): Remove deprecated storage info. See https://crbug/853260.
     private SpaceDisplay mSpaceDisplay;
-    private StorageSummary mStorageSummary;
+    private StorageSummaryProvider mStorageSummaryProvider;
     private HeaderItem mSpaceDisplayHeaderItem;
     private HeaderItem mStorageSummaryHeaderItem;
     private boolean mIsSearching;
@@ -411,20 +413,27 @@
     /**
      * Initialize space display view in storage info header and generate header item for it.
      */
-    void generateHeaderItems() {
+    private void generateHeaderItems() {
         mSpaceDisplay = new SpaceDisplay(null, this);
         View view = mSpaceDisplay.getViewContainer();
         registerAdapterDataObserver(mSpaceDisplay);
         mSpaceDisplayHeaderItem = new HeaderItem(0, view);
 
         if (ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOADS_LOCATION_CHANGE)) {
-            View storageSummaryView = LayoutInflater.from(ContextUtils.getApplicationContext())
-                                              .inflate(R.layout.download_storage_summary, null);
-            mStorageSummary = new StorageSummary((TextView) storageSummaryView);
+            Context context = ContextUtils.getApplicationContext();
+            View storageSummaryView =
+                    LayoutInflater.from(context).inflate(R.layout.download_storage_summary, null);
+            mStorageSummaryProvider =
+                    new StorageSummaryProvider(context, this ::updateStorageInfo, null);
             mStorageSummaryHeaderItem = new HeaderItem(0, storageSummaryView);
         }
     }
 
+    private void updateStorageInfo(String storageInfo) {
+        TextView storageSummaryView = (TextView) mStorageSummaryHeaderItem.getView();
+        storageSummaryView.setText(storageInfo);
+    }
+
     /** Called when a new DownloadItem has been created by the native DownloadManager. */
     @Override
     public void onDownloadItemCreated(DownloadItem item) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/StorageSummary.java b/chrome/android/java/src/org/chromium/chrome/browser/download/ui/StorageSummary.java
deleted file mode 100644
index 7ac554f..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/StorageSummary.java
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.download.ui;
-
-import android.content.Context;
-import android.os.Environment;
-import android.view.View;
-import android.widget.TextView;
-
-import org.chromium.base.AsyncTask;
-import org.chromium.chrome.R;
-import org.chromium.chrome.browser.ChromeFeatureList;
-import org.chromium.chrome.browser.download.DirectoryOption;
-import org.chromium.chrome.browser.download.DownloadUtils;
-
-import java.io.File;
-
-/**
- * The storage summary inside the download home toolbar.
- */
-public class StorageSummary {
-    private DirectoryOption mDirectoryOption;
-    private final TextView mView;
-
-    /**
-     * Asynchronous task to query the default download directory option on primary storage.
-     * Pass one String parameter as the name of the directory option.
-     */
-    private static class DefaultDirectoryTask extends AsyncTask<DirectoryOption> {
-        @Override
-        protected DirectoryOption doInBackground() {
-            File defaultDownloadDir =
-                    Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
-            DirectoryOption directoryOption =
-                    new DirectoryOption("", defaultDownloadDir.getAbsolutePath(),
-                            defaultDownloadDir.getUsableSpace(), defaultDownloadDir.getTotalSpace(),
-                            DirectoryOption.DownloadLocationDirectoryType.DEFAULT);
-            return directoryOption;
-        }
-    }
-
-    public StorageSummary(TextView view) {
-        mView = view;
-
-        if (ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOADS_LOCATION_CHANGE)) {
-            computeStorage();
-        } else {
-            mView.setVisibility(View.GONE);
-        }
-    }
-
-    private void computeStorage() {
-        DefaultDirectoryTask task = new DefaultDirectoryTask() {
-            @Override
-            protected void onPostExecute(DirectoryOption directoryOption) {
-                mDirectoryOption = directoryOption;
-                update();
-            }
-        };
-        task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
-    }
-
-    private void update() {
-        if (mDirectoryOption == null) return;
-
-        // Set the storage summary to the view.
-        Context context = mView.getContext();
-        long usedSpace = mDirectoryOption.totalSpace - mDirectoryOption.availableSpace;
-        if (usedSpace < 0) usedSpace = 0;
-        String storageSummary = context.getString(R.string.download_manager_ui_space_using,
-                DownloadUtils.getStringForBytes(context, usedSpace),
-                DownloadUtils.getStringForBytes(context, mDirectoryOption.totalSpace));
-        mView.setText(storageSummary);
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SectionList.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SectionList.java
index 8f0457c..45d1479 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SectionList.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SectionList.java
@@ -167,6 +167,12 @@
                 return;
 
             case CategoryStatus.CATEGORY_EXPLICITLY_DISABLED:
+                // Need to remove the entire section from the UI immediately. If the section is
+                // expandable, we may add the section back for displaying the header.
+                removeSection(mSections.get(category));
+                maybeAddSectionForHeader(category);
+                return;
+
             case CategoryStatus.LOADING_ERROR:
                 // Need to remove the entire section from the UI immediately.
                 removeSection(mSections.get(category));
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticleViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticleViewHolder.java
index 8c51455e..341f8b0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticleViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticleViewHolder.java
@@ -82,7 +82,7 @@
 
     @Override
     public void onCardTapped() {
-        SuggestionsMetrics.recordCardTapped();
+        if (!mArticle.isContextual()) SuggestionsMetrics.recordCardTapped();
         int windowDisposition = WindowOpenDisposition.CURRENT_TAB;
         mUiDelegate.getEventReporter().onSuggestionOpened(
                 mArticle, windowDisposition, mUiDelegate.getSuggestionsRanker());
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
index f8ae057..add0113c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
@@ -1340,10 +1340,13 @@
             @Override
             public void onRefineSuggestion(OmniboxSuggestion suggestion) {
                 stopAutocomplete(false);
-                mUrlCoordinator.setUrlBarData(
-                        UrlBarData.forNonUrlText(suggestion.getFillIntoEdit()),
+                boolean isUrlSuggestion = suggestion.isUrlSuggestion();
+                String refineText = suggestion.getFillIntoEdit();
+                if (!isUrlSuggestion) refineText = TextUtils.concat(refineText, " ").toString();
+
+                mUrlCoordinator.setUrlBarData(UrlBarData.forNonUrlText(refineText),
                         UrlBar.ScrollType.NO_SCROLL, UrlBarCoordinator.SelectionState.SELECT_END);
-                if (suggestion.isUrlSuggestion()) {
+                if (isUrlSuggestion) {
                     RecordUserAction.record("MobileOmniboxRefineSuggestion.Url");
                 } else {
                     RecordUserAction.record("MobileOmniboxRefineSuggestion.Search");
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsMetrics.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsMetrics.java
index 485f3171e..45f656aa 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsMetrics.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsMetrics.java
@@ -40,14 +40,6 @@
         RecordUserAction.record("Suggestions.SurfaceVisible");
     }
 
-    public static void recordSurfaceHalfVisible() {
-        RecordUserAction.record("Suggestions.SurfaceHalfVisible");
-    }
-
-    public static void recordSurfaceFullyVisible() {
-        RecordUserAction.record("Suggestions.SurfaceFullyVisible");
-    }
-
     public static void recordSurfaceHidden() {
         RecordUserAction.record("Suggestions.SurfaceHidden");
     }
@@ -76,18 +68,6 @@
         RecordUserAction.record("Suggestions.Card.SwipedAway");
     }
 
-    public static void recordContextualSuggestionOpened() {
-        RecordUserAction.record("Suggestions.ContextualSuggestion.Open");
-    }
-
-    public static void recordContextualSuggestionsCarouselShown() {
-        RecordUserAction.record("Suggestions.Contextual.Carousel.Shown");
-    }
-
-    public static void recordContextualSuggestionsCarouselScrolled() {
-        RecordUserAction.record("Suggestions.Contextual.Carousel.Scrolled");
-    }
-
     // Effect/Purpose of the interactions. Most are recorded in |content_suggestions_metrics.h|
 
     public static void recordActionViewAll() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsNavigationDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsNavigationDelegateImpl.java
index 32e5f92..78bdfbc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsNavigationDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsNavigationDelegateImpl.java
@@ -105,7 +105,9 @@
 
     @Override
     public void openSnippet(final int windowOpenDisposition, final SnippetArticle article) {
-        NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_SNIPPET);
+        if (!article.isContextual()) {
+            NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_SNIPPET);
+        }
 
         if (article.isAssetDownload()) {
             assert windowOpenDisposition == WindowOpenDisposition.CURRENT_TAB
@@ -157,7 +159,9 @@
         }
 
         Tab loadingTab = openUrl(windowOpenDisposition, loadUrlParams);
-        if (loadingTab != null) SuggestionsMetrics.recordVisit(loadingTab, article);
+        if (loadingTab != null && !article.isContextual()) {
+            SuggestionsMetrics.recordVisit(loadingTab, article);
+        }
     }
 
     private void openDownloadSuggestion(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/AddToHomescreenDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/AddToHomescreenDialog.java
index c3f6e07..f160378c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/AddToHomescreenDialog.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/AddToHomescreenDialog.java
@@ -43,11 +43,6 @@
         void addToHomescreen(String title);
 
         /**
-         * Called when the dialog is explicitly cancelled by the user.
-         */
-        void onDialogCancelled();
-
-        /**
          * Called when the user wants to view a native app in the Play Store.
          */
         void onNativeAppDetailsRequested();
@@ -101,7 +96,6 @@
                         .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                             @Override
                             public void onClick(DialogInterface dialog, int id) {
-                                mDelegate.onDialogCancelled();
                                 dialog.cancel();
                             }
                         });
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/AddToHomescreenManager.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/AddToHomescreenManager.java
index 20f500b..549e0fa 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/AddToHomescreenManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/AddToHomescreenManager.java
@@ -63,11 +63,6 @@
     }
 
     @Override
-    public void onDialogCancelled() {
-        // Do nothing.
-    }
-
-    @Override
     public void onNativeAppDetailsRequested() {
         // This should never be called.
         assert false;
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 026687f..f7c90e6 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -500,9 +500,11 @@
   "java/src/org/chromium/chrome/browser/download/home/list/ListPropertyViewBinder.java",
   "java/src/org/chromium/chrome/browser/download/home/list/ListUtils.java",
   "java/src/org/chromium/chrome/browser/download/home/list/UiUtils.java",
-  "java/src/org/chromium/chrome/browser/download/home/toolbar/DownloadHomeToolbar.java",
   "java/src/org/chromium/chrome/browser/download/home/snackbars/DeleteUndoCoordinator.java",
   "java/src/org/chromium/chrome/browser/download/home/snackbars/UndoUiUtils.java",
+  "java/src/org/chromium/chrome/browser/download/home/storage/StorageCoordinator.java",
+  "java/src/org/chromium/chrome/browser/download/home/storage/StorageSummaryProvider.java",
+  "java/src/org/chromium/chrome/browser/download/home/toolbar/DownloadHomeToolbar.java",
   "java/src/org/chromium/chrome/browser/download/home/view/SelectionView.java",
   "java/src/org/chromium/chrome/browser/download/items/DownloadBlockedOfflineContentProvider.java",
   "java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorFactory.java",
@@ -526,7 +528,6 @@
   "java/src/org/chromium/chrome/browser/download/ui/LoadingStateDelegate.java",
   "java/src/org/chromium/chrome/browser/download/ui/OfflineGroupHeaderView.java",
   "java/src/org/chromium/chrome/browser/download/ui/SpaceDisplay.java",
-  "java/src/org/chromium/chrome/browser/download/ui/StorageSummary.java",
   "java/src/org/chromium/chrome/browser/engagement/SiteEngagementService.java",
   "java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBridge.java",
   "java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryTile.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/README.md b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/README.md
index 543d953..8ee332d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/README.md
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/README.md
@@ -157,17 +157,19 @@
   way to manually re-enable real controller use from within the VrCore developer
   settings)
 
-#### don-enabled
+#### vr-settings-service-enabled
 
-`--don-enabled --annotation=Restriction=DON_Enabled`
+`--vr-settings-service-enabled --annotation=Restriction=VR_Settings_Service`
 
-Tells the test runner to allow the running of tests that only work with the DON
-flow enabled and limits the set of tests run to those that work with the DON
-flow enabled.
+Tells the test runner to allow the running of tests that utilize the VR settings
+service to dynamically change VrCore settings during a test instead of relying
+on whatever was set by the shared preference file that was applied. This is used
+as a catch-all for less standard tests, such as those that require the DON flow
+to be enabled or that need to switch the paired viewer mid-test.
 
 This should only be used when `--shared-prefs-file` is passed
-`//chrome/android/shared_preference_files/test/vr_ddview_don_setupcomplete.json`
-as otherwise the DON flow will be disabled.
+`//chrome/android/shared_preference_files/test/vr_enable_vr_settings_service.json`
+as otherwise trying to use the service will be a NOOP.
 
 ## Adding New Tests
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrTestFramework.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrTestFramework.java
index a6f0a00..9fa7f34 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrTestFramework.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrTestFramework.java
@@ -6,7 +6,7 @@
 
 import org.junit.Assert;
 
-import org.chromium.base.CommandLine;
+import org.chromium.chrome.browser.vr.rules.VrTestRule;
 import org.chromium.chrome.browser.vr.util.VrShellDelegateUtils;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.content_public.browser.WebContents;
@@ -31,10 +31,8 @@
         // TODO(https://crbug.com/762724): Remove this workaround when the issue with being resumed
         // before receiving the VR broadcast is fixed on VrCore's end.
         // However, we don't want to enable the workaround if the DON flow is enabled, as that
-        // causes issues. Since we don't have a way of actually checking whether the DON flow is
-        // enabled, check for the presence of the flag that's passed to tests when the DON flow is
-        // enabled.
-        if (!CommandLine.getInstance().hasSwitch("don-enabled")) {
+        // causes issues.
+        if (!((VrTestRule) getRule()).isDonEnabled()) {
             VrShellDelegateUtils.getDelegateInstance().setExpectingBroadcast();
         }
         super.enterSessionWithUserGesture(webContents);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrTransitionTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrTransitionTest.java
index e09b8e0..3494ac6a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrTransitionTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrTransitionTest.java
@@ -9,8 +9,9 @@
 import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_CHECK_INTERVAL_SHORT_MS;
 import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_TIMEOUT_LONG_MS;
 import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_TIMEOUT_SHORT_MS;
-import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_DON_ENABLED;
+import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_DEVICE_DAYDREAM;
 import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM;
+import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VR_SETTINGS_SERVICE;
 
 import android.annotation.TargetApi;
 import android.graphics.Bitmap;
@@ -38,8 +39,10 @@
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ChromeSwitches;
+import org.chromium.chrome.browser.vr.rules.VrSettingsFile;
 import org.chromium.chrome.browser.vr.rules.XrActivityRestriction;
 import org.chromium.chrome.browser.vr.util.NfcSimUtils;
+import org.chromium.chrome.browser.vr.util.VrSettingsServiceUtils;
 import org.chromium.chrome.browser.vr.util.VrTestRuleUtils;
 import org.chromium.chrome.browser.vr.util.VrTransitionUtils;
 import org.chromium.chrome.test.ChromeActivityTestRule;
@@ -216,7 +219,8 @@
      */
     @Test
     @MediumTest
-    @Restriction({RESTRICTION_TYPE_VIEWER_DAYDREAM, RESTRICTION_TYPE_DON_ENABLED})
+    @Restriction({RESTRICTION_TYPE_DEVICE_DAYDREAM, RESTRICTION_TYPE_VR_SETTINGS_SERVICE})
+    @VrSettingsFile(VrSettingsServiceUtils.FILE_DDVIEW_DONENABLED)
     @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
     public void testPresentationPromiseUnresolvedDuringDon() throws InterruptedException {
         presentationPromiseUnresolvedDuringDonImpl(
@@ -231,7 +235,8 @@
      */
     @Test
     @MediumTest
-    @Restriction({RESTRICTION_TYPE_VIEWER_DAYDREAM, RESTRICTION_TYPE_DON_ENABLED})
+    @Restriction({RESTRICTION_TYPE_DEVICE_DAYDREAM, RESTRICTION_TYPE_VR_SETTINGS_SERVICE})
+    @VrSettingsFile(VrSettingsServiceUtils.FILE_DDVIEW_DONENABLED)
     @CommandLineFlags
             .Remove({"enable-webvr"})
             @CommandLineFlags.Add({"enable-features=WebXR"})
@@ -256,7 +261,8 @@
      */
     @Test
     @MediumTest
-    @Restriction({RESTRICTION_TYPE_VIEWER_DAYDREAM, RESTRICTION_TYPE_DON_ENABLED})
+    @Restriction({RESTRICTION_TYPE_DEVICE_DAYDREAM, RESTRICTION_TYPE_VR_SETTINGS_SERVICE})
+    @VrSettingsFile(VrSettingsServiceUtils.FILE_DDVIEW_DONENABLED)
     @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
     public void testPresentationPromiseRejectedIfDonCanceled() throws InterruptedException {
         presentationPromiseRejectedIfDonCanceledImpl(
@@ -270,7 +276,8 @@
      */
     @Test
     @MediumTest
-    @Restriction({RESTRICTION_TYPE_VIEWER_DAYDREAM, RESTRICTION_TYPE_DON_ENABLED})
+    @Restriction({RESTRICTION_TYPE_DEVICE_DAYDREAM, RESTRICTION_TYPE_VR_SETTINGS_SERVICE})
+    @VrSettingsFile(VrSettingsServiceUtils.FILE_DDVIEW_DONENABLED)
     @CommandLineFlags
             .Remove({"enable-webvr"})
             @CommandLineFlags.Add({"enable-features=WebXR"})
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/adding_new_tests.md b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/adding_new_tests.md
index 9348f14e..7b67846 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/adding_new_tests.md
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/adding_new_tests.md
@@ -81,6 +81,14 @@
 override a flag set by the test class on a per-test basis, you must remove and
 re-add it.
 
+#### @VrSettingsFile
+
+You can have the test apply non-standard VrCore settings such as enabling the
+DON flow by using `@VrSettingsFile` and the pre-defined files in
+[`util/VrSettingsServiceUtils.java`][vr_settings_service_utils] (or create your
+own settings file). This should be used alongside an `@Restriction` containing
+`RESTRICTION_TYPE_VR_SETTINGS_SERVICE`.
+
 ### Test Body
 
 #### HTML Test File
@@ -170,5 +178,6 @@
 [xr_instrumentation_deep_dive]: https://chromium.googlesource.com/chromium/src/+/master/chrome/android/javatests/src/org/chromium/chrome/browser/vr/xr_instrumentation_deep_dive.md
 [webxr_vr_transition_test]: https://chromium.googlesource.com/chromium/src/+/master/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrTransitionTest.java
 [webxr_vr_transition_test_html]: https://chromium.googlesource.com/chromium/src/+/master/chrome/test/data/xr/e2e_test_files/html/test_non_immersive_stops_during_immersive.html
+[vr_settings_service_utils]: https://chromium.googlesource.com/chromium/src/+/master/chrome/android/javatests/src/org/chromium/chrome/browser/vr/util/VrSettingsServiceUtils.java
 [vr_browser_transition_test]: https://chromium.googlesource.com/chromium/src/+/master/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserTransitionTest.java
 [build_gn]: https://chromium.googlesource.com/chromium/src/+/master/chrome/android/BUILD.gn
\ No newline at end of file
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/rules/ChromeTabbedActivityVrTestRule.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/rules/ChromeTabbedActivityVrTestRule.java
index 0391f39..b7855b4 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/rules/ChromeTabbedActivityVrTestRule.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/rules/ChromeTabbedActivityVrTestRule.java
@@ -9,7 +9,6 @@
 
 import org.chromium.chrome.browser.vr.TestVrShellDelegate;
 import org.chromium.chrome.browser.vr.rules.XrActivityRestriction.SupportedActivity;
-import org.chromium.chrome.browser.vr.util.HeadTrackingUtils;
 import org.chromium.chrome.browser.vr.util.VrTestRuleUtils;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 
@@ -21,22 +20,18 @@
 public class ChromeTabbedActivityVrTestRule
         extends ChromeTabbedActivityTestRule implements VrTestRule {
     private boolean mTrackerDirty;
+    private boolean mDonEnabled;
 
     @Override
     public Statement apply(final Statement base, final Description desc) {
         return super.apply(new Statement() {
             @Override
             public void evaluate() throws Throwable {
-                VrTestRuleUtils.ensureNoVrActivitiesDisplayed();
-                HeadTrackingUtils.checkForAndApplyHeadTrackingModeAnnotation(
-                        ChromeTabbedActivityVrTestRule.this, desc);
-                startMainActivityOnBlankPage();
-                TestVrShellDelegate.createTestVrShellDelegate(getActivity());
-                try {
-                    base.evaluate();
-                } finally {
-                    if (isTrackerDirty()) HeadTrackingUtils.revertTracker();
-                }
+                VrTestRuleUtils.evaluateVrTestRuleImpl(
+                        base, desc, ChromeTabbedActivityVrTestRule.this, () -> {
+                            startMainActivityOnBlankPage();
+                            TestVrShellDelegate.createTestVrShellDelegate(getActivity());
+                        });
             }
         }, desc);
     }
@@ -55,4 +50,14 @@
     public void setTrackerDirty() {
         mTrackerDirty = true;
     }
+
+    @Override
+    public boolean isDonEnabled() {
+        return mDonEnabled;
+    }
+
+    @Override
+    public void setDonEnabled(boolean isEnabled) {
+        mDonEnabled = isEnabled;
+    }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/rules/CustomTabActivityVrTestRule.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/rules/CustomTabActivityVrTestRule.java
index 35c5751d..8e6d51be 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/rules/CustomTabActivityVrTestRule.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/rules/CustomTabActivityVrTestRule.java
@@ -13,7 +13,6 @@
 import org.chromium.chrome.browser.customtabs.CustomTabsTestUtils;
 import org.chromium.chrome.browser.vr.TestVrShellDelegate;
 import org.chromium.chrome.browser.vr.rules.XrActivityRestriction.SupportedActivity;
-import org.chromium.chrome.browser.vr.util.HeadTrackingUtils;
 import org.chromium.chrome.browser.vr.util.VrTestRuleUtils;
 
 /**
@@ -22,23 +21,21 @@
  */
 public class CustomTabActivityVrTestRule extends CustomTabActivityTestRule implements VrTestRule {
     private boolean mTrackerDirty;
+    private boolean mDonEnabled;
 
     @Override
     public Statement apply(final Statement base, final Description desc) {
         return super.apply(new Statement() {
             @Override
             public void evaluate() throws Throwable {
-                VrTestRuleUtils.ensureNoVrActivitiesDisplayed();
-                HeadTrackingUtils.checkForAndApplyHeadTrackingModeAnnotation(
-                        CustomTabActivityVrTestRule.this, desc);
-                startCustomTabActivityWithIntent(CustomTabsTestUtils.createMinimalCustomTabIntent(
-                        InstrumentationRegistry.getTargetContext(), "about:blank"));
-                TestVrShellDelegate.createTestVrShellDelegate(getActivity());
-                try {
-                    base.evaluate();
-                } finally {
-                    if (isTrackerDirty()) HeadTrackingUtils.revertTracker();
-                }
+                VrTestRuleUtils.evaluateVrTestRuleImpl(
+                        base, desc, CustomTabActivityVrTestRule.this, () -> {
+                            startCustomTabActivityWithIntent(
+                                    CustomTabsTestUtils.createMinimalCustomTabIntent(
+                                            InstrumentationRegistry.getTargetContext(),
+                                            "about:blank"));
+                            TestVrShellDelegate.createTestVrShellDelegate(getActivity());
+                        });
             }
         }, desc);
     }
@@ -57,4 +54,14 @@
     public void setTrackerDirty() {
         mTrackerDirty = true;
     }
+
+    @Override
+    public boolean isDonEnabled() {
+        return mDonEnabled;
+    }
+
+    @Override
+    public void setDonEnabled(boolean isEnabled) {
+        mDonEnabled = isEnabled;
+    }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/rules/VrSettingsFile.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/rules/VrSettingsFile.java
new file mode 100644
index 0000000..3becb9a
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/rules/VrSettingsFile.java
@@ -0,0 +1,30 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.vr.rules;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation for applying VrCore settings during pre-test setup.
+ *
+ * This doesn't have any sort of performance benefit, but does keep the specifics of test setup
+ * out of the test body.
+ *
+ * For example, the following would cause a test to start with the DON flow enabled:
+ *     <code>
+ *     @VrSettingsFile(VrSettingsFile.DDVIEW_DONENABLED)
+ *     </code>
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface VrSettingsFile {
+    /**
+     * @return The settings file to apply.
+     */
+    public String value();
+}
\ No newline at end of file
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/rules/VrTestRule.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/rules/VrTestRule.java
index 411e62d7..7ef12a6d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/rules/VrTestRule.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/rules/VrTestRule.java
@@ -21,4 +21,15 @@
      * Tells the rule that the head tracking mode has been changed.
      */
     public void setTrackerDirty();
+
+    /**
+     * Whether the currently applied settings result in the DON flow being enabled.
+     * @return True if the DON flow is enabled, false otherwise.
+     */
+    public boolean isDonEnabled();
+
+    /**
+     * Sets whether the currently applied settings result in the DON flow being enabled.
+     */
+    public void setDonEnabled(boolean isEnabled);
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/rules/WebappActivityVrTestRule.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/rules/WebappActivityVrTestRule.java
index 79f1ccc..8b5a199 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/rules/WebappActivityVrTestRule.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/rules/WebappActivityVrTestRule.java
@@ -9,7 +9,6 @@
 
 import org.chromium.chrome.browser.vr.TestVrShellDelegate;
 import org.chromium.chrome.browser.vr.rules.XrActivityRestriction.SupportedActivity;
-import org.chromium.chrome.browser.vr.util.HeadTrackingUtils;
 import org.chromium.chrome.browser.vr.util.VrTestRuleUtils;
 import org.chromium.chrome.browser.webapps.WebappActivityTestRule;
 
@@ -19,22 +18,18 @@
  */
 public class WebappActivityVrTestRule extends WebappActivityTestRule implements VrTestRule {
     private boolean mTrackerDirty;
+    private boolean mDonEnabled;
 
     @Override
     public Statement apply(final Statement base, final Description desc) {
         return super.apply(new Statement() {
             @Override
             public void evaluate() throws Throwable {
-                VrTestRuleUtils.ensureNoVrActivitiesDisplayed();
-                HeadTrackingUtils.checkForAndApplyHeadTrackingModeAnnotation(
-                        WebappActivityVrTestRule.this, desc);
-                startWebappActivity();
-                TestVrShellDelegate.createTestVrShellDelegate(getActivity());
-                try {
-                    base.evaluate();
-                } finally {
-                    if (isTrackerDirty()) HeadTrackingUtils.revertTracker();
-                }
+                VrTestRuleUtils.evaluateVrTestRuleImpl(
+                        base, desc, WebappActivityVrTestRule.this, () -> {
+                            startWebappActivity();
+                            TestVrShellDelegate.createTestVrShellDelegate(getActivity());
+                        });
             }
         }, desc);
     }
@@ -53,4 +48,14 @@
     public void setTrackerDirty() {
         mTrackerDirty = true;
     }
+
+    @Override
+    public boolean isDonEnabled() {
+        return mDonEnabled;
+    }
+
+    @Override
+    public void setDonEnabled(boolean isEnabled) {
+        mDonEnabled = isEnabled;
+    }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/util/VrSettingsServiceUtils.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/util/VrSettingsServiceUtils.java
new file mode 100644
index 0000000..cd4e8a2
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/util/VrSettingsServiceUtils.java
@@ -0,0 +1,111 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.vr.util;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+
+import org.junit.Assert;
+import org.junit.runner.Description;
+
+import org.chromium.base.CommandLine;
+import org.chromium.base.test.util.UrlUtils;
+import org.chromium.chrome.browser.vr.rules.VrSettingsFile;
+import org.chromium.chrome.browser.vr.rules.VrTestRule;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Scanner;
+
+/**
+ * Utility class for interacting with the VrCore developer settings service, which allows tests to
+ * change VrCore's configuration without applying a shared preference file.
+ *
+ * Requires that the "EnableDeveloperService" setting is enabled.
+ */
+public class VrSettingsServiceUtils {
+    // Notable properties: DON flow enabled, DDView 2016 paired, VrCore-side emulated controller
+    // disabled, VrCore head tracking service disabled.
+    public static final String FILE_DDVIEW_DONENABLED = "ddview_donenabled.txt";
+
+    private static final ComponentName SETTINGS_SERVICE_COMPONENT = new ComponentName(
+            "com.google.vr.vrcore", "com.google.vr.vrcore.developer.VrDeveloperService");
+    private static final String SETTINGS_FILE_DIR =
+            "chrome/test/data/xr/e2e_test_files/vr_settings_files/";
+    private static final String EXTRA_ACTION = "action";
+    private static final String EXTRA_ACTION_VALUE = "UPDATE_SETTINGS";
+    private static final String EXTRA_VR_SETTINGS_PATH = "vr_settings_path";
+
+    private static final int SETTINGS_APPLICATION_DELAY_MS = 500;
+
+    /**
+     * Applies the settings specified in the provided file to VrCore.
+     *
+     * @param filename The name of the file in SETTINGS_FILE_DIR to apply.
+     * @param rule The VrTestRule for the current test.
+     */
+    public static void applySettingsFile(String filename, VrTestRule rule)
+            throws FileNotFoundException {
+        // We only want to be applying settings this way when explicitly running tests that use this
+        // functionality since it persists between tests. The shared preference files applied for
+        // tests that are not expected to use this should cause application of settings this way
+        // to be a NOOP, but getting to this method without explicitly wanting this functionality
+        // is indicative of a problem, so fail.
+        Assert.assertTrue("Attempted to use the VR settings service without explicitly allowing it",
+                CommandLine.getInstance().hasSwitch("vr-settings-service-enabled"));
+
+        // Start the service and have it update VrCore settings using the provided file.
+        Intent settingsIntent = new Intent();
+        settingsIntent.putExtra(EXTRA_ACTION, EXTRA_ACTION_VALUE);
+        settingsIntent.putExtra(EXTRA_VR_SETTINGS_PATH,
+                UrlUtils.getIsolatedTestFilePath(SETTINGS_FILE_DIR) + filename);
+        settingsIntent.setComponent(SETTINGS_SERVICE_COMPONENT);
+        Assert.assertTrue("Failed to start VR setttings service",
+                InstrumentationRegistry.getContext().startService(settingsIntent) != null);
+
+        // We need to tell the rule whether the newly applied settings enable the DON flow.
+        rule.setDonEnabled(fileEnablesDon(filename));
+        // The settings seem to apply nearly instantly, but since it's done through a service, we
+        // don't have any guarantees about that or a way to be notified when they're applied. So,
+        // sleep a bit to be safe. This shouldn't be an issue in terms of runtime since this is
+        // used pretty infrequently.
+        SystemClock.sleep(SETTINGS_APPLICATION_DELAY_MS);
+    }
+
+    /**
+     * Checks if a test is annotated with a VrSettingsFile annotation, and if so, applies the
+     * given settings file. Only meant to be used by a Rule's apply() method.
+     *
+     * @param desc The Description object for the Rule currently being applied.
+     * @param rule The VrTestRule currently being applied.
+     */
+    public static void checkForAndApplyVrSettingsFileAnnotation(Description desc, VrTestRule rule)
+            throws FileNotFoundException {
+        // Check if the test has a VrSettingsFile annotation
+        VrSettingsFile annotation = desc.getAnnotation(VrSettingsFile.class);
+        if (annotation == null) return;
+        applySettingsFile(annotation.value(), rule);
+    }
+
+    private static boolean fileEnablesDon(String filename) throws FileNotFoundException {
+        File infile = new File(UrlUtils.getIsolatedTestFilePath(SETTINGS_FILE_DIR) + filename);
+        Scanner scanner = new Scanner(infile);
+        for (String curLine = scanner.nextLine(); scanner.hasNextLine();
+                curLine = scanner.nextLine()) {
+            if (!curLine.contains("VrSkipDon")) continue;
+            if (curLine.contains("true"))
+                return false;
+            else if (curLine.contains("false"))
+                return true;
+            Assert.fail(
+                    "Given file " + filename + " malformed - VrSkipDon present, but not a bool");
+        }
+        Assert.fail("Given file " + filename
+                + " does not contain VrSkipDon. Please explicitly enable or disable the DON flow");
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/util/VrTestRuleUtils.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/util/VrTestRuleUtils.java
index 0d03525..354e1da 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/util/VrTestRuleUtils.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/util/VrTestRuleUtils.java
@@ -8,9 +8,13 @@
 import android.support.test.InstrumentationRegistry;
 import android.support.test.uiautomator.UiDevice;
 
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
 import org.chromium.base.test.params.ParameterSet;
 import org.chromium.chrome.browser.vr.rules.ChromeTabbedActivityVrTestRule;
 import org.chromium.chrome.browser.vr.rules.CustomTabActivityVrTestRule;
+import org.chromium.chrome.browser.vr.rules.VrTestRule;
 import org.chromium.chrome.browser.vr.rules.WebappActivityVrTestRule;
 
 import java.util.ArrayList;
@@ -25,6 +29,40 @@
     // Daydream intent, meaning that it still thinks VR is active until this amount of time has
     // passed.
     private static final int VRCORE_UNREGISTER_DELAY_MS = 500;
+
+    /**
+     * Essentially a Runnable that can throw exceptions.
+     */
+    public interface ChromeLaunchMethod { public void launch() throws Throwable; }
+
+    /**
+     * Helper method to apply a VrTestRule/ChromeActivityTestRule combination. The only difference
+     * between various classes that implement VrTestRule is how they start their activity, so the
+     * common boilerplate code can be kept here so each VrTestRule only has to provide a way to
+     * launch Chrome.
+     *
+     * @param base The Statement passed to the calling ChromeActivityTestRule's apply() method.
+     * @param desc The Description passed to the calling ChromeActivityTestRule's apply() method.
+     * @param rule The calling VrTestRule.
+     * @param launcher A ChromeLaunchMethod whose launch() contains the code snippet to start Chrome
+     *        in the calling ChromeActivityTestRule's activity type.
+     */
+    public static void evaluateVrTestRuleImpl(final Statement base, final Description desc,
+            final VrTestRule rule, final ChromeLaunchMethod launcher) throws Throwable {
+        VrTestRuleUtils.ensureNoVrActivitiesDisplayed();
+        HeadTrackingUtils.checkForAndApplyHeadTrackingModeAnnotation(rule, desc);
+        launcher.launch();
+        // Must be called after Chrome is started, as otherwise startService fails with an
+        // IllegalStateException for being used from a backgrounded app.
+        VrSettingsServiceUtils.checkForAndApplyVrSettingsFileAnnotation(desc, rule);
+
+        try {
+            base.evaluate();
+        } finally {
+            if (rule.isTrackerDirty()) HeadTrackingUtils.revertTracker();
+        }
+    }
+
     /**
      * Creates the list of VrTestRules that are currently supported for use in test
      * parameterization.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/xr_instrumentation_deep_dive.md b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/xr_instrumentation_deep_dive.md
index 64b0e114..a8edcf7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/xr_instrumentation_deep_dive.md
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/xr_instrumentation_deep_dive.md
@@ -120,6 +120,35 @@
 VR-specific setup such as ensuring that the test is not started in VR and
 allowing the use of the experimental/broken VrCore head tracking service.
 
+## Dynamic VrCore Settings
+
+Most tests simply use whatever VrCore settings are set via the
+`--shared-prefs-file` option before testing starts. This is normally beneficial
+since only having to apply settings once per test suite is more efficient.
+However, since `--shared-prefs-file` is per-test-suite, it's less desirable for
+tests that need uncommon settings (e.g. DON flow enabled) and completely
+unusable by itself if a test needs to change VrCore settings mid-test.
+
+Thus, tests that use less common settings instead use a shared preference file
+that enables VrCore's VR settings service and apply settings on a per-test
+basis using the service. These tests must be kept separate from the regular
+tests that don't use dynamic settings because the setting changes are permanent.
+So, unless all or none of the tests in a given run use dynamic settings, we risk
+running tests with incorrect VrCore settings.
+
+Skipping over these tests is done using an `@Restriction` annotation instead of
+a rule like for test parameterization because skipping via `@Restriction` is
+faster.
+
+Why not use this for all tests? Since many tests are run twice, once with
+Cardboard and once with Daydream View, we would need to use parameterization and
+annotations for supported settings types to run with. Add in additional, less
+commonly used settings types, and you could end up with most tests being tried
+in three different activity types and something like five different settings
+types, for a total of fifteen attempted runs. While most of these would end up
+being skipped, starting the rule application process does have some overhead, so
+the end result would be a noticeable increase in test run time.
+
 ## VR Controller Input
 
 There are currently two ways of injecting Daydream controller input into tests,
diff --git a/chrome/android/shared_preference_files/test/vr_cardboard_skipdon_setupcomplete.json b/chrome/android/shared_preference_files/test/vr_cardboard_skipdon_setupcomplete.json
index 1d8aedbe..3c36564f 100644
--- a/chrome/android/shared_preference_files/test/vr_cardboard_skipdon_setupcomplete.json
+++ b/chrome/android/shared_preference_files/test/vr_cardboard_skipdon_setupcomplete.json
@@ -9,7 +9,8 @@
       "VrDeviceParams": "CgZHb29nbGUSCUNhcmRib2FyZB0J-SA9JQHegj0qEAAAcEIAAHBCAABwQgAAcEI1KVwPPToIV_GrPmKxDT9QAFgAYAM",
       "UseAutomatedController": false,
       "GvrPlatformLibraryPref": false,
-      "EnableVrCoreHeadTracking": false
+      "EnableVrCoreHeadTracking": false,
+      "EnableDeveloperService": false
     }
   }
 ]
diff --git a/chrome/android/shared_preference_files/test/vr_ddview_skipdon_setupcomplete.json b/chrome/android/shared_preference_files/test/vr_ddview_skipdon_setupcomplete.json
index 1c27f12..b38f73648 100644
--- a/chrome/android/shared_preference_files/test/vr_ddview_skipdon_setupcomplete.json
+++ b/chrome/android/shared_preference_files/test/vr_ddview_skipdon_setupcomplete.json
@@ -11,7 +11,8 @@
       "PairedControllerDriver": "DRIVER_AUTOMATED",
       "PairedControllerAddress": "FOO",
       "GvrPlatformLibraryPref": false,
-      "EnableVrCoreHeadTracking": false
+      "EnableVrCoreHeadTracking": false,
+      "EnableDeveloperService": false
     }
   }
 ]
diff --git a/chrome/android/shared_preference_files/test/vr_enable_vr_settings_service.json b/chrome/android/shared_preference_files/test/vr_enable_vr_settings_service.json
new file mode 100644
index 0000000..6ed1172
--- /dev/null
+++ b/chrome/android/shared_preference_files/test/vr_enable_vr_settings_service.json
@@ -0,0 +1,10 @@
+[
+  {
+    "package": "com.google.vr.vrcore",
+    "filename": "VrCoreSettings.xml",
+    "supports_encrypted_path": true,
+    "set": {
+      "EnableDeveloperService": true
+    }
+  }
+]
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 8b856ba..f88e1756 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -590,12 +590,6 @@
     "infobars/infobar_responder.h",
     "infobars/infobar_service.cc",
     "infobars/infobar_service.h",
-    "install_verification/win/install_verification.cc",
-    "install_verification/win/install_verification.h",
-    "install_verification/win/loaded_module_verification.cc",
-    "install_verification/win/loaded_module_verification.h",
-    "install_verification/win/module_ids.cc",
-    "install_verification/win/module_ids.h",
     "install_verification/win/module_info.h",
     "install_verification/win/module_list.cc",
     "install_verification/win/module_list.h",
@@ -1748,6 +1742,7 @@
     "//components/mirroring/browser:browser",
     "//components/mirroring/mojom:host",
     "//components/mirroring/mojom:service",
+    "//components/mirroring/service:service",
     "//components/navigation_interception",
     "//components/navigation_metrics",
     "//components/net_log",
@@ -4081,16 +4076,7 @@
   }
 
   if (enable_rlz) {
-    sources += [
-      "rlz/chrome_rlz_tracker_delegate.cc",
-      "rlz/chrome_rlz_tracker_delegate.h",
-    ]
-
-    public_deps += [
-      "//components/google/core/browser",
-      "//components/rlz",
-      "//rlz:rlz_lib",
-    ]
+    deps += [ ":rlz" ]
   }
 
   if (enable_service_discovery) {
@@ -5144,3 +5130,19 @@
     ]
   }
 }
+
+if (enable_rlz_support) {
+  static_library("rlz") {
+    sources = [
+      "rlz/chrome_rlz_tracker_delegate.cc",
+      "rlz/chrome_rlz_tracker_delegate.h",
+    ]
+    public_deps = [
+      "//components/google/core/browser",
+      "//components/omnibox/browser",
+      "//components/rlz",
+      "//components/search_engines",
+      "//rlz:rlz_lib",
+    ]
+  }
+}
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 87d5502..e8b836c 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3024,6 +3024,12 @@
      kOsDesktop,
      FEATURE_VALUE_TYPE(
          autofill::kAutofillSaveCardDialogUnlabeledExpirationDate)},
+    {"enable-autofill-save-card-sign-in-after-local-save",
+     flag_descriptions::kEnableAutofillSaveCardSignInAfterLocalSaveName,
+     flag_descriptions::kEnableAutofillSaveCardSignInAfterLocalSaveDescription,
+     kOsDesktop,
+     FEATURE_VALUE_TYPE(
+         autofill::features::kAutofillSaveCardSignInAfterLocalSave)},
     {"enable-autofill-send-experiment-ids-in-payments-rpcs",
      flag_descriptions::kEnableAutofillSendExperimentIdsInPaymentsRPCsName,
      flag_descriptions::
diff --git a/chrome/browser/apps/guest_view/web_view_browsertest.cc b/chrome/browser/apps/guest_view/web_view_browsertest.cc
index a874c0e..9183faa 100644
--- a/chrome/browser/apps/guest_view/web_view_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_browsertest.cc
@@ -1143,41 +1143,6 @@
   ASSERT_TRUE(launched_again_listener.WaitUntilSatisfied());
 }
 
-IN_PROC_BROWSER_TEST_F(WebViewBrowserPluginSpecificTest, AcceptTouchEvents) {
-  // This test only makes sense for non-OOPIF WebView, since with
-  // GuestViewCrossProcessFrames events are routed directly to the
-  // guest, so the embedder does not need to know about the installation of
-  // touch handlers.
-  ASSERT_FALSE(
-      base::FeatureList::IsEnabled(::features::kGuestViewCrossProcessFrames));
-
-  LoadAppWithGuest("web_view/accept_touch_events");
-
-  content::RenderViewHost* embedder_rvh =
-      GetEmbedderWebContents()->GetRenderViewHost();
-
-  bool embedder_has_touch_handler =
-      content::RenderViewHostTester::HasTouchEventHandler(embedder_rvh);
-  EXPECT_FALSE(embedder_has_touch_handler);
-
-  SendMessageToGuestAndWait("install-touch-handler", "installed-touch-handler");
-
-  // Note that we need to wait for the installed/registered touch handler to
-  // appear in browser process before querying |embedder_rvh|.
-  // In practice, since we do a roundrtip from browser process to guest and
-  // back, this is sufficient.
-  embedder_has_touch_handler =
-      content::RenderViewHostTester::HasTouchEventHandler(embedder_rvh);
-  EXPECT_TRUE(embedder_has_touch_handler);
-
-  SendMessageToGuestAndWait("uninstall-touch-handler",
-                            "uninstalled-touch-handler");
-  // Same as the note above about waiting.
-  embedder_has_touch_handler =
-      content::RenderViewHostTester::HasTouchEventHandler(embedder_rvh);
-  EXPECT_FALSE(embedder_has_touch_handler);
-}
-
 // This test ensures JavaScript errors ("Cannot redefine property") do not
 // happen when a <webview> is removed from DOM and added back.
 IN_PROC_BROWSER_TEST_F(WebViewTest, AddRemoveWebView_AddRemoveWebView) {
diff --git a/chrome/browser/chrome_browser_main_win.cc b/chrome/browser/chrome_browser_main_win.cc
index 97966608..b39ae49e 100644
--- a/chrome/browser/chrome_browser_main_win.cc
+++ b/chrome/browser/chrome_browser_main_win.cc
@@ -40,7 +40,6 @@
 #include "chrome/browser/conflicts/module_database_win.h"
 #include "chrome/browser/conflicts/module_event_sink_impl_win.h"
 #include "chrome/browser/first_run/first_run.h"
-#include "chrome/browser/install_verification/win/install_verification.h"
 #include "chrome/browser/memory/swap_thrashing_monitor.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/profiles/profile_shortcut_manager.h"
@@ -550,18 +549,6 @@
   UMA_HISTOGRAM_BOOLEAN("Windows.Tablet",
       base::win::IsTabletDevice(nullptr, ui::GetHiddenWindow()));
 
-  // Set up a task to verify installed modules in the current process.
-  // TODO(gab): Use base::PostTaskWithTraits() directly when we're convinced
-  // BACKGROUND work doesn't interfere with startup (i.e.
-  // https://crbug.com/726937).
-  // TODO(robertshield): remove this altogether, https://crbug.com/747557.
-  content::BrowserThread::PostAfterStartupTask(
-      FROM_HERE,
-      base::CreateTaskRunnerWithTraits(
-          {base::TaskPriority::BEST_EFFORT,
-           base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}),
-      base::Bind(&VerifyInstallation));
-
   InitializeChromeElf();
 
   // Reset settings for the current profile if it's tagged to be reset after a
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 883c15fb..1f11627 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -56,6 +56,7 @@
     "//cc/paint",
     "//chrome/app:command_ids",
     "//chrome/app/vector_icons",
+    "//chrome/browser:rlz",
     "//chrome/browser/apps/foundation/app_service:lib",
     "//chrome/browser/devtools",
     "//chrome/browser/extensions",
@@ -104,7 +105,6 @@
     "//components/feedback",
     "//components/flags_ui",
     "//components/gcm_driver",
-    "//components/google/core/common:common",
     "//components/guest_view/browser",
     "//components/image_fetcher/core",
     "//components/invalidation/impl",
@@ -127,7 +127,6 @@
     "//components/quirks",
     "//components/rappor",
     "//components/renderer_context_menu",
-    "//components/rlz",
     "//components/safe_browsing:csd_proto",
     "//components/safe_browsing/db:metadata_proto",
     "//components/session_manager/core",
diff --git a/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.cc b/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.cc
index 8c88b811..cae2312 100644
--- a/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.cc
+++ b/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.cc
@@ -204,7 +204,7 @@
 
   event_bundle.updates.emplace_back();
   if (event_data->event_type == AXEventType::WINDOW_CONTENT_CHANGED) {
-    current_tree_serializer_->DeleteClientSubtree(
+    current_tree_serializer_->InvalidateSubtree(
         GetFromId(event_data->source_id));
   }
   current_tree_serializer_->SerializeChanges(GetFromId(event_data->source_id),
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_webui.cc b/chrome/browser/chromeos/login/ui/login_display_host_webui.cc
index ccda02c..86b3a2e 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host_webui.cc
+++ b/chrome/browser/chromeos/login/ui/login_display_host_webui.cc
@@ -348,6 +348,23 @@
   DISALLOW_COPY_AND_ASSIGN(CloseAfterCommit);
 };
 
+// Returns true if we have default audio device.
+bool CanPlayStartupSound() {
+  chromeos::AudioDevice device;
+  bool found =
+      chromeos::CrasAudioHandler::Get()->GetPrimaryActiveOutputDevice(&device);
+  return found && device.stable_device_id_version &&
+         device.type != chromeos::AudioDeviceType::AUDIO_TYPE_OTHER;
+}
+
+// Returns true if it is too late to play startup sound.
+bool StartupSoundOutdated(base::TimeTicks login_prompt_visible_time) {
+  // Don't try to play startup sound if login prompt has been already visible
+  // for a long time.
+  return base::TimeTicks::Now() - login_prompt_visible_time >
+         base::TimeDelta::FromMilliseconds(kStartupSoundMaxDelayMs);
+}
+
 }  // namespace
 
 namespace chromeos {
@@ -851,7 +868,7 @@
 // LoginDisplayHostWebUI, chromeos::CrasAudioHandler::AudioObserver:
 
 void LoginDisplayHostWebUI::OnActiveOutputNodeChanged() {
-  TryToPlayOobeStartupSound();
+  PlayStartupSoundIfPossible();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -1102,25 +1119,8 @@
 }
 
 void LoginDisplayHostWebUI::TryToPlayOobeStartupSound() {
-  if (is_voice_interaction_oobe_)
-    return;
-
-  if (oobe_startup_sound_played_ || login_prompt_visible_time_.is_null() ||
-      !CrasAudioHandler::Get()->GetPrimaryActiveOutputNode()) {
-    return;
-  }
-
-  oobe_startup_sound_played_ = true;
-
-  // Don't try play startup sound if login prompt is already visible
-  // for a long time or can't be played.
-  if (base::TimeTicks::Now() - login_prompt_visible_time_ >
-      base::TimeDelta::FromMilliseconds(kStartupSoundMaxDelayMs)) {
-    return;
-  }
-
-  AccessibilityManager::Get()->PlayEarcon(SOUND_STARTUP,
-                                          PlaySoundOption::ALWAYS);
+  need_to_play_startup_sound_ = true;
+  PlayStartupSoundIfPossible();
 }
 
 void LoginDisplayHostWebUI::OnLoginPromptVisible() {
@@ -1193,6 +1193,26 @@
   NOTREACHED();
 }
 
+void LoginDisplayHostWebUI::PlayStartupSoundIfPossible() {
+  if (!need_to_play_startup_sound_ || oobe_startup_sound_played_)
+    return;
+
+  if (login_prompt_visible_time_.is_null())
+    return;
+
+  if (is_voice_interaction_oobe_ || !CanPlayStartupSound())
+    return;
+
+  need_to_play_startup_sound_ = false;
+  oobe_startup_sound_played_ = true;
+
+  if (StartupSoundOutdated(login_prompt_visible_time_))
+    return;
+
+  AccessibilityManager::Get()->PlayEarcon(SOUND_STARTUP,
+                                          PlaySoundOption::ALWAYS);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // external
 
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_webui.h b/chrome/browser/chromeos/login/ui/login_display_host_webui.h
index 07e385d..6f0af3c 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host_webui.h
+++ b/chrome/browser/chromeos/login/ui/login_display_host_webui.h
@@ -185,6 +185,9 @@
   // Creates or recreates |existing_user_controller_|.
   void CreateExistingUserController();
 
+  // Plays startup sound if needed and audio device is ready.
+  void PlayStartupSoundIfPossible();
+
   // Sign in screen controller.
   std::unique_ptr<ExistingUserController> existing_user_controller_;
 
@@ -259,6 +262,9 @@
 
   bool is_voice_interaction_oobe_ = false;
 
+  // True if we need to play startup sound when audio device becomes available.
+  bool need_to_play_startup_sound_ = false;
+
   base::WeakPtrFactory<LoginDisplayHostWebUI> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(LoginDisplayHostWebUI);
diff --git a/chrome/browser/chromeos/login/ui/oobe_ui_dialog_delegate.cc b/chrome/browser/chromeos/login/ui/oobe_ui_dialog_delegate.cc
index bc94e4d..74c040160 100644
--- a/chrome/browser/chromeos/login/ui/oobe_ui_dialog_delegate.cc
+++ b/chrome/browser/chromeos/login/ui/oobe_ui_dialog_delegate.cc
@@ -137,7 +137,7 @@
   gfx::Rect display_rect =
       display::Screen::GetScreen()
           ->GetDisplayNearestWindow(dialog_widget_->GetNativeWindow())
-          .bounds();
+          .work_area();
 
   // Place the dialog in the center of the screen.
   const gfx::Rect bounds(
diff --git a/chrome/browser/dom_distiller/dom_distiller_viewer_source_browsertest.cc b/chrome/browser/dom_distiller/dom_distiller_viewer_source_browsertest.cc
index d7c2a28..ca1eb36a 100644
--- a/chrome/browser/dom_distiller/dom_distiller_viewer_source_browsertest.cc
+++ b/chrome/browser/dom_distiller/dom_distiller_viewer_source_browsertest.cc
@@ -376,12 +376,11 @@
   // Wait for the page load to complete (this will be a distiller error page).
   content::WaitForLoadStop(contents);
 
-  bool result;
   // Execute in isolated world; where all distiller scripts are run.
-  EXPECT_TRUE(content::ExecuteScriptInIsolatedWorldAndExtractBool(
-      contents, ISOLATED_WORLD_ID_CHROME_INTERNAL, kTestDistillerObject,
-      &result));
-  EXPECT_TRUE(result);
+  EXPECT_EQ(true, content::EvalJsWithManualReply(
+                      contents, kTestDistillerObject,
+                      content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
+                      ISOLATED_WORLD_ID_CHROME_INTERNAL));
 }
 
 IN_PROC_BROWSER_TEST_F(DomDistillerViewerSourceBrowserTest,
diff --git a/chrome/browser/extensions/api/tab_capture/offscreen_tab.cc b/chrome/browser/extensions/api/tab_capture/offscreen_tab.cc
index 089841c8..185deae 100644
--- a/chrome/browser/extensions/api/tab_capture/offscreen_tab.cc
+++ b/chrome/browser/extensions/api/tab_capture/offscreen_tab.cc
@@ -326,13 +326,13 @@
           request.render_process_id,
           request.render_frame_id,
           extension_id)) {
-    if (request.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE) {
+    if (request.audio_type == content::MEDIA_GUM_TAB_AUDIO_CAPTURE) {
       devices.push_back(content::MediaStreamDevice(
-          content::MEDIA_TAB_AUDIO_CAPTURE, std::string(), std::string()));
+          content::MEDIA_GUM_TAB_AUDIO_CAPTURE, std::string(), std::string()));
     }
-    if (request.video_type == content::MEDIA_TAB_VIDEO_CAPTURE) {
+    if (request.video_type == content::MEDIA_GUM_TAB_VIDEO_CAPTURE) {
       devices.push_back(content::MediaStreamDevice(
-          content::MEDIA_TAB_VIDEO_CAPTURE, std::string(), std::string()));
+          content::MEDIA_GUM_TAB_VIDEO_CAPTURE, std::string(), std::string()));
     }
   }
 
@@ -351,8 +351,8 @@
     content::MediaStreamType type) {
   DCHECK_EQ(offscreen_tab_web_contents_.get(),
             content::WebContents::FromRenderFrameHost(render_frame_host));
-  return type == content::MEDIA_TAB_AUDIO_CAPTURE ||
-      type == content::MEDIA_TAB_VIDEO_CAPTURE;
+  return type == content::MEDIA_GUM_TAB_AUDIO_CAPTURE ||
+         type == content::MEDIA_GUM_TAB_VIDEO_CAPTURE;
 }
 
 void OffscreenTab::DidShowFullscreenWidget() {
diff --git a/chrome/browser/extensions/api/tab_capture/tab_capture_registry.cc b/chrome/browser/extensions/api/tab_capture/tab_capture_registry.cc
index 9decb9d..c4a61aabd 100644
--- a/chrome/browser/extensions/api/tab_capture/tab_capture_registry.cc
+++ b/chrome/browser/extensions/api/tab_capture/tab_capture_registry.cc
@@ -354,8 +354,8 @@
     content::MediaStreamType stream_type,
     const content::MediaRequestState new_state) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  if (stream_type != content::MEDIA_TAB_VIDEO_CAPTURE &&
-      stream_type != content::MEDIA_TAB_AUDIO_CAPTURE) {
+  if (stream_type != content::MEDIA_GUM_TAB_VIDEO_CAPTURE &&
+      stream_type != content::MEDIA_GUM_TAB_AUDIO_CAPTURE) {
     return;
   }
 
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index b3f5370..6aabe42 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -415,6 +415,13 @@
     "If enabled, expiration dates on the save card dialog (both local and "
     "upstream) are shown without an 'Exp:' label.";
 
+const char kEnableAutofillSaveCardSignInAfterLocalSaveName[] =
+    "Show Sign-In/Sync promo after saving a card locally";
+const char kEnableAutofillSaveCardSignInAfterLocalSaveDescription[] =
+    "If enabled, shows a sign in prompt to the user after the user "
+    "saves a card locally. This also introduces a Manage Cards bubble "
+    "which you can access from the card icon after saving a card.";
+
 const char kEnableAutofillSendExperimentIdsInPaymentsRPCsName[] =
     "Send experiment flag IDs in calls to Google Payments";
 const char kEnableAutofillSendExperimentIdsInPaymentsRPCsDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index fd2e89a..63d2853 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -284,6 +284,9 @@
 extern const char
     kEnableAutofillSaveCardDialogUnlabeledExpirationDateDescription[];
 
+extern const char kEnableAutofillSaveCardSignInAfterLocalSaveName[];
+extern const char kEnableAutofillSaveCardSignInAfterLocalSaveDescription[];
+
 extern const char kEnableAutofillSendExperimentIdsInPaymentsRPCsName[];
 extern const char kEnableAutofillSendExperimentIdsInPaymentsRPCsDescription[];
 
diff --git a/chrome/browser/history/top_sites_factory.cc b/chrome/browser/history/top_sites_factory.cc
index a8746a8..07b6c1e 100644
--- a/chrome/browser/history/top_sites_factory.cc
+++ b/chrome/browser/history/top_sites_factory.cc
@@ -21,6 +21,7 @@
 #include "chrome/browser/history/history_utils.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/chrome_features.h"
+#include "chrome/common/pref_names.h"
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/grit/locale_settings.h"
@@ -32,6 +33,7 @@
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "components/ntp_tiles/constants.h"
 #include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_service.h"
 #include "content/public/browser/browser_thread.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "url/gurl.h"
@@ -66,12 +68,16 @@
 #endif
 
 void InitializePrepopulatedPageList(
+    PrefService* prefs,
     history::PrepopulatedPageList* prepopulated_pages) {
 #if !defined(OS_ANDROID)
   DCHECK(prepopulated_pages);
+  bool hide_web_store_icon = prefs->GetBoolean(prefs::kHideWebStoreIcon);
   prepopulated_pages->reserve(arraysize(kRawPrepopulatedPages));
   for (size_t i = 0; i < arraysize(kRawPrepopulatedPages); ++i) {
     const RawPrepopulatedPage& page = kRawPrepopulatedPages[i];
+    if (hide_web_store_icon && page.url_id == IDS_WEBSTORE_URL)
+      continue;
     prepopulated_pages->push_back(history::PrepopulatedPage(
         GURL(l10n_util::GetStringUTF8(page.url_id)),
         l10n_util::GetStringUTF16(page.title_id),
@@ -116,7 +122,6 @@
   history::HistoryService* history_service =
       HistoryServiceFactory::GetForProfile(profile,
                                            ServiceAccessType::EXPLICIT_ACCESS);
-
   scoped_refptr<history::TopSitesImpl> top_sites(new history::TopSitesImpl(
       profile->GetPrefs(), history_service,
       CreateTopSitesProvider(profile, history_service), prepopulated_page_list,
@@ -142,7 +147,8 @@
 scoped_refptr<RefcountedKeyedService> TopSitesFactory::BuildServiceInstanceFor(
     content::BrowserContext* context) const {
   history::PrepopulatedPageList prepopulated_pages;
-  InitializePrepopulatedPageList(&prepopulated_pages);
+  InitializePrepopulatedPageList(
+      Profile::FromBrowserContext(context)->GetPrefs(), &prepopulated_pages);
   return BuildTopSites(context, prepopulated_pages);
 }
 
diff --git a/chrome/browser/install_verification/win/install_verification.cc b/chrome/browser/install_verification/win/install_verification.cc
deleted file mode 100644
index 0c0b3810d..0000000
--- a/chrome/browser/install_verification/win/install_verification.cc
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/install_verification/win/install_verification.h"
-
-#include <stddef.h>
-#include <windows.h>
-
-#include <set>
-#include <vector>
-
-#include "base/files/file_path.h"
-#include "base/metrics/histogram_functions.h"
-#include "base/process/process.h"
-#include "base/process/process_handle.h"
-#include "base/strings/string_util.h"
-#include "base/strings/sys_string_conversions.h"
-#include "chrome/browser/install_verification/win/loaded_module_verification.h"
-#include "chrome/browser/install_verification/win/module_ids.h"
-#include "chrome/browser/install_verification/win/module_info.h"
-#include "chrome/browser/install_verification/win/module_verification_common.h"
-#include "components/variations/hashing.h"
-
-namespace {
-
-void ReportModuleMatch(size_t module_id) {
-  base::UmaHistogramSparse("InstallVerifier.ModuleMatch", module_id);
-}
-
-base::FilePath GetExeFilePathForProcess(const base::Process& process) {
-  wchar_t exe_name[MAX_PATH];
-  DWORD exe_name_len = arraysize(exe_name);
-  // Note: requesting the Win32 path format.
-  if (::QueryFullProcessImageName(process.Handle(), 0, exe_name,
-                                  &exe_name_len) == 0) {
-    DPLOG(ERROR) << "Failed to get executable name for process";
-    return base::FilePath();
-  }
-
-  // QueryFullProcessImageName's documentation does not specify behavior when
-  // the buffer is too small, but we know that GetModuleFileNameEx succeeds and
-  // truncates the returned name in such a case. Given that paths of arbitrary
-  // length may exist, the conservative approach is to reject names when
-  // the returned length is that of the buffer.
-  if (exe_name_len > 0 && exe_name_len < arraysize(exe_name))
-    return base::FilePath(exe_name);
-
-  return base::FilePath();
-}
-
-void ReportParentProcessName() {
-  base::ProcessId ppid =
-      base::GetParentProcessId(base::GetCurrentProcessHandle());
-
-  base::Process process(
-      base::Process::OpenWithAccess(ppid, PROCESS_QUERY_LIMITED_INFORMATION));
-
-  uint32_t hash = 0U;
-
-  if (process.IsValid()) {
-    base::FilePath path(GetExeFilePathForProcess(process));
-
-    if (!path.empty()) {
-      std::string ascii_path(base::SysWideToUTF8(path.BaseName().value()));
-      DCHECK(base::IsStringASCII(ascii_path));
-      hash = variations::HashName(base::ToLowerASCII(ascii_path));
-    }
-  }
-
-  base::UmaHistogramSparse("Windows.ParentProcessNameHash", hash);
-}
-
-}  // namespace
-
-void VerifyInstallation() {
-  ReportParentProcessName();
-  ModuleIDs module_ids;
-  LoadModuleIDs(&module_ids);
-  std::set<ModuleInfo> loaded_modules;
-  if (GetLoadedModules(&loaded_modules)) {
-    VerifyLoadedModules(loaded_modules, module_ids, &ReportModuleMatch);
-  }
-}
diff --git a/chrome/browser/install_verification/win/install_verification.h b/chrome/browser/install_verification/win/install_verification.h
deleted file mode 100644
index 64f6c42..0000000
--- a/chrome/browser/install_verification/win/install_verification.h
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_INSTALL_VERIFICATION_WIN_INSTALL_VERIFICATION_H_
-#define CHROME_BROWSER_INSTALL_VERIFICATION_WIN_INSTALL_VERIFICATION_H_
-
-// Starts a background process to verify the Chrome installation. Results are
-// reported via UMA.
-void VerifyInstallation();
-
-#endif  // CHROME_BROWSER_INSTALL_VERIFICATION_WIN_INSTALL_VERIFICATION_H_
diff --git a/chrome/browser/install_verification/win/loaded_module_verification.cc b/chrome/browser/install_verification/win/loaded_module_verification.cc
deleted file mode 100644
index da6253a68..0000000
--- a/chrome/browser/install_verification/win/loaded_module_verification.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/install_verification/win/loaded_module_verification.h"
-
-#include <algorithm>
-#include <iterator>
-#include <string>
-#include "chrome/browser/install_verification/win/module_ids.h"
-#include "chrome/browser/install_verification/win/module_info.h"
-
-namespace {
-
-std::string ExtractModuleNameDigest(const ModuleInfo& module_info) {
-  return CalculateModuleNameDigest(module_info.name);
-}
-
-}  // namespace
-
-void VerifyLoadedModules(const std::set<ModuleInfo>& loaded_modules,
-                         const ModuleIDs& module_ids,
-                         ModuleVerificationDelegate* delegate) {
-  std::vector<std::string> module_name_digests;
-  std::transform(loaded_modules.begin(),
-                 loaded_modules.end(),
-                 std::back_inserter(module_name_digests),
-                 &ExtractModuleNameDigest);
-  ReportModuleMatches(module_name_digests, module_ids, delegate);
-}
diff --git a/chrome/browser/install_verification/win/loaded_module_verification.h b/chrome/browser/install_verification/win/loaded_module_verification.h
deleted file mode 100644
index 94786cc..0000000
--- a/chrome/browser/install_verification/win/loaded_module_verification.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_INSTALL_VERIFICATION_WIN_LOADED_MODULE_VERIFICATION_H_
-#define CHROME_BROWSER_INSTALL_VERIFICATION_WIN_LOADED_MODULE_VERIFICATION_H_
-
-#include <set>
-
-#include "chrome/browser/install_verification/win/module_ids.h"
-#include "chrome/browser/install_verification/win/module_verification_common.h"
-
-struct ModuleInfo;
-
-// Verifies the provided |loaded_modules| info against the expected
-// |module_ids|. Reports results to |delegate|.
-void VerifyLoadedModules(const std::set<ModuleInfo>& loaded_modules,
-                         const ModuleIDs& module_ids,
-                         ModuleVerificationDelegate* delegate);
-
-#endif  // CHROME_BROWSER_INSTALL_VERIFICATION_WIN_LOADED_MODULE_VERIFICATION_H_
diff --git a/chrome/browser/install_verification/win/loaded_module_verification_unittest.cc b/chrome/browser/install_verification/win/loaded_module_verification_unittest.cc
deleted file mode 100644
index 6cff3af..0000000
--- a/chrome/browser/install_verification/win/loaded_module_verification_unittest.cc
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/install_verification/win/loaded_module_verification.h"
-
-#include "chrome/browser/install_verification/win/module_verification_test.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-class LoadedModuleVerificationTest : public ModuleVerificationTest { };
-
-TEST_F(LoadedModuleVerificationTest, TestCase) {
-  std::set<ModuleInfo> loaded_modules;
-  ModuleIDs empty_modules_of_interest;
-  ModuleIDs non_matching_modules_of_interest;
-  ModuleIDs matching_modules_of_interest;
-
-  matching_modules_of_interest.insert(
-      std::make_pair(CalculateModuleNameDigest(L"fancy_pants.dll"), 999u));
-  matching_modules_of_interest.insert(
-      std::make_pair(CalculateModuleNameDigest(L"advapi32.dll"), 666u));
-  matching_modules_of_interest.insert(
-      std::make_pair(CalculateModuleNameDigest(L"unit_tests.exe"), 777u));
-  matching_modules_of_interest.insert(
-      std::make_pair(CalculateModuleNameDigest(L"user32.dll"), 888u));
-
-  non_matching_modules_of_interest.insert(
-      std::make_pair(CalculateModuleNameDigest(L"fancy_pants.dll"), 999u));
-
-  // With empty loaded_modules, nothing matches.
-  VerifyLoadedModules(loaded_modules,
-                      empty_modules_of_interest,
-                      &ModuleVerificationTest::ReportModule);
-  ASSERT_TRUE(reported_module_ids_.empty());
-  VerifyLoadedModules(loaded_modules,
-                      non_matching_modules_of_interest,
-                      &ModuleVerificationTest::ReportModule);
-  ASSERT_TRUE(reported_module_ids_.empty());
-  VerifyLoadedModules(loaded_modules,
-                      matching_modules_of_interest,
-                      &ModuleVerificationTest::ReportModule);
-  ASSERT_TRUE(reported_module_ids_.empty());
-
-  // With populated loaded_modules, only the 'matching' module data gives a
-  // match.
-  ASSERT_TRUE(GetLoadedModuleInfoSet(&loaded_modules));
-  VerifyLoadedModules(loaded_modules,
-                      empty_modules_of_interest,
-                      &ModuleVerificationTest::ReportModule);
-  ASSERT_TRUE(reported_module_ids_.empty());
-  VerifyLoadedModules(loaded_modules,
-                      non_matching_modules_of_interest,
-                      &ModuleVerificationTest::ReportModule);
-  ASSERT_TRUE(reported_module_ids_.empty());
-  VerifyLoadedModules(loaded_modules,
-                      matching_modules_of_interest,
-                      &ModuleVerificationTest::ReportModule);
-  ASSERT_EQ(3u, reported_module_ids_.size());
-  ASSERT_NE(reported_module_ids_.end(), reported_module_ids_.find(666u));
-  ASSERT_NE(reported_module_ids_.end(), reported_module_ids_.find(777u));
-  ASSERT_NE(reported_module_ids_.end(), reported_module_ids_.find(888u));
-}
diff --git a/chrome/browser/install_verification/win/module_ids.cc b/chrome/browser/install_verification/win/module_ids.cc
deleted file mode 100644
index 409eaab8..0000000
--- a/chrome/browser/install_verification/win/module_ids.cc
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/install_verification/win/module_ids.h"
-
-#include <utility>
-
-#include "base/logging.h"
-#include "base/macros.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/strings/string_piece.h"
-#include "base/strings/string_tokenizer.h"
-#include "base/strings/string_util.h"
-#include "chrome/grit/browser_resources.h"
-#include "ui/base/resource/resource_bundle.h"
-
-namespace {
-
-struct { size_t id; const char* module_name_digest; }
-  kExpectedInstallModules[] = {
-    {1u, "c8cc47613e155f2129f480c6ced84549"},  // chrome.dll
-    {2u, "49b78a23b0d8d5d8fb60d4e472b22764"},  // chrome_child.dll
-  };
-
-// Parses a line consisting of a positive decimal number and a 32-digit
-// hexadecimal number, separated by a space. Inserts the output, if valid, into
-// |module_ids|. Unexpected leading or trailing characters will cause
-// the line to be ignored, as will invalid decimal/hexadecimal characters.
-void ParseAdditionalModuleID(
-    const base::StringPiece& line,
-    ModuleIDs* module_ids) {
-  DCHECK(module_ids);
-
-  base::CStringTokenizer line_tokenizer(line.begin(), line.end(), " ");
-
-  if (!line_tokenizer.GetNext())
-    return;  // Empty string.
-  base::StringPiece id_piece(line_tokenizer.token_piece());
-
-  if (!line_tokenizer.GetNext())
-    return;  // No delimiter (' ').
-  base::StringPiece digest_piece(line_tokenizer.token_piece());
-
-  if (line_tokenizer.GetNext())
-    return;  // Unexpected trailing characters.
-
-  unsigned id = 0;
-  if (!StringToUint(id_piece, &id))
-    return;  // First token was not decimal.
-
-  if (digest_piece.length() != 32)
-    return;  // Second token is not the right length.
-
-  for (base::StringPiece::const_iterator it = digest_piece.begin();
-       it != digest_piece.end(); ++it) {
-    if (!base::IsHexDigit(*it))
-      return;  // Second token has invalid characters.
-  }
-
-  // This is a valid line.
-  module_ids->insert(std::make_pair(digest_piece.as_string(), id));
-}
-
-}  // namespace
-
-void ParseAdditionalModuleIDs(
-    const base::StringPiece& raw_data,
-    ModuleIDs* module_ids) {
-  DCHECK(module_ids);
-
-  base::CStringTokenizer file_tokenizer(raw_data.begin(),
-                                        raw_data.end(),
-                                        "\r\n");
-  while (file_tokenizer.GetNext()) {
-    ParseAdditionalModuleID(base::StringPiece(file_tokenizer.token_piece()),
-                            module_ids);
-  }
-}
-
-void LoadModuleIDs(ModuleIDs* module_ids) {
-  for (size_t i = 0; i < arraysize(kExpectedInstallModules); ++i) {
-    module_ids->insert(
-        std::make_pair(
-            kExpectedInstallModules[i].module_name_digest,
-            kExpectedInstallModules[i].id));
-  }
-  base::StringPiece additional_module_ids;
-#if defined(GOOGLE_CHROME_BUILD)
-  additional_module_ids =
-      ui::ResourceBundle::GetSharedInstance().GetRawDataResource(
-          IDR_ADDITIONAL_MODULE_IDS);
-#endif
-  ParseAdditionalModuleIDs(additional_module_ids, module_ids);
-}
diff --git a/chrome/browser/install_verification/win/module_ids.h b/chrome/browser/install_verification/win/module_ids.h
deleted file mode 100644
index e02373bd..0000000
--- a/chrome/browser/install_verification/win/module_ids.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_INSTALL_VERIFICATION_WIN_MODULE_IDS_H_
-#define CHROME_BROWSER_INSTALL_VERIFICATION_WIN_MODULE_IDS_H_
-
-#include <stddef.h>
-
-#include <map>
-#include <string>
-
-#include "base/strings/string_piece.h"
-
-typedef std::map<std::string, size_t> ModuleIDs;
-
-// Parses a list of additional modules to verify. The data format is a series of
-// lines. Each line starts with a decimal ID, then a module name digest,
-// separated by a space. Lines are terminated by \r and/or \n. Invalid lines are
-// ignored.
-//
-// The result is a map of module name digests to module IDs.
-void ParseAdditionalModuleIDs(
-    const base::StringPiece& raw_data,
-    ModuleIDs* module_ids);
-
-// Loads standard module IDs and additional module IDs from a resource.
-void LoadModuleIDs(ModuleIDs* module_ids);
-
-#endif  // CHROME_BROWSER_INSTALL_VERIFICATION_WIN_MODULE_IDS_H_
diff --git a/chrome/browser/install_verification/win/module_ids_unittest.cc b/chrome/browser/install_verification/win/module_ids_unittest.cc
deleted file mode 100644
index 55b1f0c..0000000
--- a/chrome/browser/install_verification/win/module_ids_unittest.cc
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/install_verification/win/module_ids.h"
-
-#include "base/strings/string_piece.h"
-#include "chrome/browser/install_verification/win/module_verification_common.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-TEST(ModuleIDsTest, LoadModuleIDs) {
-  ModuleIDs module_ids;
-  LoadModuleIDs(&module_ids);
-  ASSERT_EQ(1u, module_ids[CalculateModuleNameDigest(L"chrome.dll")]);
-}
-
-TEST(ModuleIDsTest, ParseAdditionalModuleIDs) {
-  ModuleIDs module_ids;
-
-  ParseAdditionalModuleIDs(base::StringPiece(), &module_ids);
-  ASSERT_EQ(0u, module_ids.size());
-
-  // Invalid input.
-  ParseAdditionalModuleIDs("hello", &module_ids);
-  ASSERT_EQ(0u, module_ids.size());
-  ParseAdditionalModuleIDs("hello world", &module_ids);
-  ASSERT_EQ(0u, module_ids.size());
-  ParseAdditionalModuleIDs("hello world\nfoo bar", &module_ids);
-  ASSERT_EQ(0u, module_ids.size());
-  ParseAdditionalModuleIDs("123 world\nfoo bar", &module_ids);
-  ASSERT_EQ(0u, module_ids.size());
-  ParseAdditionalModuleIDs("hello 0123456789abcdef0123456789abcdef\nfoo bar",
-                           &module_ids);
-  ASSERT_EQ(0u, module_ids.size());
-
-  // A single valid line followed by an invalid line.
-  ParseAdditionalModuleIDs("123 0123456789abcdef0123456789abcdef\nfoo bar",
-                         &module_ids);
-  ASSERT_EQ(1u, module_ids.size());
-  ASSERT_EQ(123u, module_ids.begin()->second);
-  ASSERT_EQ("0123456789abcdef0123456789abcdef", module_ids.begin()->first);
-  module_ids.clear();
-
-  // The same, but with \r\n.
-  ParseAdditionalModuleIDs("123 0123456789abcdef0123456789abcdef\r\nfoo bar",
-                           &module_ids);
-  ASSERT_EQ(1u, module_ids.size());
-  ASSERT_EQ(123u, module_ids.begin()->second);
-  ASSERT_EQ("0123456789abcdef0123456789abcdef", module_ids.begin()->first);
-  module_ids.clear();
-
-  // Several valid and invalid lines, with varying line terminations etc.
-  ParseAdditionalModuleIDs("123 0123456789abcdef0123456789abcdef\r\n"
-                           "456 DEADBEEFDEADBEEF\r"
-                           "789 DEADBEEFDEADBEEFDEADBEEFDEADBEEF\n"
-                           "\n"
-                           "\n"
-                           "321 BAADCAFEBAADCAFEBAADCAFEBAADCAFE\n",
-                           &module_ids);
-  ASSERT_EQ(3u, module_ids.size());
-  ASSERT_EQ(123u, module_ids["0123456789abcdef0123456789abcdef"]);
-  ASSERT_EQ(789u, module_ids["DEADBEEFDEADBEEFDEADBEEFDEADBEEF"]);
-  ASSERT_EQ(321u, module_ids["BAADCAFEBAADCAFEBAADCAFEBAADCAFE"]);
-  module_ids.clear();
-
-  // Leading empty lines, no termination on final line.
-  ParseAdditionalModuleIDs("\n"
-                           "123 0123456789abcdef0123456789abcdef\r\n"
-                           "321 BAADCAFEBAADCAFEBAADCAFEBAADCAFE",
-                           &module_ids);
-  ASSERT_EQ(2u, module_ids.size());
-  ASSERT_EQ(123u, module_ids["0123456789abcdef0123456789abcdef"]);
-  ASSERT_EQ(321u, module_ids["BAADCAFEBAADCAFEBAADCAFEBAADCAFE"]);
-  module_ids.clear();
-}
diff --git a/chrome/browser/install_verification/win/module_verification_common.cc b/chrome/browser/install_verification/win/module_verification_common.cc
index e8a3dcf..4d8e3d6 100644
--- a/chrome/browser/install_verification/win/module_verification_common.cc
+++ b/chrome/browser/install_verification/win/module_verification_common.cc
@@ -12,11 +12,6 @@
 #include "chrome/browser/install_verification/win/module_info.h"
 #include "chrome/browser/install_verification/win/module_list.h"
 
-std::string CalculateModuleNameDigest(const base::string16& module_name) {
-  return base::MD5String(base::ToLowerASCII(base::UTF16ToUTF8(
-      base::FilePath(module_name).BaseName().value())));
-}
-
 bool GetLoadedModules(std::set<ModuleInfo>* loaded_modules) {
   std::vector<HMODULE> snapshot;
   if (!base::win::GetLoadedModulesSnapshot(::GetCurrentProcess(), &snapshot))
@@ -26,13 +21,3 @@
       loaded_modules);
   return true;
 }
-
-void ReportModuleMatches(const std::vector<std::string>& module_name_digests,
-                         const ModuleIDs& module_ids,
-                         ModuleVerificationDelegate* delegate) {
-  for (size_t i = 0; i < module_name_digests.size(); ++i) {
-    ModuleIDs::const_iterator entry = module_ids.find(module_name_digests[i]);
-    if (entry != module_ids.end())
-      delegate(entry->second);
-  }
-}
diff --git a/chrome/browser/install_verification/win/module_verification_common.h b/chrome/browser/install_verification/win/module_verification_common.h
index de061a4..64021f1 100644
--- a/chrome/browser/install_verification/win/module_verification_common.h
+++ b/chrome/browser/install_verification/win/module_verification_common.h
@@ -11,25 +11,11 @@
 #include <string>
 #include <vector>
 #include "base/strings/string16.h"
-#include "chrome/browser/install_verification/win/module_ids.h"
 
 struct ModuleInfo;
 
-// Calculates a canonical digest for |module_name|. Ignores case and strips path
-// information if present.
-std::string CalculateModuleNameDigest(const base::string16& module_name);
-
 // Retrieves a ModuleInfo set representing all currenly loaded modules. Returns
 // false in case of failure.
 bool GetLoadedModules(std::set<ModuleInfo>* loaded_modules);
 
-// Receives notification of a module verification result.
-typedef void (ModuleVerificationDelegate)(size_t module_id);
-
-// For each module in |module_name_digests|, reports the associated ID from
-// |module_ids|, if any, to |delegate|.
-void ReportModuleMatches(const std::vector<std::string>& module_name_digests,
-                         const ModuleIDs& module_ids,
-                         ModuleVerificationDelegate* delegate);
-
 #endif  // CHROME_BROWSER_INSTALL_VERIFICATION_WIN_MODULE_VERIFICATION_COMMON_H_
diff --git a/chrome/browser/interstitials/enterprise_util.cc b/chrome/browser/interstitials/enterprise_util.cc
index 4189404..bb158b5 100644
--- a/chrome/browser/interstitials/enterprise_util.cc
+++ b/chrome/browser/interstitials/enterprise_util.cc
@@ -81,7 +81,7 @@
     case safe_browsing::SB_THREAT_TYPE_URL_CLIENT_SIDE_MALWARE:
       return "MALWARE";
     case safe_browsing::SB_THREAT_TYPE_URL_UNWANTED:
-    case safe_browsing::SB_THREAT_TYPE_TRICK_TO_BILL:
+    case safe_browsing::SB_THREAT_TYPE_BILLING:
       return "HARMFUL";
     case safe_browsing::SB_THREAT_TYPE_UNUSED:
     case safe_browsing::SB_THREAT_TYPE_SAFE:
diff --git a/chrome/browser/media/capture_access_handler_base.cc b/chrome/browser/media/capture_access_handler_base.cc
index c9ed70b..7b6fd50 100644
--- a/chrome/browser/media/capture_access_handler_base.cc
+++ b/chrome/browser/media/capture_access_handler_base.cc
@@ -73,8 +73,8 @@
     content::MediaStreamType stream_type,
     content::MediaRequestState state) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  if ((stream_type != content::MEDIA_DESKTOP_VIDEO_CAPTURE) &&
-      (stream_type != content::MEDIA_TAB_VIDEO_CAPTURE))
+  if ((stream_type != content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE) &&
+      (stream_type != content::MEDIA_GUM_TAB_VIDEO_CAPTURE))
     return;
 
   if (state == content::MEDIA_REQUEST_STATE_DONE) {
diff --git a/chrome/browser/media/cast_mirroring_service_host.cc b/chrome/browser/media/cast_mirroring_service_host.cc
index 5ae2b87..3da2353 100644
--- a/chrome/browser/media/cast_mirroring_service_host.cc
+++ b/chrome/browser/media/cast_mirroring_service_host.cc
@@ -49,10 +49,10 @@
     case content::DesktopMediaID::TYPE_NONE:
       return content::MediaStreamType::MEDIA_NO_SERVICE;
     case content::DesktopMediaID::TYPE_WEB_CONTENTS:
-      return content::MediaStreamType::MEDIA_TAB_VIDEO_CAPTURE;
+      return content::MediaStreamType::MEDIA_GUM_TAB_VIDEO_CAPTURE;
     case content::DesktopMediaID::TYPE_SCREEN:
     case content::DesktopMediaID::TYPE_WINDOW:
-      return content::MediaStreamType::MEDIA_DESKTOP_VIDEO_CAPTURE;
+      return content::MediaStreamType::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE;
   }
 
   // To suppress compiler warning on Windows.
diff --git a/chrome/browser/media/public_session_tab_capture_access_handler.cc b/chrome/browser/media/public_session_tab_capture_access_handler.cc
index 29665bb6..09918f0 100644
--- a/chrome/browser/media/public_session_tab_capture_access_handler.cc
+++ b/chrome/browser/media/public_session_tab_capture_access_handler.cc
@@ -46,8 +46,8 @@
   // This class handles requests for Public Sessions only, outside of them just
   // pass the request through to the original class.
   if (!profiles::IsPublicSession() || !extension ||
-      (request.audio_type != content::MEDIA_TAB_AUDIO_CAPTURE &&
-       request.video_type != content::MEDIA_TAB_VIDEO_CAPTURE)) {
+      (request.audio_type != content::MEDIA_GUM_TAB_AUDIO_CAPTURE &&
+       request.video_type != content::MEDIA_GUM_TAB_VIDEO_CAPTURE)) {
     return tab_capture_access_handler_.HandleRequest(
         web_contents, request, std::move(callback), extension);
   }
diff --git a/chrome/browser/media/router/media_router_feature.cc b/chrome/browser/media/router/media_router_feature.cc
index 3bfb3c0..492f02b 100644
--- a/chrome/browser/media/router/media_router_feature.cc
+++ b/chrome/browser/media/router/media_router_feature.cc
@@ -9,6 +9,7 @@
 #include "base/strings/string_util.h"
 #include "build/build_config.h"
 #include "chrome/browser/profiles/profile.h"
+#include "components/mirroring/service/features.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/common/content_features.h"
 #include "crypto/random.h"
@@ -151,7 +152,7 @@
 }
 
 bool ShouldUseMirroringService() {
-  return base::FeatureList::IsEnabled(features::kMirroringService) &&
+  return base::FeatureList::IsEnabled(mirroring::features::kMirroringService) &&
          base::FeatureList::IsEnabled(features::kAudioServiceAudioStreams) &&
          base::FeatureList::IsEnabled(features::kAudioServiceOutOfProcess) &&
          base::FeatureList::IsEnabled(network::features::kNetworkService);
diff --git a/chrome/browser/media/webrtc/desktop_capture_access_handler.cc b/chrome/browser/media/webrtc/desktop_capture_access_handler.cc
index 316a264..8061f241 100644
--- a/chrome/browser/media/webrtc/desktop_capture_access_handler.cc
+++ b/chrome/browser/media/webrtc/desktop_capture_access_handler.cc
@@ -167,25 +167,25 @@
 
   // Add selected desktop source to the list.
   devices->push_back(
-      content::MediaStreamDevice(content::MEDIA_DESKTOP_VIDEO_CAPTURE,
+      content::MediaStreamDevice(content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE,
                                  media_id.ToString(), media_id.ToString()));
   if (capture_audio) {
     if (media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS) {
       content::WebContentsMediaCaptureId web_id = media_id.web_contents_id;
       web_id.disable_local_echo = disable_local_echo;
       devices->push_back(
-          content::MediaStreamDevice(content::MEDIA_DESKTOP_AUDIO_CAPTURE,
+          content::MediaStreamDevice(content::MEDIA_GUM_DESKTOP_AUDIO_CAPTURE,
                                      web_id.ToString(), "Tab audio"));
     } else if (disable_local_echo) {
       // Use the special loopback device ID for system audio capture.
       devices->push_back(content::MediaStreamDevice(
-          content::MEDIA_DESKTOP_AUDIO_CAPTURE,
+          content::MEDIA_GUM_DESKTOP_AUDIO_CAPTURE,
           media::AudioDeviceDescription::kLoopbackWithMuteDeviceId,
           "System Audio"));
     } else {
       // Use the special loopback device ID for system audio capture.
       devices->push_back(content::MediaStreamDevice(
-          content::MEDIA_DESKTOP_AUDIO_CAPTURE,
+          content::MEDIA_GUM_DESKTOP_AUDIO_CAPTURE,
           media::AudioDeviceDescription::kLoopbackInputDeviceId,
           "System Audio"));
     }
@@ -242,7 +242,7 @@
   content::MediaStreamDevices devices;
   std::unique_ptr<content::MediaStreamUI> ui;
 
-  DCHECK_EQ(request.video_type, content::MEDIA_DESKTOP_VIDEO_CAPTURE);
+  DCHECK_EQ(request.video_type, content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE);
 
   UpdateExtensionTrusted(request, extension);
 
@@ -334,7 +334,7 @@
 #endif  // !defined(OS_CHROMEOS)
 
       bool capture_audio =
-          (request.audio_type == content::MEDIA_DESKTOP_AUDIO_CAPTURE &&
+          (request.audio_type == content::MEDIA_GUM_DESKTOP_AUDIO_CAPTURE &&
            loopback_audio_supported);
 
       // Determine if the extension is required to display a notification.
@@ -368,8 +368,8 @@
     content::WebContents* web_contents,
     const content::MediaStreamType type,
     const extensions::Extension* extension) {
-  return type == content::MEDIA_DESKTOP_VIDEO_CAPTURE ||
-         type == content::MEDIA_DESKTOP_AUDIO_CAPTURE;
+  return type == content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE ||
+         type == content::MEDIA_GUM_DESKTOP_AUDIO_CAPTURE;
 }
 
 bool DesktopCaptureAccessHandler::CheckMediaAccessPermission(
@@ -388,7 +388,7 @@
   content::MediaStreamDevices devices;
   std::unique_ptr<content::MediaStreamUI> ui;
 
-  if (request.video_type != content::MEDIA_DESKTOP_VIDEO_CAPTURE) {
+  if (request.video_type != content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE) {
     std::move(callback).Run(devices, content::MEDIA_DEVICE_INVALID_STATE,
                             std::move(ui));
     return;
@@ -442,7 +442,7 @@
 
   // This value essentially from whether getUserMedia requests audio stream.
   const bool audio_requested =
-      request.audio_type == content::MEDIA_DESKTOP_AUDIO_CAPTURE;
+      request.audio_type == content::MEDIA_GUM_DESKTOP_AUDIO_CAPTURE;
 
   // This value shows for a given capture type, whether the system or our code
   // can support audio sharing. Currently audio is only supported for screen and
diff --git a/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc b/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc
index 24ac5db..52f5d57 100644
--- a/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc
+++ b/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc
@@ -423,10 +423,10 @@
   for (const auto& handler : media_access_handlers_) {
     if (handler->SupportsStreamType(
             WebContentsFromIds(render_process_id, render_frame_id),
-            content::MEDIA_DESKTOP_VIDEO_CAPTURE, nullptr) ||
+            content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE, nullptr) ||
         handler->SupportsStreamType(
             WebContentsFromIds(render_process_id, render_frame_id),
-            content::MEDIA_TAB_VIDEO_CAPTURE, nullptr)) {
+            content::MEDIA_GUM_TAB_VIDEO_CAPTURE, nullptr)) {
       if (ToCaptureAccessHandlerBase(handler.get())
               ->IsInsecureCapturingInProgress(render_process_id,
                                               render_frame_id))
@@ -454,8 +454,8 @@
     content::MediaStreamType stream_type,
     bool is_secure) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  if (stream_type != content::MEDIA_TAB_VIDEO_CAPTURE &&
-      stream_type != content::MEDIA_DESKTOP_VIDEO_CAPTURE)
+  if (stream_type != content::MEDIA_GUM_TAB_VIDEO_CAPTURE &&
+      stream_type != content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE)
     return;
 
   BrowserThread::PostTask(
@@ -472,8 +472,8 @@
     content::MediaStreamType stream_type,
     bool is_secure) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  if (stream_type != content::MEDIA_TAB_VIDEO_CAPTURE &&
-      stream_type != content::MEDIA_DESKTOP_VIDEO_CAPTURE)
+  if (stream_type != content::MEDIA_GUM_TAB_VIDEO_CAPTURE &&
+      stream_type != content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE)
     return;
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
diff --git a/chrome/browser/media/webrtc/media_stream_capture_indicator.cc b/chrome/browser/media/webrtc/media_stream_capture_indicator.cc
index 69c409d..866984f 100644
--- a/chrome/browser/media/webrtc/media_stream_capture_indicator.cc
+++ b/chrome/browser/media/webrtc/media_stream_capture_indicator.cc
@@ -229,12 +229,12 @@
     case content::MEDIA_DEVICE_VIDEO_CAPTURE:
       return video_stream_count_;
 
-    case content::MEDIA_TAB_AUDIO_CAPTURE:
-    case content::MEDIA_TAB_VIDEO_CAPTURE:
+    case content::MEDIA_GUM_TAB_AUDIO_CAPTURE:
+    case content::MEDIA_GUM_TAB_VIDEO_CAPTURE:
       return mirroring_stream_count_;
 
-    case content::MEDIA_DESKTOP_VIDEO_CAPTURE:
-    case content::MEDIA_DESKTOP_AUDIO_CAPTURE:
+    case content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE:
+    case content::MEDIA_GUM_DESKTOP_AUDIO_CAPTURE:
       return desktop_stream_count_;
 
     default:
diff --git a/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc b/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
index 8dc8bee..3595a406 100644
--- a/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
+++ b/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
@@ -69,7 +69,7 @@
 #if defined(OS_ANDROID)
   return type == content::MEDIA_DEVICE_VIDEO_CAPTURE ||
          type == content::MEDIA_DEVICE_AUDIO_CAPTURE ||
-         type == content::MEDIA_DESKTOP_VIDEO_CAPTURE;
+         type == content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE;
 #else
   return type == content::MEDIA_DEVICE_VIDEO_CAPTURE ||
          type == content::MEDIA_DEVICE_AUDIO_CAPTURE;
@@ -107,7 +107,7 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
 #if defined(OS_ANDROID)
-  if (request.video_type == content::MEDIA_DESKTOP_VIDEO_CAPTURE &&
+  if (request.video_type == content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE &&
       !base::FeatureList::IsEnabled(
           chrome::android::kUserMediaScreenCapturing)) {
     // If screen capturing isn't enabled on Android, we'll use "invalid state"
@@ -143,7 +143,7 @@
 
   const content::MediaStreamRequest request = it->second.front().request;
 #if defined(OS_ANDROID)
-  if (request.video_type == content::MEDIA_DESKTOP_VIDEO_CAPTURE) {
+  if (request.video_type == content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE) {
     ScreenCaptureInfoBarDelegateAndroid::Create(
         web_contents, request,
         base::Bind(&PermissionBubbleMediaAccessHandler::OnAccessRequestResponse,
diff --git a/chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.cc b/chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.cc
index 1051293..990ec42 100644
--- a/chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.cc
+++ b/chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.cc
@@ -39,7 +39,7 @@
     : web_contents_(web_contents),
       request_(request),
       callback_(std::move(callback)) {
-  DCHECK_EQ(content::MEDIA_DESKTOP_VIDEO_CAPTURE, request.video_type);
+  DCHECK_EQ(content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE, request.video_type);
 }
 
 ScreenCaptureInfoBarDelegateAndroid::~ScreenCaptureInfoBarDelegateAndroid() {
@@ -94,8 +94,9 @@
   if (result == content::MEDIA_DEVICE_OK) {
     content::DesktopMediaID screen_id = content::DesktopMediaID(
         content::DesktopMediaID::TYPE_SCREEN, webrtc::kFullDesktopScreenId);
-    devices.push_back(content::MediaStreamDevice(
-        content::MEDIA_DESKTOP_VIDEO_CAPTURE, screen_id.ToString(), "Screen"));
+    devices.push_back(
+        content::MediaStreamDevice(content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE,
+                                   screen_id.ToString(), "Screen"));
 
     ui = MediaCaptureDevicesDispatcher::GetInstance()
              ->GetMediaStreamCaptureIndicator()
diff --git a/chrome/browser/media/webrtc/tab_capture_access_handler.cc b/chrome/browser/media/webrtc/tab_capture_access_handler.cc
index 4a2aaa23..1034a8d 100644
--- a/chrome/browser/media/webrtc/tab_capture_access_handler.cc
+++ b/chrome/browser/media/webrtc/tab_capture_access_handler.cc
@@ -23,8 +23,8 @@
     content::WebContents* web_contents,
     const content::MediaStreamType type,
     const extensions::Extension* extension) {
-  return type == content::MEDIA_TAB_VIDEO_CAPTURE ||
-         type == content::MEDIA_TAB_AUDIO_CAPTURE;
+  return type == content::MEDIA_GUM_TAB_VIDEO_CAPTURE ||
+         type == content::MEDIA_GUM_TAB_AUDIO_CAPTURE;
 }
 
 bool TabCaptureAccessHandler::CheckMediaAccessPermission(
@@ -62,20 +62,20 @@
   const bool tab_capture_allowed = tab_capture_registry->VerifyRequest(
       request.render_process_id, request.render_frame_id, extension->id());
 
-  if (request.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE &&
+  if (request.audio_type == content::MEDIA_GUM_TAB_AUDIO_CAPTURE &&
       tab_capture_allowed &&
       extension->permissions_data()->HasAPIPermission(
           extensions::APIPermission::kTabCapture)) {
     devices.push_back(content::MediaStreamDevice(
-        content::MEDIA_TAB_AUDIO_CAPTURE, std::string(), std::string()));
+        content::MEDIA_GUM_TAB_AUDIO_CAPTURE, std::string(), std::string()));
   }
 
-  if (request.video_type == content::MEDIA_TAB_VIDEO_CAPTURE &&
+  if (request.video_type == content::MEDIA_GUM_TAB_VIDEO_CAPTURE &&
       tab_capture_allowed &&
       extension->permissions_data()->HasAPIPermission(
           extensions::APIPermission::kTabCapture)) {
     devices.push_back(content::MediaStreamDevice(
-        content::MEDIA_TAB_VIDEO_CAPTURE, std::string(), std::string()));
+        content::MEDIA_GUM_TAB_VIDEO_CAPTURE, std::string(), std::string()));
   }
 
   if (!devices.empty()) {
diff --git a/chrome/browser/policy/policy_browsertest.cc b/chrome/browser/policy/policy_browsertest.cc
index 7dc43c3..4ce40a1 100644
--- a/chrome/browser/policy/policy_browsertest.cc
+++ b/chrome/browser/policy/policy_browsertest.cc
@@ -97,6 +97,8 @@
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/location_bar/location_bar.h"
 #include "chrome/browser/ui/permission_bubble/mock_permission_prompt_factory.h"
+#include "chrome/browser/ui/search/instant_test_utils.h"
+#include "chrome/browser/ui/search/local_ntp_test_utils.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/toolbar/component_toolbar_actions_factory.h"
 #include "chrome/browser/ui/toolbar/media_router_action_controller.h"
@@ -113,7 +115,9 @@
 #include "chrome/common/pref_names.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/common/web_application_info.h"
+#include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/generated_resources.h"
+#include "chrome/grit/locale_settings.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/search_test_utils.h"
 #include "chrome/test/base/testing_profile.h"
@@ -353,6 +357,14 @@
       base::PathService::Get(chrome::DIR_TEST_DATA, test_data_directory));
 }
 
+content::RenderFrameHost* GetMostVisitedIframe(content::WebContents* tab) {
+  for (content::RenderFrameHost* frame : tab->GetAllFrames()) {
+    if (frame->GetFrameName() == "mv-single")
+      return frame;
+  }
+  return nullptr;
+}
+
 // Filters requests to the hosts in |urls| and redirects them to the test data
 // dir through URLRequestMockHTTPJobs.
 void RedirectHostsToTestData(const char* const urls[], size_t size) {
@@ -598,6 +610,16 @@
   return result;
 }
 
+bool ContainsWebstoreTile(content::RenderFrameHost* iframe) {
+  int num_webstore_tiles = 0;
+  EXPECT_TRUE(instant_test_utils::GetIntFromJS(
+      iframe,
+      "document.querySelectorAll(\".md-tile[href='" +
+          l10n_util::GetStringUTF8(IDS_WEBSTORE_URL) + "']\").length",
+      &num_webstore_tiles));
+  return num_webstore_tiles == 1;
+}
+
 #if defined(OS_CHROMEOS)
 class TestAudioObserver : public chromeos::CrasAudioHandler::AudioObserver {
  public:
@@ -2037,12 +2059,30 @@
   EXPECT_TRUE(is_toggle_dev_mode_checkbox_disabled);
 }
 
-// TODO(samarth): remove along with rest of NTP4 code.
-IN_PROC_BROWSER_TEST_F(PolicyTest, DISABLED_WebStoreIconHidden) {
-  // Verifies that the web store icons can be hidden from the new tab page.
+IN_PROC_BROWSER_TEST_F(PolicyTest, NTPWebStoreIconShown) {
+  // This test is to verify that the web store icons is shown when no policy
+  // applies. See WebStoreIconPolicyTest.NTPWebStoreIconHidden for verification
+  // when a policy is in effect.
 
   // Open new tab page and look for the web store icons.
-  ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUINewTabURL));
+  content::WebContents* active_tab =
+      local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
+  local_ntp_test_utils::NavigateToNTPAndWaitUntilLoaded(browser());
+
+  content::RenderFrameHost* iframe = GetMostVisitedIframe(active_tab);
+
+  // Look though all the tiles and see whether there is a webstore icon.
+  // Make sure that there is one web store icon.
+  EXPECT_TRUE(ContainsWebstoreTile(iframe));
+}
+
+IN_PROC_BROWSER_TEST_F(PolicyTest, AppsWebStoreIconHidden) {
+  // Verifies that the web store icon can be hidden from the chrome://apps
+  // page. A policy change takes immediate effect on the apps page for the
+  // current profile. Browser restart is not required.
+
+  // Open new tab page and look for the web store icons.
+  ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUIAppsURL));
   content::WebContents* contents =
     browser()->tab_strip_model()->GetActiveWebContents();
 
@@ -2064,7 +2104,7 @@
   UpdateProviderPolicy(policies);
 
   // The web store icons should now be hidden.
-  ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUINewTabURL));
+  ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUIAppsURL));
   EXPECT_FALSE(ContainsVisibleElement(contents,
                                       "ahfgeienlihckogmohjhadlkjgocpleb"));
   EXPECT_FALSE(ContainsVisibleElement(contents, "chrome-web-store-link"));
@@ -4062,6 +4102,43 @@
   EXPECT_GT(samples->GetCount(82), 0);
 }
 
+// Similar to PolicyTest, but applies the HideWebStoreIcon policy before
+// the browser is started. This is required because the list that includes the
+// WebStoreIcon on the NTP is initialized at browser start.
+class PolicyWebStoreIconTest : public PolicyTest {
+ public:
+  PolicyWebStoreIconTest() {}
+  ~PolicyWebStoreIconTest() override {}
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PolicyTest::SetUpInProcessBrowserTestFixture();
+    PolicyMap policies;
+    policies.Set(key::kHideWebStoreIcon, POLICY_LEVEL_MANDATORY,
+                 POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+                 std::make_unique<base::Value>(true), nullptr);
+    provider_.UpdateChromePolicy(policies);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PolicyWebStoreIconTest);
+};
+
+IN_PROC_BROWSER_TEST_F(PolicyWebStoreIconTest, NTPWebStoreIconHidden) {
+  // Verifies that the web store icon can be hidden from the new tab page. Check
+  // to see NTPWebStoreIconShown for behavior when the policy is not applied.
+
+  // Open new tab page and look for the web store icon
+  content::WebContents* active_tab =
+      local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
+  local_ntp_test_utils::NavigateToNTPAndWaitUntilLoaded(browser());
+
+  content::RenderFrameHost* iframe = GetMostVisitedIframe(active_tab);
+
+  // Applying the policy before the browser started, the web store icon should
+  // now be hidden.
+  EXPECT_FALSE(ContainsWebstoreTile(iframe));
+}
+
 class MediaStreamDevicesControllerBrowserTest
     : public PolicyTest,
       public testing::WithParamInterface<bool> {
diff --git a/chrome/browser/profiles/profile.h b/chrome/browser/profiles/profile.h
index 26616f4..b9d4660 100644
--- a/chrome/browser/profiles/profile.h
+++ b/chrome/browser/profiles/profile.h
@@ -318,7 +318,8 @@
 
   // Returns whether the profile is new.  A profile is new if the browser has
   // not been shut down since the profile was created.
-  bool IsNewProfile();
+  // This method is virtual in order to be overridden for tests.
+  virtual bool IsNewProfile();
 
   // Checks whether sync is configurable by the user. Returns false if sync is
   // disallowed by the command line or controlled by configuration management.
diff --git a/chrome/browser/profiles/profile_manager.cc b/chrome/browser/profiles/profile_manager.cc
index fdc32ea..ae7ade8 100644
--- a/chrome/browser/profiles/profile_manager.cc
+++ b/chrome/browser/profiles/profile_manager.cc
@@ -997,7 +997,8 @@
     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) {
+    const bool profile_is_new = profile->IsNewProfile();
+    if (!profile_is_new && profile_is_child != user_is_child) {
       ProfileAttributesEntry* entry;
       if (storage.GetProfileAttributesWithPath(profile->GetPath(), &entry)) {
         LOG(WARNING) << "Profile child status has changed.";
diff --git a/chrome/browser/profiles/profile_manager_unittest.cc b/chrome/browser/profiles/profile_manager_unittest.cc
index f752528..07eb826 100644
--- a/chrome/browser/profiles/profile_manager_unittest.cc
+++ b/chrome/browser/profiles/profile_manager_unittest.cc
@@ -60,6 +60,8 @@
 #include "chrome/browser/ui/ash/test_wallpaper_controller.h"
 #include "chrome/browser/ui/ash/wallpaper_controller_client.h"
 #include "chromeos/chromeos_switches.h"
+#include "components/arc/arc_prefs.h"
+#include "components/arc/arc_supervision_transition.h"
 #include "components/user_manager/scoped_user_manager.h"
 #include "components/user_manager/user_manager.h"
 #include "components/user_manager/user_names.h"
@@ -247,7 +249,6 @@
   base::ScopedTempDir temp_dir_;
   ScopedTestingLocalState local_state_;
 
-
 #if defined(OS_CHROMEOS)
   chromeos::ScopedTestUserManager test_user_manager_;
   std::unique_ptr<base::AutoReset<extensions::FeatureSessionType>>
@@ -808,6 +809,90 @@
   EXPECT_EQ(avatar_index, entry->GetAvatarIconIndex());
 }
 
+#if defined(OS_CHROMEOS)
+TEST_F(ProfileManagerTest, InitProfileForChildOnFirstSignIn) {
+  chromeos::ProfileHelper* profile_helper = chromeos::ProfileHelper::Get();
+  user_manager::UserManager* user_manager = user_manager::UserManager::Get();
+
+  const std::string user_email = "child@example.com";
+  const AccountId account_id = AccountId::FromUserEmailGaiaId(user_email, "1");
+  const std::string user_id_hash =
+      profile_helper->GetUserIdHashByUserIdForTesting(user_email);
+  const base::FilePath dest_path =
+      profile_helper->GetProfilePathByUserIdHash(user_id_hash);
+
+  TestingProfile::Builder builder;
+  builder.SetPath(dest_path);
+  builder.OverrideIsNewProfile(true);
+  std::unique_ptr<Profile> profile = builder.Build();
+
+  user_manager->UserLoggedIn(account_id, user_id_hash,
+                             false /* browser_restart */, true /* is_child */);
+  g_browser_process->profile_manager()->InitProfileUserPrefs(profile.get());
+
+  EXPECT_EQ(
+      profile->GetPrefs()->GetInteger(arc::prefs::kArcSupervisionTransition),
+      static_cast<int>(arc::ArcSupervisionTransition::NO_TRANSITION));
+  EXPECT_EQ(profile->GetPrefs()->GetString(prefs::kSupervisedUserId),
+            supervised_users::kChildAccountSUID);
+}
+
+TEST_F(ProfileManagerTest, InitProfileForRegularToChildTransition) {
+  chromeos::ProfileHelper* profile_helper = chromeos::ProfileHelper::Get();
+  user_manager::UserManager* user_manager = user_manager::UserManager::Get();
+
+  const std::string user_email = "child@example.com";
+  const AccountId account_id = AccountId::FromUserEmailGaiaId(user_email, "1");
+  const std::string user_id_hash =
+      profile_helper->GetUserIdHashByUserIdForTesting(user_email);
+  const base::FilePath dest_path =
+      profile_helper->GetProfilePathByUserIdHash(user_id_hash);
+
+  TestingProfile::Builder builder;
+  builder.SetPath(dest_path);
+  builder.OverrideIsNewProfile(false);
+  std::unique_ptr<Profile> profile = builder.Build();
+
+  user_manager->UserLoggedIn(account_id, user_id_hash,
+                             false /* browser_restart */, true /* is_child */);
+  g_browser_process->profile_manager()->InitProfileUserPrefs(profile.get());
+
+  EXPECT_EQ(
+      profile->GetPrefs()->GetInteger(arc::prefs::kArcSupervisionTransition),
+      static_cast<int>(arc::ArcSupervisionTransition::REGULAR_TO_CHILD));
+  EXPECT_EQ(profile->GetPrefs()->GetString(prefs::kSupervisedUserId),
+            supervised_users::kChildAccountSUID);
+}
+
+TEST_F(ProfileManagerTest, InitProfileForChildToRegularTransition) {
+  chromeos::ProfileHelper* profile_helper = chromeos::ProfileHelper::Get();
+  user_manager::UserManager* user_manager = user_manager::UserManager::Get();
+
+  const std::string user_email = "child@example.com";
+  const AccountId account_id = AccountId::FromUserEmailGaiaId(user_email, "1");
+  const std::string user_id_hash =
+      profile_helper->GetUserIdHashByUserIdForTesting(user_email);
+  const base::FilePath dest_path =
+      profile_helper->GetProfilePathByUserIdHash(user_id_hash);
+
+  TestingProfile::Builder builder;
+  builder.SetPath(dest_path);
+  builder.OverrideIsNewProfile(false);
+  builder.SetSupervisedUserId(supervised_users::kChildAccountSUID);
+  std::unique_ptr<Profile> profile = builder.Build();
+
+  user_manager->UserLoggedIn(account_id, user_id_hash,
+                             false /* browser_restart */, false /* is_child */);
+  g_browser_process->profile_manager()->InitProfileUserPrefs(profile.get());
+
+  EXPECT_EQ(
+      profile->GetPrefs()->GetInteger(arc::prefs::kArcSupervisionTransition),
+      static_cast<int>(arc::ArcSupervisionTransition::CHILD_TO_REGULAR));
+  EXPECT_TRUE(profile->GetPrefs()->GetString(prefs::kSupervisedUserId).empty());
+}
+
+#endif
+
 TEST_F(ProfileManagerTest, GetLastUsedProfileAllowedByPolicy) {
   ProfileManager* profile_manager = g_browser_process->profile_manager();
   ASSERT_TRUE(profile_manager);
diff --git a/chrome/browser/resource_coordinator/tab_lifecycle_unit_unittest.cc b/chrome/browser/resource_coordinator/tab_lifecycle_unit_unittest.cc
index 7be8ecf1..82cbcce 100644
--- a/chrome/browser/resource_coordinator/tab_lifecycle_unit_unittest.cc
+++ b/chrome/browser/resource_coordinator/tab_lifecycle_unit_unittest.cc
@@ -361,7 +361,7 @@
                                       tab_strip_model_.get());
 
   content::MediaStreamDevices desktop_capture_devices{
-      content::MediaStreamDevice(content::MEDIA_DESKTOP_VIDEO_CAPTURE,
+      content::MediaStreamDevice(content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE,
                                  "fake_media_device", "fake_media_device")};
   std::unique_ptr<content::MediaStreamUI> ui =
       MediaCaptureDevicesDispatcher::GetInstance()
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/command_handler.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/command_handler.js
index 28f9888..143e58ba 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/command_handler.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/command_handler.js
@@ -495,6 +495,12 @@
           return false;
         }
 
+        if (EventSourceState.get() == EventSourceType.TOUCH_GESTURE &&
+            AutomationPredicate.editText(actionNode)) {
+          actionNode.focus();
+          return false;
+        }
+
         while (actionNode.role == RoleType.INLINE_TEXT_BOX ||
                actionNode.role == RoleType.STATIC_TEXT)
           actionNode = actionNode.parent;
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/desktop_automation_handler.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/desktop_automation_handler.js
index 266cb74..5043a42 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/desktop_automation_handler.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/desktop_automation_handler.js
@@ -133,16 +133,20 @@
       return;
 
     // Decide whether to announce and sync this event.
-    if (!DesktopAutomationHandler.announceActions && evt.eventFrom == 'action')
+    if (!DesktopAutomationHandler.announceActions &&
+        evt.eventFrom == 'action' &&
+        EventSourceState.get() != EventSourceType.TOUCH_GESTURE)
       return;
 
     var prevRange = ChromeVoxState.instance.currentRange;
 
     ChromeVoxState.instance.setCurrentRange(cursors.Range.fromNode(node));
 
-    // Don't output if focused node hasn't changed.
+    // Don't output if focused node hasn't changed. Allow focus announcements
+    // when interacting via touch. Touch never sets focus without a double tap.
     if (prevRange && evt.type == 'focus' &&
-        ChromeVoxState.instance.currentRange.equals(prevRange))
+        ChromeVoxState.instance.currentRange.equals(prevRange) &&
+        EventSourceState.get() != EventSourceType.TOUCH_GESTURE)
       return;
 
     var output = new Output();
@@ -236,6 +240,8 @@
     if (!GestureCommandHandler.getEnabled())
       return;
 
+    EventSourceState.set(EventSourceType.TOUCH_GESTURE);
+
     var target = evt.target;
     if (!AutomationPredicate.object(target)) {
       target = AutomationUtil.findNodePre(
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
index 3c7dc071..31fc466 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
@@ -1871,6 +1871,7 @@
       return;
 
     var node = range.start.node;
+
     if (!node) {
       this.append_(buff, Msgs.getMsg('warning_no_current_range'));
       return;
@@ -1882,8 +1883,16 @@
       return;
     }
 
-    if (type == EventType.HOVER ||
-        EventSourceState.get() == EventSourceType.TOUCH_GESTURE) {
+    if (EventSourceState.get() == EventSourceType.TOUCH_GESTURE) {
+      if (node.state[StateType.EDITABLE]) {
+        this.format_(
+            node,
+            node.state[StateType.FOCUSED] ? '@hint_is_editing' :
+                                            '@hint_double_tap_to_edit',
+            buff);
+        return;
+      }
+
       var isWithinVirtualKeyboard = AutomationUtil.getAncestors(node).find(
           (n) => n.role == RoleType.KEYBOARD);
       if (node.defaultActionVerb != 'none' && !isWithinVirtualKeyboard)
@@ -1902,7 +1911,6 @@
 
     if (node.state[StateType.EDITABLE] && node.state[StateType.FOCUSED] &&
         !this.formatOptions_.braille) {
-      this.format_(node, '@hint_is_editing', buff);
       if (node.state[StateType.MULTILINE] ||
           node.state[StateType.RICHLY_EDITABLE])
         this.format_(node, '@hint_search_within_text_field', buff);
diff --git a/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd b/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd
index 98ae0ed1..bd4de2e 100644
--- a/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd
+++ b/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd
@@ -3005,6 +3005,9 @@
       <message desc="A hint to the user on how to interact with the virtual on screen keyboard." name="IDS_CHROMEVOX_HINT_TOUCH_TYPE">
         Find a key, then lift to type
       </message>
+      <message desc="Hint for how to start editing a text field while exploring the screen using touch exploration." name="IDS_CHROMEVOX_HINT_DOUBLE_TAP_TO_EDIT">
+        Double tap to start editing
+      </message>
     </messages>
   </release>
 </grit>
diff --git a/chrome/browser/resources/local_ntp/local_ntp.js b/chrome/browser/resources/local_ntp/local_ntp.js
index 5c437d1..ecfdc01e 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.js
+++ b/chrome/browser/resources/local_ntp/local_ntp.js
@@ -576,7 +576,7 @@
 function showNotification(msg) {
   $(IDS.NOTIFICATION_MESSAGE).textContent = msg;
 
-  if (configData.isMDIconsEnabled || configData.isMDUIEnabled) {
+  if (configData.isMDIconsEnabled) {
     $(IDS.NOTIFICATION).classList.remove(CLASSES.HIDE_NOTIFICATION);
     // Timeout is required for the "float up" transition to work. Modifying the
     // "display" property prevents transitions from activating.
@@ -602,7 +602,7 @@
  * Hides the pop-up notification.
  */
 function hideNotification() {
-  if (configData.isMDIconsEnabled || configData.isMDUIEnabled) {
+  if (configData.isMDIconsEnabled) {
     let notification = $(IDS.NOTIFICATION_CONTAINER);
     if (!notification.classList.contains(CLASSES.FLOAT_UP)) {
       return;
@@ -767,7 +767,12 @@
               !args.showRestoreDefault);
     }
   } else if (cmd === 'tileBlacklisted') {
-    showNotification(configData.translatedStrings.linkRemovedMsg);
+    if (configData.isCustomLinksEnabled) {
+      showNotification(configData.translatedStrings.linkRemovedMsg);
+    } else {
+      showNotification(
+          configData.translatedStrings.thumbnailRemovedNotification);
+    }
     lastBlacklistedTile = args.tid;
 
     ntpApiHandle.deleteMostVisitedItem(args.tid);
@@ -790,25 +795,26 @@
 
 
 /**
- * Enables Material Design styles for all of NTP.
- */
-function enableMD() {
-  document.body.classList.add(CLASSES.MATERIAL_DESIGN);
-  enableMDIcons();
-}
-
-
-/**
- * Enables Material Design styles for the Most Visited section.
+ * Enables Material Design styles for the Most Visited section. Implicitly
+ * enables Material Design for the rest of NTP.
  */
 function enableMDIcons() {
   $(IDS.MOST_VISITED).classList.add(CLASSES.MATERIAL_DESIGN_ICONS);
   $(IDS.TILES).classList.add(CLASSES.MATERIAL_DESIGN_ICONS);
+  enableMD();
   addRippleAnimations();
 }
 
 
 /**
+ * Enables Material Design styles for all NTP components except Most Visited.
+ */
+function enableMD() {
+  document.body.classList.add(CLASSES.MATERIAL_DESIGN);
+}
+
+
+/**
  * Enables ripple animations for elements with CLASSES.RIPPLE.
  * TODO(kristipark): Remove after migrating to WebUI.
  */
@@ -924,10 +930,10 @@
   var searchboxApiHandle = embeddedSearchApiHandle.searchBox;
 
   if (configData.isGooglePage) {
-    if (configData.isMDUIEnabled) {
-      enableMD();
-    } else if (configData.isMDIconsEnabled) {
+    if (configData.isMDIconsEnabled || configData.isCustomLinksEnabled) {
       enableMDIcons();
+    } else if (configData.isMDUIEnabled) {
+      enableMD();
     }
 
     if (configData.isCustomLinksEnabled) {
diff --git a/chrome/browser/safe_browsing/safe_browsing_blocking_page.h b/chrome/browser/safe_browsing/safe_browsing_blocking_page.h
index d2817327..ca4344e 100644
--- a/chrome/browser/safe_browsing/safe_browsing_blocking_page.h
+++ b/chrome/browser/safe_browsing/safe_browsing_blocking_page.h
@@ -97,7 +97,7 @@
                            ExtendedReportingNotShownInIncognito);
   FRIEND_TEST_ALL_PREFIXES(SafeBrowsingBlockingPageTest,
                            ExtendedReportingNotShownNotAllowExtendedReporting);
-  FRIEND_TEST_ALL_PREFIXES(SafeBrowsingBlockingPageTest, TrickToBillPage);
+  FRIEND_TEST_ALL_PREFIXES(SafeBrowsingBlockingPageTest, BillingPage);
 
   void UpdateReportingPref();  // Used for the transition from old to new pref.
 
diff --git a/chrome/browser/safe_browsing/safe_browsing_blocking_page_unittest.cc b/chrome/browser/safe_browsing/safe_browsing_blocking_page_unittest.cc
index 5c3493d..c298bac 100644
--- a/chrome/browser/safe_browsing/safe_browsing_blocking_page_unittest.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_blocking_page_unittest.cc
@@ -949,14 +949,14 @@
   ui_manager_->GetThreatDetails()->clear();
 }
 
-// Tests showing a blocking page for trick-to-bill.
-TEST_F(SafeBrowsingBlockingPageTest, TrickToBillPage) {
+// Tests showing a blocking page for billing.
+TEST_F(SafeBrowsingBlockingPageTest, BillingPage) {
   // Start a load.
   controller().LoadURL(GURL(kBadURL), content::Referrer(),
                        ui::PAGE_TRANSITION_TYPED, std::string());
 
   // Simulate the load causing a safe browsing interstitial to be shown.
-  ShowInterstitial(false, kBadURL, SB_THREAT_TYPE_TRICK_TO_BILL);
+  ShowInterstitial(false, kBadURL, SB_THREAT_TYPE_BILLING);
 
   SafeBrowsingBlockingPage* sb_interstitial = GetSafeBrowsingBlockingPage();
   ASSERT_TRUE(sb_interstitial);
@@ -967,14 +967,13 @@
   base::string16 str;
 
   load_time_data.GetString("heading", &str);
-  EXPECT_EQ(str, l10n_util::GetStringUTF16(IDS_TRICK_TO_BILL_HEADING));
+  EXPECT_EQ(str, l10n_util::GetStringUTF16(IDS_BILLING_HEADING));
   load_time_data.GetString("primaryParagraph", &str);
-  EXPECT_EQ(str,
-            l10n_util::GetStringUTF16(IDS_TRICK_TO_BILL_PRIMARY_PARAGRAPH));
+  EXPECT_EQ(str, l10n_util::GetStringUTF16(IDS_BILLING_PRIMARY_PARAGRAPH));
   load_time_data.GetString("primaryButtonText", &str);
-  EXPECT_EQ(str, l10n_util::GetStringUTF16(IDS_TRICK_TO_BILL_PRIMARY_BUTTON));
+  EXPECT_EQ(str, l10n_util::GetStringUTF16(IDS_BILLING_PRIMARY_BUTTON));
   load_time_data.GetString("proceedButtonText", &str);
-  EXPECT_EQ(str, l10n_util::GetStringUTF16(IDS_TRICK_TO_BILL_PROCEED_BUTTON));
+  EXPECT_EQ(str, l10n_util::GetStringUTF16(IDS_BILLING_PROCEED_BUTTON));
 
   load_time_data.GetString("openDetails", &str);
   EXPECT_EQ(str, base::string16());
@@ -986,7 +985,7 @@
   EXPECT_EQ(str, base::string16());
 
   bool flag;
-  load_time_data.GetBoolean("trick_to_bill", &flag);
+  load_time_data.GetBoolean("billing", &flag);
   EXPECT_TRUE(flag);
   load_time_data.GetBoolean("phishing", &flag);
   EXPECT_FALSE(flag);
diff --git a/chrome/browser/ssl/security_state_tab_helper.cc b/chrome/browser/ssl/security_state_tab_helper.cc
index 3220bea..90ec5ca 100644
--- a/chrome/browser/ssl/security_state_tab_helper.cc
+++ b/chrome/browser/ssl/security_state_tab_helper.cc
@@ -284,9 +284,9 @@
     switch (threat_type) {
       case safe_browsing::SB_THREAT_TYPE_UNUSED:
       case safe_browsing::SB_THREAT_TYPE_SAFE:
-      // TODO(https://crbug.com/867518): Create a malicious trick to bill enum
-      // and return it.
-      case safe_browsing::SB_THREAT_TYPE_TRICK_TO_BILL:
+      // TODO(https://crbug.com/867518): Create a malicious billing enum and
+      // return it.
+      case safe_browsing::SB_THREAT_TYPE_BILLING:
         break;
       case safe_browsing::SB_THREAT_TYPE_URL_PHISHING:
       case safe_browsing::SB_THREAT_TYPE_URL_CLIENT_SIDE_PHISHING:
diff --git a/chrome/browser/sync/test/integration/two_client_bookmarks_sync_test.cc b/chrome/browser/sync/test/integration/two_client_bookmarks_sync_test.cc
index 27a6ba99..ffb8d9c 100644
--- a/chrome/browser/sync/test/integration/two_client_bookmarks_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_bookmarks_sync_test.cc
@@ -1680,7 +1680,7 @@
 
 // Test a scenario of updating the name of the same bookmark from two clients at
 // the same time.
-IN_PROC_BROWSER_TEST_P(TwoClientBookmarksSyncTestIncludingUssTests,
+IN_PROC_BROWSER_TEST_F(TwoClientBookmarksSyncTest,
                        MC_BookmarkNameChangeConflict) {
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
 
@@ -1705,7 +1705,7 @@
 
 // Test a scenario of updating the URL of the same bookmark from two clients at
 // the same time.
-IN_PROC_BROWSER_TEST_P(TwoClientBookmarksSyncTestIncludingUssTests,
+IN_PROC_BROWSER_TEST_F(TwoClientBookmarksSyncTest,
                        MC_BookmarkURLChangeConflict) {
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
 
@@ -1732,7 +1732,7 @@
 
 // Test a scenario of updating the BM Folder name from two clients at the same
 // time.
-IN_PROC_BROWSER_TEST_P(TwoClientBookmarksSyncTestIncludingUssTests,
+IN_PROC_BROWSER_TEST_F(TwoClientBookmarksSyncTest,
                        MC_FolderNameChangeConflict) {
   ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
   DisableVerifier();
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 57f5f3e..05586080 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2809,6 +2809,7 @@
       "autofill/save_card_bubble_controller_impl.cc",
       "autofill/save_card_bubble_controller_impl.h",
       "autofill/save_card_bubble_view.h",
+      "autofill/save_card_ui.h",
       "bubble_anchor_util.h",
       "desktop_ios_promotion/desktop_ios_promotion_footnote_delegate.h",
 
@@ -2845,6 +2846,12 @@
       "views/autofill/local_card_migration_bubble_views.h",
       "views/autofill/save_card_bubble_views.cc",
       "views/autofill/save_card_bubble_views.h",
+      "views/autofill/save_card_manage_cards_bubble_views.cc",
+      "views/autofill/save_card_manage_cards_bubble_views.h",
+      "views/autofill/save_card_offer_bubble_views.cc",
+      "views/autofill/save_card_offer_bubble_views.h",
+      "views/autofill/save_card_sign_in_promo_bubble_views.cc",
+      "views/autofill/save_card_sign_in_promo_bubble_views.h",
       "views/autofill/view_util.cc",
       "views/autofill/view_util.h",
       "views/bookmarks/bookmark_bubble_view.cc",
diff --git a/chrome/browser/ui/ash/assistant/assistant_client.cc b/chrome/browser/ui/ash/assistant/assistant_client.cc
index e52963d..d09ce50 100644
--- a/chrome/browser/ui/ash/assistant/assistant_client.cc
+++ b/chrome/browser/ui/ash/assistant/assistant_client.cc
@@ -61,9 +61,12 @@
 }
 
 void AssistantClient::OnAssistantStatusChanged(bool running) {
+  // |running| means assistent mojom service is running. This maps to
+  // |STOPPED| and |NOT_READY|. |RUNNING| maps to UI is shown and an assistant
+  // session is running.
   arc::VoiceInteractionControllerClient::Get()->NotifyStatusChanged(
-      running ? ash::mojom::VoiceInteractionState::RUNNING
-              : ash::mojom::VoiceInteractionState::STOPPED);
+      running ? ash::mojom::VoiceInteractionState::STOPPED
+              : ash::mojom::VoiceInteractionState::NOT_READY);
 }
 
 void AssistantClient::RequestAssistantStructure(
diff --git a/chrome/browser/ui/autofill/save_card_bubble_controller_impl.cc b/chrome/browser/ui/autofill/save_card_bubble_controller_impl.cc
index 11afa81d..f7184b1 100644
--- a/chrome/browser/ui/autofill/save_card_bubble_controller_impl.cc
+++ b/chrome/browser/ui/autofill/save_card_bubble_controller_impl.cc
@@ -8,18 +8,28 @@
 
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/signin/account_consistency_mode_manager.h"
 #include "chrome/browser/signin/account_tracker_service_factory.h"
 #include "chrome/browser/signin/signin_manager_factory.h"
+#include "chrome/browser/signin/signin_ui_util.h"
 #include "chrome/browser/ssl/security_state_tab_helper.h"
+#include "chrome/browser/sync/profile_sync_service_factory.h"
 #include "chrome/browser/ui/autofill/popup_constants.h"
 #include "chrome/browser/ui/autofill/save_card_bubble_view.h"
+#include "chrome/browser/ui/autofill/save_card_ui.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/browser/ui/location_bar/location_bar.h"
+#include "chrome/browser/ui/sync/sync_promo_ui.h"
+#include "chrome/common/url_constants.h"
 #include "components/autofill/core/browser/autofill_experiments.h"
 #include "components/autofill/core/browser/autofill_metrics.h"
+#include "components/autofill/core/common/autofill_constants.h"
+#include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/autofill_prefs.h"
+#include "components/browser_sync/profile_sync_service.h"
 #include "components/prefs/pref_service.h"
 #include "components/signin/core/browser/account_tracker_service.h"
 #include "components/signin/core/browser/signin_manager_base.h"
@@ -34,7 +44,6 @@
     content::WebContents* web_contents)
     : content::WebContentsObserver(web_contents),
       web_contents_(web_contents),
-      save_card_bubble_view_(nullptr),
       pref_service_(
           user_prefs::UserPrefs::Get(web_contents->GetBrowserContext())) {
   security_state::SecurityInfo security_info;
@@ -55,13 +64,13 @@
   if (save_card_bubble_view_)
     return;
 
-  is_uploading_ = false;
+  is_upload_save_ = false;
   is_reshow_ = false;
   should_request_name_from_user_ = false;
   legal_message_lines_.clear();
 
   AutofillMetrics::LogSaveCardPromptMetric(
-      AutofillMetrics::SAVE_CARD_PROMPT_SHOW_REQUESTED, is_uploading_,
+      AutofillMetrics::SAVE_CARD_PROMPT_SHOW_REQUESTED, is_upload_save_,
       is_reshow_, should_request_name_from_user_,
       pref_service_->GetInteger(
           prefs::kAutofillAcceptSaveCreditCardPromptState),
@@ -69,6 +78,7 @@
 
   card_ = card;
   local_save_card_callback_ = save_card_callback;
+  current_bubble_type_ = BubbleType::LOCAL_SAVE;
   ShowBubble();
 }
 
@@ -85,11 +95,11 @@
   if (should_request_name_from_user && account_info_.IsEmpty())
     FetchAccountInfo();
 
-  is_uploading_ = true;
+  is_upload_save_ = true;
   is_reshow_ = false;
   should_request_name_from_user_ = should_request_name_from_user;
   AutofillMetrics::LogSaveCardPromptMetric(
-      AutofillMetrics::SAVE_CARD_PROMPT_SHOW_REQUESTED, is_uploading_,
+      AutofillMetrics::SAVE_CARD_PROMPT_SHOW_REQUESTED, is_upload_save_,
       is_reshow_, should_request_name_from_user_,
       pref_service_->GetInteger(
           prefs::kAutofillAcceptSaveCreditCardPromptState),
@@ -99,7 +109,7 @@
                                /*escape_apostrophes=*/true)) {
     AutofillMetrics::LogSaveCardPromptMetric(
         AutofillMetrics::SAVE_CARD_PROMPT_END_INVALID_LEGAL_MESSAGE,
-        is_uploading_, is_reshow_, should_request_name_from_user_,
+        is_upload_save_, is_reshow_, should_request_name_from_user_,
         pref_service_->GetInteger(
             prefs::kAutofillAcceptSaveCreditCardPromptState),
         GetSecurityLevel());
@@ -108,6 +118,29 @@
 
   card_ = card;
   upload_save_card_callback_ = std::move(save_card_callback);
+  current_bubble_type_ = BubbleType::UPLOAD_SAVE;
+  ShowBubble();
+}
+
+void SaveCardBubbleControllerImpl::ShowBubbleForSignInPromo() {
+  // TODO(crbug/855186): Implement metrics for sign-in promo.
+  if (!ShouldShowSignInPromo())
+    return;
+
+  current_bubble_type_ = BubbleType::SIGN_IN_PROMO;
+
+  // If DICe is disabled, then we need to know whether the user is signed in
+  // to determine whether or not to show a sign-in vs sync promo.
+  if (GetAccountInfo().IsEmpty())
+    FetchAccountInfo();
+  ShowBubble();
+}
+
+// Exists for testing purposes only.
+void SaveCardBubbleControllerImpl::ShowBubbleForManageCardsForTesting(
+    const CreditCard& card) {
+  card_ = card;
+  current_bubble_type_ = BubbleType::MANAGE_CARDS;
   ShowBubble();
 }
 
@@ -124,19 +157,24 @@
     return;
 
   is_reshow_ = true;
-  AutofillMetrics::LogSaveCardPromptMetric(
-      AutofillMetrics::SAVE_CARD_PROMPT_SHOW_REQUESTED, is_uploading_,
-      is_reshow_, should_request_name_from_user_,
-      pref_service_->GetInteger(
-          prefs::kAutofillAcceptSaveCreditCardPromptState),
-      GetSecurityLevel());
+
+  // TODO(crbug/855186): Implement metrics for sign-in promo.
+  if (current_bubble_type_ == BubbleType::LOCAL_SAVE ||
+      current_bubble_type_ == BubbleType::UPLOAD_SAVE) {
+    AutofillMetrics::LogSaveCardPromptMetric(
+        AutofillMetrics::SAVE_CARD_PROMPT_SHOW_REQUESTED, is_upload_save_,
+        is_reshow_, should_request_name_from_user_,
+        pref_service_->GetInteger(
+            prefs::kAutofillAcceptSaveCreditCardPromptState),
+        GetSecurityLevel());
+  }
 
   ShowBubble();
 }
 
 bool SaveCardBubbleControllerImpl::IsIconVisible() const {
-  return !upload_save_card_callback_.is_null() ||
-         !local_save_card_callback_.is_null();
+  // If there is no bubble to show, then there should be no icon.
+  return current_bubble_type_ != BubbleType::INACTIVE;
 }
 
 SaveCardBubbleView* SaveCardBubbleControllerImpl::save_card_bubble_view()
@@ -145,12 +183,29 @@
 }
 
 base::string16 SaveCardBubbleControllerImpl::GetWindowTitle() const {
-  return l10n_util::GetStringUTF16(
-      IDS_AUTOFILL_SAVE_CARD_PROMPT_TITLE_TO_CLOUD_V3);
+  switch (current_bubble_type_) {
+    case BubbleType::LOCAL_SAVE:
+    case BubbleType::UPLOAD_SAVE:
+      return l10n_util::GetStringUTF16(
+          IDS_AUTOFILL_SAVE_CARD_PROMPT_TITLE_TO_CLOUD_V3);
+    case BubbleType::SIGN_IN_PROMO:
+      if (AccountConsistencyModeManager::IsDiceEnabledForProfile(
+              GetProfile())) {
+        return l10n_util::GetStringUTF16(GetAccountInfo().IsEmpty()
+                                             ? IDS_AUTOFILL_SIGNIN_PROMO_MESSAGE
+                                             : IDS_AUTOFILL_SYNC_PROMO_MESSAGE);
+      }
+      return l10n_util::GetStringUTF16(IDS_AUTOFILL_CARD_SAVED);
+    case BubbleType::MANAGE_CARDS:
+      return l10n_util::GetStringUTF16(IDS_AUTOFILL_CARD_SAVED);
+    case BubbleType::INACTIVE:
+      NOTREACHED();
+      return base::string16();
+  }
 }
 
 base::string16 SaveCardBubbleControllerImpl::GetExplanatoryMessage() const {
-  if (is_uploading_) {
+  if (current_bubble_type_ == BubbleType::UPLOAD_SAVE) {
     if (should_request_name_from_user_) {
       return l10n_util::GetStringUTF16(
           IDS_AUTOFILL_SAVE_CARD_PROMPT_UPLOAD_EXPLANATION_V3_WITH_NAME);
@@ -167,6 +222,12 @@
   return account_info_;
 }
 
+Profile* SaveCardBubbleControllerImpl::GetProfile() const {
+  if (!web_contents())
+    return nullptr;
+  return Profile::FromBrowserContext(web_contents()->GetBrowserContext());
+}
+
 const CreditCard& SaveCardBubbleControllerImpl::GetCard() const {
   return card_;
 }
@@ -175,64 +236,143 @@
   return should_request_name_from_user_;
 }
 
+bool SaveCardBubbleControllerImpl::ShouldShowSignInPromo() const {
+  if (is_upload_save_ || !base::FeatureList::IsEnabled(
+                             features::kAutofillSaveCardSignInAfterLocalSave))
+    return false;
+
+  const browser_sync::ProfileSyncService* sync_service =
+      ProfileSyncServiceFactory::GetForProfile(GetProfile());
+
+  return !sync_service ||
+         sync_service->HasDisableReason(
+             browser_sync::ProfileSyncService::DISABLE_REASON_NOT_SIGNED_IN) ||
+         sync_service->HasDisableReason(
+             browser_sync::ProfileSyncService::DISABLE_REASON_USER_CHOICE);
+}
+
+void SaveCardBubbleControllerImpl::OnSyncPromoAccepted(
+    const AccountInfo& account,
+    bool is_default_promo_account) {
+  DCHECK(current_bubble_type_ == BubbleType::SIGN_IN_PROMO ||
+         current_bubble_type_ == BubbleType::MANAGE_CARDS);
+  Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
+  signin_ui_util::EnableSyncFromPromo(
+      browser, account,
+      signin_metrics::AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE,
+      is_default_promo_account);
+}
+
 void SaveCardBubbleControllerImpl::OnSaveButton(
     const base::string16& cardholder_name) {
-  if (!upload_save_card_callback_.is_null()) {
-    base::string16 name_provided_by_user;
-    if (!cardholder_name.empty()) {
-      // Log whether the name was changed by the user or simply accepted without
-      // edits.
-      AutofillMetrics::LogSaveCardCardholderNameWasEdited(
-          cardholder_name != base::UTF8ToUTF16(account_info_.full_name));
-      // Trim the cardholder name provided by the user and send it in the
-      // callback so it can be included in the final request.
-      DCHECK(ShouldRequestNameFromUser());
-      base::TrimWhitespace(cardholder_name, base::TRIM_ALL,
-                           &name_provided_by_user);
+  save_card_bubble_view_ = nullptr;
+
+  switch (current_bubble_type_) {
+    case BubbleType::UPLOAD_SAVE: {
+      DCHECK(!upload_save_card_callback_.is_null());
+
+      base::string16 name_provided_by_user;
+      if (!cardholder_name.empty()) {
+        // Log whether the name was changed by the user or simply accepted
+        // without edits.
+        AutofillMetrics::LogSaveCardCardholderNameWasEdited(
+            cardholder_name != base::UTF8ToUTF16(account_info_.full_name));
+        // Trim the cardholder name provided by the user and send it in the
+        // callback so it can be included in the final request.
+        DCHECK(ShouldRequestNameFromUser());
+        base::TrimWhitespace(cardholder_name, base::TRIM_ALL,
+                             &name_provided_by_user);
+      }
+      std::move(upload_save_card_callback_).Run(name_provided_by_user);
+      break;
     }
-    std::move(upload_save_card_callback_).Run(name_provided_by_user);
-  } else {
-    DCHECK(!local_save_card_callback_.is_null());
-    local_save_card_callback_.Run();
-    local_save_card_callback_.Reset();
+    case BubbleType::LOCAL_SAVE:
+      DCHECK(!local_save_card_callback_.is_null());
+      local_save_card_callback_.Run();
+      local_save_card_callback_.Reset();
+      break;
+    case BubbleType::MANAGE_CARDS:
+      // TODO(crbug/855186): Implement metrics for [Done] button.
+      return;
+    case BubbleType::SIGN_IN_PROMO:
+    case BubbleType::INACTIVE:
+      NOTREACHED();
   }
-  AutofillMetrics::LogSaveCardPromptMetric(
-      AutofillMetrics::SAVE_CARD_PROMPT_END_ACCEPTED, is_uploading_, is_reshow_,
-      should_request_name_from_user_,
-      pref_service_->GetInteger(
-          prefs::kAutofillAcceptSaveCreditCardPromptState),
-      GetSecurityLevel());
-  pref_service_->SetInteger(
-      prefs::kAutofillAcceptSaveCreditCardPromptState,
-      prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_ACCEPTED);
+
+  const BubbleType previous_bubble_type = current_bubble_type_;
+  current_bubble_type_ = BubbleType::INACTIVE;
+
+  // If user just saved a card locally, and we can show the sign in promo,
+  // then show the sign in promo. If we can't show the sign in promo
+  // then |current_bubble_type_| will be set to |MANAGE_CARDS|.
+  if (previous_bubble_type == BubbleType::LOCAL_SAVE &&
+      base::FeatureList::IsEnabled(
+          features::kAutofillSaveCardSignInAfterLocalSave)) {
+    if (ShouldShowSignInPromo())
+      ShowBubbleForSignInPromo();
+    else
+      current_bubble_type_ = BubbleType::MANAGE_CARDS;
+  }
+
+  if (previous_bubble_type == BubbleType::LOCAL_SAVE ||
+      previous_bubble_type == BubbleType::UPLOAD_SAVE) {
+    AutofillMetrics::LogSaveCardPromptMetric(
+        AutofillMetrics::SAVE_CARD_PROMPT_END_ACCEPTED, is_upload_save_,
+        is_reshow_, should_request_name_from_user_,
+        pref_service_->GetInteger(
+            prefs::kAutofillAcceptSaveCreditCardPromptState),
+        GetSecurityLevel());
+    pref_service_->SetInteger(
+        prefs::kAutofillAcceptSaveCreditCardPromptState,
+        prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_ACCEPTED);
+  }
 }
 
 void SaveCardBubbleControllerImpl::OnCancelButton() {
+  // Should only be applicable for non-material UI,
+  // as Harmony dialogs do not have [No thanks] buttons.
+  const BubbleType previous_bubble_type = current_bubble_type_;
+  current_bubble_type_ = BubbleType::INACTIVE;
   upload_save_card_callback_.Reset();
   local_save_card_callback_.Reset();
-  AutofillMetrics::LogSaveCardPromptMetric(
-      AutofillMetrics::SAVE_CARD_PROMPT_END_DENIED, is_uploading_, is_reshow_,
-      should_request_name_from_user_,
-      pref_service_->GetInteger(
-          prefs::kAutofillAcceptSaveCreditCardPromptState),
-      GetSecurityLevel());
-  pref_service_->SetInteger(
-      prefs::kAutofillAcceptSaveCreditCardPromptState,
-      prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_DENIED);
+
+  if (previous_bubble_type == BubbleType::LOCAL_SAVE ||
+      previous_bubble_type == BubbleType::UPLOAD_SAVE) {
+    AutofillMetrics::LogSaveCardPromptMetric(
+        AutofillMetrics::SAVE_CARD_PROMPT_END_DENIED, is_upload_save_,
+        is_reshow_, should_request_name_from_user_,
+        pref_service_->GetInteger(
+            prefs::kAutofillAcceptSaveCreditCardPromptState),
+        GetSecurityLevel());
+    pref_service_->SetInteger(
+        prefs::kAutofillAcceptSaveCreditCardPromptState,
+        prefs::PREVIOUS_SAVE_CREDIT_CARD_PROMPT_USER_DECISION_DENIED);
+  }
 }
 
 void SaveCardBubbleControllerImpl::OnLegalMessageLinkClicked(const GURL& url) {
   OpenUrl(url);
   AutofillMetrics::LogSaveCardPromptMetric(
       AutofillMetrics::SAVE_CARD_PROMPT_DISMISS_CLICK_LEGAL_MESSAGE,
-      is_uploading_, is_reshow_, should_request_name_from_user_,
+      is_upload_save_, is_reshow_, should_request_name_from_user_,
       pref_service_->GetInteger(
           prefs::kAutofillAcceptSaveCreditCardPromptState),
       GetSecurityLevel());
 }
 
+void SaveCardBubbleControllerImpl::OnManageCardsClicked() {
+  // TODO(crbug/855186): Implement metrics for sign-in promo.
+  chrome::ShowSettingsSubPage(
+      chrome::FindBrowserWithWebContents(web_contents()),
+      chrome::kAutofillSubPage);
+}
+
 void SaveCardBubbleControllerImpl::OnBubbleClosed() {
   save_card_bubble_view_ = nullptr;
+  // Sign-in promo should only be shown once, so if it was displayed presently,
+  // reopening the bubble will show the card management bubble.
+  if (current_bubble_type_ == BubbleType::SIGN_IN_PROMO)
+    current_bubble_type_ = BubbleType::MANAGE_CARDS;
   UpdateIcon();
 }
 
@@ -241,6 +381,14 @@
   return legal_message_lines_;
 }
 
+bool SaveCardBubbleControllerImpl::IsUploadSave() const {
+  return is_upload_save_;
+}
+
+BubbleType SaveCardBubbleControllerImpl::GetBubbleType() const {
+  return current_bubble_type_;
+}
+
 base::TimeDelta SaveCardBubbleControllerImpl::Elapsed() const {
   return timer_->Elapsed();
 }
@@ -251,10 +399,8 @@
     return;
 
   // Nothing to do if there's no bubble available.
-  if (upload_save_card_callback_.is_null() &&
-      local_save_card_callback_.is_null()) {
+  if (current_bubble_type_ == BubbleType::INACTIVE)
     return;
-  }
 
   // Don't react to same-document (fragment) navigations.
   if (navigation_handle->IsSameDocument())
@@ -266,6 +412,9 @@
     return;
 
   // Otherwise, get rid of the bubble and icon.
+  const BubbleType previous_bubble_type = current_bubble_type_;
+  current_bubble_type_ = BubbleType::INACTIVE;
+
   upload_save_card_callback_.Reset();
   local_save_card_callback_.Reset();
   bool bubble_was_visible = save_card_bubble_view_;
@@ -275,14 +424,18 @@
   } else {
     UpdateIcon();
   }
-  AutofillMetrics::LogSaveCardPromptMetric(
-      bubble_was_visible
-          ? AutofillMetrics::SAVE_CARD_PROMPT_END_NAVIGATION_SHOWING
-          : AutofillMetrics::SAVE_CARD_PROMPT_END_NAVIGATION_HIDDEN,
-      is_uploading_, is_reshow_, should_request_name_from_user_,
-      pref_service_->GetInteger(
-          prefs::kAutofillAcceptSaveCreditCardPromptState),
-      GetSecurityLevel());
+
+  if (previous_bubble_type == BubbleType::LOCAL_SAVE ||
+      previous_bubble_type == BubbleType::UPLOAD_SAVE) {
+    AutofillMetrics::LogSaveCardPromptMetric(
+        bubble_was_visible
+            ? AutofillMetrics::SAVE_CARD_PROMPT_END_NAVIGATION_SHOWING
+            : AutofillMetrics::SAVE_CARD_PROMPT_END_NAVIGATION_HIDDEN,
+        is_upload_save_, is_reshow_, should_request_name_from_user_,
+        pref_service_->GetInteger(
+            prefs::kAutofillAcceptSaveCreditCardPromptState),
+        GetSecurityLevel());
+  }
 }
 
 void SaveCardBubbleControllerImpl::OnVisibilityChanged(
@@ -296,8 +449,7 @@
 }
 
 void SaveCardBubbleControllerImpl::FetchAccountInfo() {
-  Profile* profile =
-      Profile::FromBrowserContext(web_contents_->GetBrowserContext());
+  Profile* profile = GetProfile();
   if (!profile)
     return;
   SigninManagerBase* signin_manager =
@@ -311,8 +463,13 @@
 }
 
 void SaveCardBubbleControllerImpl::ShowBubble() {
-  DCHECK(!upload_save_card_callback_.is_null() ||
-         !local_save_card_callback_.is_null());
+  DCHECK(current_bubble_type_ != BubbleType::INACTIVE);
+  // Upload save callback should not be null for UPLOAD_SAVE state.
+  DCHECK(!(upload_save_card_callback_.is_null() &&
+           current_bubble_type_ == BubbleType::UPLOAD_SAVE));
+  // Local save callback should not be null for LOCAL_SAVE state.
+  DCHECK(!(local_save_card_callback_.is_null() &&
+           current_bubble_type_ == BubbleType::LOCAL_SAVE));
   DCHECK(!save_card_bubble_view_);
 
   // Need to create location bar icon before bubble, otherwise bubble will be
@@ -330,12 +487,15 @@
 
   timer_.reset(new base::ElapsedTimer());
 
-  AutofillMetrics::LogSaveCardPromptMetric(
-      AutofillMetrics::SAVE_CARD_PROMPT_SHOWN, is_uploading_, is_reshow_,
-      should_request_name_from_user_,
-      pref_service_->GetInteger(
-          prefs::kAutofillAcceptSaveCreditCardPromptState),
-      GetSecurityLevel());
+  if (current_bubble_type_ == BubbleType::LOCAL_SAVE ||
+      current_bubble_type_ == BubbleType::UPLOAD_SAVE) {
+    AutofillMetrics::LogSaveCardPromptMetric(
+        AutofillMetrics::SAVE_CARD_PROMPT_SHOWN, is_upload_save_, is_reshow_,
+        should_request_name_from_user_,
+        pref_service_->GetInteger(
+            prefs::kAutofillAcceptSaveCreditCardPromptState),
+        GetSecurityLevel());
+  }
 }
 
 void SaveCardBubbleControllerImpl::UpdateIcon() {
diff --git a/chrome/browser/ui/autofill/save_card_bubble_controller_impl.h b/chrome/browser/ui/autofill/save_card_bubble_controller_impl.h
index 880261e..d58cf4a 100644
--- a/chrome/browser/ui/autofill/save_card_bubble_controller_impl.h
+++ b/chrome/browser/ui/autofill/save_card_bubble_controller_impl.h
@@ -9,6 +9,7 @@
 
 #include "base/macros.h"
 #include "base/timer/elapsed_timer.h"
+#include "chrome/browser/ui/autofill/save_card_ui.h"
 #include "components/autofill/core/browser/credit_card.h"
 #include "components/autofill/core/browser/ui/save_card_bubble_controller.h"
 #include "components/security_state/core/security_state.h"
@@ -20,6 +21,8 @@
 
 namespace autofill {
 
+enum class BubbleType;
+
 // Implementation of per-tab class to control the save credit card bubble and
 // Omnibox icon.
 class SaveCardBubbleControllerImpl
@@ -47,6 +50,16 @@
       bool should_request_name_from_user,
       base::OnceCallback<void(const base::string16&)> save_card_callback);
 
+  // Sets up the controller for the sign in promo and shows the bubble.
+  // This bubble is only shown after a local save is accepted and if
+  // |ShouldShowSignInPromo()| returns true.
+  void ShowBubbleForSignInPromo();
+
+  // Exists for testing purposes only. (Otherwise shown through ReshowBubble())
+  // Sets up the controller for the Manage Cards view. This displays the card
+  // just saved and links the user to manage their other cards.
+  void ShowBubbleForManageCardsForTesting(const CreditCard& card);
+
   void HideBubble();
   void ReshowBubble();
 
@@ -60,14 +73,30 @@
   base::string16 GetWindowTitle() const override;
   base::string16 GetExplanatoryMessage() const override;
   const AccountInfo& GetAccountInfo() const override;
+  Profile* GetProfile() const override;
   const CreditCard& GetCard() const override;
   bool ShouldRequestNameFromUser() const override;
+
+  // Returns true only if at least one of the following cases is true:
+  // 1) The user is signed out.
+  // 2) The user is signed in through DICe, but did not turn on syncing.
+  // Consequently returns false in the following cases:
+  // 1) The user has paused syncing (Auth Error).
+  // 2) The user is not required to be syncing in order to upload cards
+  //    to the server -- this should change.
+  // TODO(crbug.com/864702): Don't show promo if user is a butter user.
+  bool ShouldShowSignInPromo() const override;
+  void OnSyncPromoAccepted(const AccountInfo& account,
+                           bool is_default_promo_account) override;
   void OnSaveButton(
       const base::string16& cardholder_name = base::string16()) override;
   void OnCancelButton() override;
   void OnLegalMessageLinkClicked(const GURL& url) override;
+  void OnManageCardsClicked() override;
   void OnBubbleClosed() override;
   const LegalMessageLines& GetLegalMessageLines() const override;
+  bool IsUploadSave() const override;
+  BubbleType GetBubbleType() const override;
 
  protected:
   explicit SaveCardBubbleControllerImpl(content::WebContents* web_contents);
@@ -101,7 +130,11 @@
   content::WebContents* web_contents_;
 
   // Weak reference. Will be nullptr if no bubble is currently shown.
-  SaveCardBubbleView* save_card_bubble_view_;
+  SaveCardBubbleView* save_card_bubble_view_ = nullptr;
+
+  // The type of bubble that is either currently being shown or would
+  // be shown when the save card icon is clicked.
+  BubbleType current_bubble_type_ = BubbleType::INACTIVE;
 
   // Weak reference to read & write |kAutofillAcceptSaveCreditCardPromptState|.
   PrefService* pref_service_;
@@ -118,7 +151,7 @@
   base::Closure local_save_card_callback_;
 
   // Governs whether the upload or local save version of the UI should be shown.
-  bool is_uploading_ = false;
+  bool is_upload_save_ = false;
 
   // Whether ReshowBubble() has been called since ShowBubbleFor*() was called.
   bool is_reshow_ = false;
diff --git a/chrome/browser/ui/autofill/save_card_bubble_controller_impl_browsertest.cc b/chrome/browser/ui/autofill/save_card_bubble_controller_impl_browsertest.cc
index 1fdfd339..e7b8328 100644
--- a/chrome/browser/ui/autofill/save_card_bubble_controller_impl_browsertest.cc
+++ b/chrome/browser/ui/autofill/save_card_bubble_controller_impl_browsertest.cc
@@ -10,6 +10,7 @@
 #include "base/macros.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/values.h"
+#include "chrome/browser/ui/autofill/save_card_ui.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/test/test_browser_dialog.h"
@@ -17,6 +18,7 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
+#include "components/autofill/core/common/autofill_features.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/ui_base_features.h"
 
@@ -28,7 +30,12 @@
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
     DialogBrowserTest::SetUpCommandLine(command_line);
-    scoped_feature_list_.InitAndEnableFeature(features::kExperimentalUi);
+    scoped_feature_list_.InitWithFeatures(
+        // Enabled.
+        {::features::kExperimentalUi,
+         features::kAutofillSaveCardSignInAfterLocalSave},
+        // Disabled.
+        {});
   }
 
   std::unique_ptr<base::DictionaryValue> GetTestLegalMessage() {
@@ -61,17 +68,37 @@
     controller_ = SaveCardBubbleControllerImpl::FromWebContents(web_contents);
     DCHECK(controller_);
 
-    // Behavior depends on the test case name (not the most robust, but it will
-    // do).
-    if (name == "Local") {
-      controller_->ShowBubbleForLocalSave(test::GetCreditCard(),
-                                          base::DoNothing());
-    } else {
-      bool should_request_name_from_user =
-          name == "Server_WithCardholderNameTextfield";
-      controller_->ShowBubbleForUpload(
-          test::GetMaskedServerCard(), GetTestLegalMessage(),
-          should_request_name_from_user, base::DoNothing());
+    bool should_request_name_from_user =
+        name.find("WithCardholderNameTextfield") != std::string::npos;
+
+    BubbleType bubble_type = BubbleType::INACTIVE;
+    if (name.find("Local") != std::string::npos)
+      bubble_type = BubbleType::LOCAL_SAVE;
+    if (name.find("Server") != std::string::npos)
+      bubble_type = BubbleType::UPLOAD_SAVE;
+    if (name.find("Promo") != std::string::npos)
+      bubble_type = BubbleType::SIGN_IN_PROMO;
+    if (name.find("Manage") != std::string::npos)
+      bubble_type = BubbleType::MANAGE_CARDS;
+
+    switch (bubble_type) {
+      case BubbleType::LOCAL_SAVE:
+        controller_->ShowBubbleForLocalSave(test::GetCreditCard(),
+                                            base::DoNothing());
+        break;
+      case BubbleType::UPLOAD_SAVE:
+        controller_->ShowBubbleForUpload(
+            test::GetMaskedServerCard(), GetTestLegalMessage(),
+            should_request_name_from_user, base::DoNothing());
+        break;
+      case BubbleType::SIGN_IN_PROMO:
+        controller_->ShowBubbleForSignInPromo();
+        break;
+      case BubbleType::MANAGE_CARDS:
+        controller_->ShowBubbleForManageCardsForTesting(test::GetCreditCard());
+        break;
+      case BubbleType::INACTIVE:
+        break;
     }
   }
 
@@ -102,6 +129,21 @@
   ShowAndVerifyUi();
 }
 
+// Invokes a sign-in promo bubble.
+// TODO(crbug.com/855186): This browsertest isn't emulating the environment
+//   quite correctly; disabling test for now until cause is found.
+/*
+IN_PROC_BROWSER_TEST_F(SaveCardBubbleControllerImplTest, InvokeUi_Promo) {
+  ShowAndVerifyUi();
+}
+*/
+
+// Invokes a bubble displaying the card just saved and an option to
+// manage cards.
+IN_PROC_BROWSER_TEST_F(SaveCardBubbleControllerImplTest, InvokeUi_Manage) {
+  ShowAndVerifyUi();
+}
+
 // Tests that opening a new tab will hide the save card bubble.
 IN_PROC_BROWSER_TEST_F(SaveCardBubbleControllerImplTest, NewTabHidesDialog) {
   ShowUi("Local");
diff --git a/chrome/browser/ui/autofill/save_card_bubble_controller_impl_unittest.cc b/chrome/browser/ui/autofill/save_card_bubble_controller_impl_unittest.cc
index d01c1b07..5006473 100644
--- a/chrome/browser/ui/autofill/save_card_bubble_controller_impl_unittest.cc
+++ b/chrome/browser/ui/autofill/save_card_bubble_controller_impl_unittest.cc
@@ -11,14 +11,17 @@
 #include "base/macros.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/values.h"
 #include "chrome/browser/ui/autofill/save_card_bubble_view.h"
+#include "chrome/browser/ui/autofill/save_card_ui.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "components/autofill/core/browser/autofill_metrics.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
 #include "components/autofill/core/browser/credit_card.h"
+#include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/autofill_prefs.h"
 #include "components/user_prefs/user_prefs.h"
 #include "content/public/browser/navigation_handle.h"
@@ -131,6 +134,8 @@
             browser()->tab_strip_model()->GetActiveWebContents()));
   }
 
+  base::test::ScopedFeatureList scoped_feature_list_;
+
  private:
   class TestSaveCardBubbleView final : public SaveCardBubbleView {
     void Hide() override {}
@@ -907,4 +912,81 @@
           .empty());
 }
 
+// Tests for Sign-In after Local Save.
+
+TEST_F(SaveCardBubbleControllerImplTest,
+       Local_FirstShow_SaveButton_SigninPromo) {
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillSaveCardSignInAfterLocalSave);
+
+  ShowLocalBubble();
+  controller()->OnSaveButton();
+
+  // Sign-in promo should be shown after accepting local save.
+  EXPECT_EQ(BubbleType::SIGN_IN_PROMO, controller()->GetBubbleType());
+  EXPECT_NE(nullptr, controller()->save_card_bubble_view());
+}
+
+TEST_F(SaveCardBubbleControllerImplTest, Local_FirstShow_SaveButton_NoBubble) {
+  scoped_feature_list_.InitAndDisableFeature(
+      features::kAutofillSaveCardSignInAfterLocalSave);
+
+  ShowLocalBubble();
+  controller()->OnSaveButton();
+
+  // When this flag is disabled, no promo should appear and
+  // the icon should go away.
+  EXPECT_FALSE(controller()->IsIconVisible());
+  EXPECT_EQ(nullptr, controller()->save_card_bubble_view());
+}
+
+// Tests for Manage Cards.
+
+TEST_F(SaveCardBubbleControllerImplTest,
+       Local_FirstShow_SaveButton_SigninPromo_Close_Reshow_ManageCards) {
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillSaveCardSignInAfterLocalSave);
+
+  ShowLocalBubble();
+  controller()->OnSaveButton();
+  CloseAndReshowBubble();
+
+  // After closing the sign-in promo, clicking the icon should bring
+  // up the Manage cards bubble.
+  EXPECT_EQ(BubbleType::MANAGE_CARDS, controller()->GetBubbleType());
+  EXPECT_NE(nullptr, controller()->save_card_bubble_view());
+}
+
+TEST_F(SaveCardBubbleControllerImplTest,
+       Local_FirstShow_SaveButton_SigninPromo_Close_Reshow_Close_Navigate) {
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillSaveCardSignInAfterLocalSave);
+
+  ShowLocalBubble();
+  controller()->OnSaveButton();
+  CloseAndReshowBubble();
+  controller()->OnBubbleClosed();
+
+  controller()->set_elapsed(base::TimeDelta::FromSeconds(6));
+  controller()->SimulateNavigation();
+
+  // Icon should disappear after navigating away.
+  EXPECT_FALSE(controller()->IsIconVisible());
+  EXPECT_EQ(nullptr, controller()->save_card_bubble_view());
+}
+
+TEST_F(SaveCardBubbleControllerImplTest,
+       Upload_FirstShow_SaveButton_NoSigninPromo) {
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillSaveCardSignInAfterLocalSave);
+
+  ShowUploadBubble();
+  controller()->OnSaveButton();
+
+  // Icon should disappear after an upload save,
+  // even when this flag is enabled.
+  EXPECT_FALSE(controller()->IsIconVisible());
+  EXPECT_EQ(nullptr, controller()->save_card_bubble_view());
+}
+
 }  // namespace autofill
diff --git a/chrome/browser/ui/autofill/save_card_ui.h b/chrome/browser/ui/autofill/save_card_ui.h
new file mode 100644
index 0000000..965f40cb
--- /dev/null
+++ b/chrome/browser/ui/autofill/save_card_ui.h
@@ -0,0 +1,32 @@
+// 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 CHROME_BROWSER_UI_AUTOFILL_SAVE_CARD_UI_H_
+#define CHROME_BROWSER_UI_AUTOFILL_SAVE_CARD_UI_H_
+
+namespace autofill {
+
+// The type of save card bubble to show.
+enum class BubbleType {
+  // Save prompt when the user is saving locally.
+  LOCAL_SAVE,
+
+  // Save prompt when uploading a card to Google payments.
+  UPLOAD_SAVE,
+
+  // The sign-in promo that is shown after local save.
+  SIGN_IN_PROMO,
+
+  // The manage cards bubble when bubble is reshown after
+  // icon is clicked.
+  MANAGE_CARDS,
+
+  // There is no bubble to show anymore. This also
+  // indicates that the icon should not be visible.
+  INACTIVE
+};
+
+}  // namespace autofill
+
+#endif  // CHROME_BROWSER_UI_AUTOFILL_SAVE_CARD_UI_H_
diff --git a/chrome/browser/ui/cocoa/autofill/save_card_bubble_view_views.mm b/chrome/browser/ui/cocoa/autofill/save_card_bubble_view_views.mm
index 4984c23..d4446edc 100644
--- a/chrome/browser/ui/cocoa/autofill/save_card_bubble_view_views.mm
+++ b/chrome/browser/ui/cocoa/autofill/save_card_bubble_view_views.mm
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/platform_util.h"
+#include "chrome/browser/ui/autofill/save_card_ui.h"
 #include "chrome/browser/ui/cocoa/browser_dialogs_views_mac.h"
 #include "chrome/browser/ui/cocoa/browser_window_cocoa.h"
 #include "chrome/browser/ui/cocoa/browser_window_controller.h"
@@ -10,6 +11,9 @@
 #include "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h"
 #include "chrome/browser/ui/cocoa/location_bar/save_credit_card_decoration.h"
 #include "chrome/browser/ui/views/autofill/save_card_bubble_views.h"
+#include "chrome/browser/ui/views/autofill/save_card_manage_cards_bubble_views.h"
+#include "chrome/browser/ui/views/autofill/save_card_offer_bubble_views.h"
+#include "chrome/browser/ui/views/autofill/save_card_sign_in_promo_bubble_views.h"
 #include "ui/base/cocoa/cocoa_base_utils.h"
 #include "ui/gfx/mac/coordinate_conversion.h"
 
@@ -26,8 +30,29 @@
   gfx::Point anchor =
       gfx::ScreenPointFromNSPoint(ui::ConvertPointFromWindowToScreen(
           parent_window, location_bar->GetSaveCreditCardBubblePoint()));
-  autofill::SaveCardBubbleViews* bubble =
-      new SaveCardBubbleViews(nullptr, anchor, web_contents, controller);
+
+  autofill::BubbleType bubble_type = controller->GetBubbleType();
+  autofill::SaveCardBubbleViews* bubble = nullptr;
+
+  switch (bubble_type) {
+    case autofill::BubbleType::LOCAL_SAVE:
+    case autofill::BubbleType::UPLOAD_SAVE:
+      bubble = new autofill::SaveCardOfferBubbleViews(nullptr, anchor,
+                                                      web_contents, controller);
+      break;
+    case autofill::BubbleType::SIGN_IN_PROMO:
+      bubble = new autofill::SaveCardSignInPromoBubbleViews(
+          nullptr, anchor, web_contents, controller);
+      break;
+    case autofill::BubbleType::MANAGE_CARDS:
+      bubble = new autofill::SaveCardManageCardsBubbleViews(
+          nullptr, anchor, web_contents, controller);
+      break;
+    case autofill::BubbleType::INACTIVE:
+      break;
+  }
+
+  DCHECK(bubble);
 
   // Usually the anchor view determines the arrow type, but there is none in
   // MacViews. So always use TOP_RIGHT, even in fullscreen.
diff --git a/chrome/browser/ui/cocoa/tabs/tab_strip_controller_unittest.mm b/chrome/browser/ui/cocoa/tabs/tab_strip_controller_unittest.mm
index 9cbc2ce..03a6c116 100644
--- a/chrome/browser/ui/cocoa/tabs/tab_strip_controller_unittest.mm
+++ b/chrome/browser/ui/cocoa/tabs/tab_strip_controller_unittest.mm
@@ -318,7 +318,7 @@
       MediaCaptureDevicesDispatcher::GetInstance()->
           GetMediaStreamCaptureIndicator();
   const MediaStreamDevice dummyVideoCaptureDevice(
-      content::MEDIA_TAB_VIDEO_CAPTURE, "dummy_id", "dummy name");
+      content::MEDIA_GUM_TAB_VIDEO_CAPTURE, "dummy_id", "dummy name");
   std::unique_ptr<MediaStreamUI> streamUi(indicator->RegisterMediaStream(
       contents, MediaStreamDevices(1, dummyVideoCaptureDevice)));
   streamUi->OnStarted(base::DoNothing());
diff --git a/chrome/browser/ui/cocoa/touchbar/suggested_text_touch_bar_controller.h b/chrome/browser/ui/cocoa/touchbar/suggested_text_touch_bar_controller.h
index 7e4c5d3..4c4f5e2 100644
--- a/chrome/browser/ui/cocoa/touchbar/suggested_text_touch_bar_controller.h
+++ b/chrome/browser/ui/cocoa/touchbar/suggested_text_touch_bar_controller.h
@@ -19,6 +19,10 @@
 class WebContents;
 }  // namespace content
 
+namespace gfx {
+class Range;
+}  // namespace gfx
+
 @interface SuggestedTextTouchBarController
     : NSObject<NSTouchBarDelegate, NSCandidateListTouchBarItemDelegate>
 
@@ -40,19 +44,15 @@
      endSelectingCandidateAtIndex:(NSInteger)index
     API_AVAILABLE(macos(10.12.2));
 
-- (void)webContentsTextSelectionChanged:(const base::string16&)text
-                                  range:(NSRange)range
-    API_AVAILABLE(macos(10.12.2));
-
-- (void)webContentsFinishedLoading API_AVAILABLE(macos(10.12.2));
+- (void)updateTextSelection:(const base::string16&)text
+                      range:(const gfx::Range&)range;
 
 // Returns a range from start to the end of the word that the cursor is
 // currently in.
 - (NSRange)editingWordRangeFromText:(const base::string16&)text
                      cursorPosition:(size_t)cursor;
 
-- (void)requestSuggestionsForText:(NSString*)text
-                          inRange:(NSRange)range API_AVAILABLE(macos(10.12.2));
+- (void)requestSuggestions API_AVAILABLE(macos(10.12.2));
 
 // Select the range of the editing word and replace it with a suggestion
 // from the touch bar.
@@ -69,8 +69,8 @@
 - (WebTextfieldTouchBarController*)controller;
 - (void)setWebContents:(content::WebContents*)webContents;
 - (content::WebContents*)webContents;
-- (void)setSelectionRange:(NSRange)range;
-- (NSRange)selectionRange;
+- (void)setSelectionRange:(const gfx::Range&)range;
+- (gfx::Range)selectionRange;
 
 @end
 
diff --git a/chrome/browser/ui/cocoa/touchbar/suggested_text_touch_bar_controller.mm b/chrome/browser/ui/cocoa/touchbar/suggested_text_touch_bar_controller.mm
index 0cfeb53..7895679e 100644
--- a/chrome/browser/ui/cocoa/touchbar/suggested_text_touch_bar_controller.mm
+++ b/chrome/browser/ui/cocoa/touchbar/suggested_text_touch_bar_controller.mm
@@ -6,6 +6,7 @@
 
 #include "base/i18n/break_iterator.h"
 #include "base/strings/sys_string_conversions.h"
+#include "content/public/browser/render_widget_host_view.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
 #import "ui/base/cocoa/touch_bar_util.h"
@@ -32,16 +33,12 @@
 
   void DidChangeTextSelection(const base::string16& text,
                               const gfx::Range& range) override {
-    if (@available(macOS 10.12.2, *)) {
-      [owner_ webContentsTextSelectionChanged:text range:range.ToNSRange()];
-    }
+    [owner_ updateTextSelection:text range:range];
   }
 
   void DidFinishLoad(content::RenderFrameHost* render_frame_host,
                      const GURL& validated_url) override {
-    if (@available(macOS 10.12.2, *)) {
-      [owner_ webContentsFinishedLoading];
-    }
+    [owner_ updateTextSelection:base::string16() range:gfx::Range()];
   }
 
  private:
@@ -80,6 +77,10 @@
 
 @implementation SuggestedTextTouchBarController
 
+- (BOOL)isTextfieldFocused {
+  return webContents_ && webContents_->IsFocusedElementEditable();
+}
+
 - (instancetype)initWithWebContents:(content::WebContents*)webContents
                          controller:
                              (WebTextfieldTouchBarController*)controller {
@@ -94,7 +95,7 @@
 }
 
 - (NSTouchBar*)makeTouchBar {
-  if (!webContents_ || !webContents_->IsFocusedElementEditable())
+  if (![self isTextfieldFocused])
     return nil;
 
   base::scoped_nsobject<NSTouchBar> touchBar([[ui::NSTouchBar() alloc] init]);
@@ -143,26 +144,19 @@
   ui::LogTouchBarUMA(ui::TouchBarAction::TEXT_SUGGESTION);
 }
 
-- (void)webContentsTextSelectionChanged:(const base::string16&)text
-                                  range:(NSRange)range {
-  if (webContents_->IsFocusedElementEditable()) {
-    [self setText:base::SysUTF16ToNSString(text)];
-    selectionRange_ = range;
-    editingWordRange_ =
-        [self editingWordRangeFromText:text cursorPosition:range.location];
-    [self requestSuggestionsForText:text_ inRange:range];
-  } else {
-    [controller_ invalidateTouchBar];
-  }
-}
+- (void)updateTextSelection:(const base::string16&)text
+                      range:(const gfx::Range&)range {
+  if (@available(macOS 10.12.2, *)) {
+    if (![self isTextfieldFocused]) {
+      [controller_ invalidateTouchBar];
+      return;
+    }
 
-- (void)webContentsFinishedLoading {
-  if (webContents_->IsFocusedElementEditable()) {
-    // TODO(tnijssen): Actually request the current text and cursor position.
-    [self setText:@""];
-    selectionRange_ = NSMakeRange(0, 0);
-    editingWordRange_ = NSMakeRange(0, 0);
-    [self requestSuggestionsForText:@"" inRange:NSMakeRange(0, 0)];
+    text_.reset([base::SysUTF16ToNSString(text) retain]);
+    selectionRange_ = range.ToNSRange();
+    editingWordRange_ =
+        [self editingWordRangeFromText:text cursorPosition:range.start()];
+    [self requestSuggestions];
   }
 }
 
@@ -204,11 +198,11 @@
   return NSMakeRange(location, length);
 }
 
-- (void)requestSuggestionsForText:(NSString*)text inRange:(NSRange)range {
+- (void)requestSuggestions {
   NSSpellChecker* spell_checker = [NSSpellChecker sharedSpellChecker];
   [spell_checker
-      requestCandidatesForSelectedRange:range
-                               inString:text
+      requestCandidatesForSelectedRange:selectionRange_
+                               inString:text_
                                   types:NSTextCheckingAllSystemTypes
                                 options:nil
                  inSpellDocumentWithTag:0
@@ -254,21 +248,34 @@
 }
 
 - (void)setWebContents:(content::WebContents*)webContents {
-  // TODO(tnijssen): Update the text suggestions when we change web contents.
   webContents_ = webContents;
   observer_->UpdateWebContents(webContents);
+
+  if (![self isTextfieldFocused]) {
+    [controller_ invalidateTouchBar];
+    return;
+  }
+
+  const base::string16 text =
+      webContents_->GetTopLevelRenderWidgetHostView()->GetSurroundingText();
+  const gfx::Range range =
+      webContents_->GetTopLevelRenderWidgetHostView()->GetSelectedRange();
+  if (range.IsValid())
+    [self updateTextSelection:text range:range];
+  else
+    [self updateTextSelection:base::string16() range:gfx::Range()];
 }
 
 - (content::WebContents*)webContents {
   return webContents_;
 }
 
-- (void)setSelectionRange:(NSRange)range {
-  selectionRange_ = range;
+- (void)setSelectionRange:(const gfx::Range&)range {
+  selectionRange_ = range.ToNSRange();
 }
 
-- (NSRange)selectionRange {
-  return selectionRange_;
+- (gfx::Range)selectionRange {
+  return gfx::Range(selectionRange_);
 }
 
 @end
diff --git a/chrome/browser/ui/cocoa/touchbar/suggested_text_touch_bar_controller_browsertest.mm b/chrome/browser/ui/cocoa/touchbar/suggested_text_touch_bar_controller_browsertest.mm
index 33821b7..e9d7c70 100644
--- a/chrome/browser/ui/cocoa/touchbar/suggested_text_touch_bar_controller_browsertest.mm
+++ b/chrome/browser/ui/cocoa/touchbar/suggested_text_touch_bar_controller_browsertest.mm
@@ -52,8 +52,8 @@
 
 @implementation MockSuggestedTextTouchBarController
 
-- (void)requestSuggestionsForText:(NSString*)text inRange:(NSRange)range {
-  [self setSuggestions:@[ text ]];
+- (void)requestSuggestions {
+  [self setSuggestions:@[ [self text] ]];
   [[self controller] invalidateTouchBar];
 }
 
@@ -65,10 +65,6 @@
 
 namespace {
 
-NSString* const kSuggestedTextTouchBarId = @"suggested-text";
-NSString* const kText = @"text";
-NSString* const kEmptyText = @"";
-
 class SuggestedTextTouchBarControllerBrowserTest : public InProcessBrowserTest {
  public:
   void SetUp() override {
@@ -80,10 +76,8 @@
     web_textfield_controller_.reset(
         [[MockWebTextfieldTouchBarController alloc] init]);
     [web_textfield_controller_ resetNumInvalidations];
-    content::WebContents* web_contents =
-        browser()->tab_strip_model()->GetActiveWebContents();
     touch_bar_controller_.reset([[MockSuggestedTextTouchBarController alloc]
-        initWithWebContents:web_contents
+        initWithWebContents:GetActiveWebContents()
                  controller:web_textfield_controller_]);
   }
 
@@ -98,15 +92,10 @@
         browser(), GURL("data:text/html;charset=utf-8,<input type=\"text\">"));
   }
 
-  MockWebTextfieldTouchBarController* web_textfield_controller() const {
-    return web_textfield_controller_;
+  content::WebContents* GetActiveWebContents() {
+    return browser()->tab_strip_model()->GetActiveWebContents();
   }
 
-  MockSuggestedTextTouchBarController* touch_bar_controller() const {
-    return touch_bar_controller_;
-  }
-
- private:
   base::scoped_nsobject<MockWebTextfieldTouchBarController>
       web_textfield_controller_;
   base::scoped_nsobject<MockSuggestedTextTouchBarController>
@@ -118,13 +107,15 @@
 IN_PROC_BROWSER_TEST_F(SuggestedTextTouchBarControllerBrowserTest,
                        TouchBarTest) {
   if (@available(macOS 10.12.2, *)) {
+    NSString* const kSuggestedTextTouchBarId = @"suggested-text";
+
     // Touch bar shouldn't appear if the focused element is not a textfield.
     UnfocusTextfield();
-    EXPECT_FALSE([touch_bar_controller() makeTouchBar]);
+    EXPECT_FALSE([touch_bar_controller_ makeTouchBar]);
 
     // Touch bar should appear if a textfield is focused.
     FocusTextfield();
-    NSTouchBar* touch_bar = [touch_bar_controller() makeTouchBar];
+    NSTouchBar* touch_bar = [touch_bar_controller_ makeTouchBar];
     EXPECT_TRUE(touch_bar);
     EXPECT_TRUE([[touch_bar customizationIdentifier]
         isEqual:ui::GetTouchBarId(kSuggestedTextTouchBarId)]);
@@ -133,47 +124,81 @@
 
 // Tests that a change in text selection is handled properly.
 IN_PROC_BROWSER_TEST_F(SuggestedTextTouchBarControllerBrowserTest,
-                       TextSelectionChangedTest) {
+                       UpdateTextSelectionTest) {
+  NSString* const kText = @"text";
+  NSString* const kEmptyText = @"";
+  const gfx::Range kRange = gfx::Range(0, 4);
+  const gfx::Range kEmptyRange = gfx::Range();
+
+  // If not in a textfield,
+  //  1. Textfield text should not be saved.
+  //  2. Selected range should not be saved.
+  //  3. Touch bar should be invalidated (if on MacOS 10.12.2 or later).
+  UnfocusTextfield();
+  [touch_bar_controller_ setText:kEmptyText];
+  [touch_bar_controller_ setSelectionRange:kEmptyRange];
+  [web_textfield_controller_ resetNumInvalidations];
+
+  [touch_bar_controller_ updateTextSelection:base::SysNSStringToUTF16(kText)
+                                       range:kRange];
+  EXPECT_STREQ(kEmptyText.UTF8String, [touch_bar_controller_ text].UTF8String);
+  EXPECT_EQ(kEmptyRange, [touch_bar_controller_ selectionRange]);
+  if (@available(macOS 10.12.2, *))
+    EXPECT_EQ(1, [web_textfield_controller_ numInvalidations]);
+  else
+    EXPECT_EQ(0, [web_textfield_controller_ numInvalidations]);
+
+  // If in a textfield and on MacOS 10.12.2 or later,
+  //   1. Textfield text should be saved.
+  //   2. Selected range should be saved.
+  //   3. Suggestions should be generated based on text selection.
+  //   4. Touch bar should be invalidated.
+  FocusTextfield();
+  [touch_bar_controller_ setText:kEmptyText];
+  [touch_bar_controller_ setSelectionRange:kEmptyRange];
+  [web_textfield_controller_ resetNumInvalidations];
+
+  [touch_bar_controller_ updateTextSelection:base::SysNSStringToUTF16(kText)
+                                       range:kRange];
   if (@available(macOS 10.12.2, *)) {
-    const NSRange kRange = NSMakeRange(0, [kText length]);
-    const NSRange kEmptyRange = NSMakeRange(0, 0);
+    EXPECT_STREQ(kText.UTF8String, [touch_bar_controller_ text].UTF8String);
+    EXPECT_EQ(kRange, [touch_bar_controller_ selectionRange]);
+    EXPECT_STREQ(kText.UTF8String,
+                 [touch_bar_controller_ firstSuggestion].UTF8String);
+    EXPECT_EQ(1, [web_textfield_controller_ numInvalidations]);
+  } else {
+    EXPECT_STREQ(kEmptyText.UTF8String,
+                 [touch_bar_controller_ text].UTF8String);
+    EXPECT_EQ(kEmptyRange, [touch_bar_controller_ selectionRange]);
+    EXPECT_EQ(0, [web_textfield_controller_ numInvalidations]);
+  }
+}
 
-    // If not in a textfield,
-    //  1. Textfield text should not be saved.
-    //  2. Selected range should not be saved.
-    //  3. Touch bar should be invalidated.
-    UnfocusTextfield();
-    [touch_bar_controller() setText:kEmptyText];
-    [touch_bar_controller() setSelectionRange:kEmptyRange];
-    [web_textfield_controller() resetNumInvalidations];
+// Tests that a change in WebContents is handled properly.
+IN_PROC_BROWSER_TEST_F(SuggestedTextTouchBarControllerBrowserTest,
+                       SetWebContentsTest) {
+  NSString* const kText = @"text";
+  const gfx::Range kRange = gfx::Range(1, 1);
 
-    [touch_bar_controller()
-        webContentsTextSelectionChanged:base::SysNSStringToUTF16(kText)
-                                  range:kRange];
-    EXPECT_TRUE([kEmptyText isEqualToString:[touch_bar_controller() text]]);
-    EXPECT_EQ(gfx::Range(kEmptyRange),
-              gfx::Range([touch_bar_controller() selectionRange]));
-    EXPECT_EQ(1, [web_textfield_controller() numInvalidations]);
+  // A null-pointer should not break the controller.
+  UnfocusTextfield();
+  [touch_bar_controller_ setWebContents:nullptr];
+  EXPECT_FALSE([touch_bar_controller_ webContents]);
 
-    // If in a textfield,
-    //   1. Textfield text should be saved.
-    //   2. Selected range should be saved.
-    //   3. Suggestions should be generated based on text selection.
-    //   4. Touch bar should be invalidated.
-    FocusTextfield();
-    [touch_bar_controller() setText:kEmptyText];
-    [touch_bar_controller() setSelectionRange:kEmptyRange];
-    [web_textfield_controller() resetNumInvalidations];
+  FocusTextfield();
+  [touch_bar_controller_ setText:kText];
+  [touch_bar_controller_ setSelectionRange:kRange];
 
-    [touch_bar_controller()
-        webContentsTextSelectionChanged:base::SysNSStringToUTF16(kText)
-                                  range:kRange];
-    EXPECT_TRUE([kText isEqualToString:[touch_bar_controller() text]]);
-    EXPECT_EQ(gfx::Range(kRange),
-              gfx::Range([touch_bar_controller() selectionRange]));
-    EXPECT_TRUE(
-        [kText isEqualToString:[touch_bar_controller() firstSuggestion]]);
-    EXPECT_EQ(1, [web_textfield_controller() numInvalidations]);
+  // The text selection should change on MacOS 10.12.2 and later if the
+  // WebContents pointer is not null.
+  [touch_bar_controller_ setWebContents:GetActiveWebContents()];
+  EXPECT_EQ(GetActiveWebContents(), [touch_bar_controller_ webContents]);
+  if (@available(macOS 10.12.2, *)) {
+    EXPECT_STRNE(kText.UTF8String, [touch_bar_controller_ text].UTF8String);
+    EXPECT_NE(kRange, [touch_bar_controller_ selectionRange]);
+  } else {
+    EXPECT_STREQ(kText.UTF8String, [touch_bar_controller_ text].UTF8String);
+    EXPECT_EQ(kRange, [touch_bar_controller_ selectionRange]);
   }
 }
 
diff --git a/chrome/browser/ui/cocoa/touchbar/suggested_text_touch_bar_controller_unittest.mm b/chrome/browser/ui/cocoa/touchbar/suggested_text_touch_bar_controller_unittest.mm
index 2574c0a..590ae073 100644
--- a/chrome/browser/ui/cocoa/touchbar/suggested_text_touch_bar_controller_unittest.mm
+++ b/chrome/browser/ui/cocoa/touchbar/suggested_text_touch_bar_controller_unittest.mm
@@ -39,11 +39,11 @@
   if (@available(macOS 10.12.2, *)) {
     base::scoped_nsobject<NSCandidateListTouchBarItem> item;
 
-    [controller_ setSelectionRange:NSMakeRange(0, 0)];
+    [controller_ setSelectionRange:gfx::Range()];
     item.reset([controller_ createCandidateListItem]);
     EXPECT_FALSE([item isCollapsed]);
 
-    [controller_ setSelectionRange:NSMakeRange(0, 1)];
+    [controller_ setSelectionRange:gfx::Range(0, 1)];
     item.reset([controller_ createCandidateListItem]);
     EXPECT_TRUE([item isCollapsed]);
   }
diff --git a/chrome/browser/ui/views/autofill/dialog_view_ids.h b/chrome/browser/ui/views/autofill/dialog_view_ids.h
index dbecee1..50bd64e0 100644
--- a/chrome/browser/ui/views/autofill/dialog_view_ids.h
+++ b/chrome/browser/ui/views/autofill/dialog_view_ids.h
@@ -19,10 +19,14 @@
   MAIN_CONTENT_VIEW_LOCAL,   // The main content view, for a local save bubble
   MAIN_CONTENT_VIEW_UPLOAD,  // The main content view, for an upload save bubble
   FOOTNOTE_VIEW,             // Contains the legal messages for upload save
+  SIGN_IN_PROMO_VIEW,        // Contains the sign-in promo view
+  MANAGE_CARDS_VIEW,         // The manage cards view
 
   // The following are views::LabelButton objects (clickable).
-  OK_BUTTON,      // Can say [Save], [Next], and [Confirm] depend on context
-  CANCEL_BUTTON,  // Typically says [No thanks]
+  OK_BUTTON,            // Can say [Save], [Next], [Confirm],
+                        // or [Done] depending on context
+  CANCEL_BUTTON,        // Typically says [No thanks]
+  MANAGE_CARDS_BUTTON,  // Typicall says [Manage cards]
 
   // The following are views::Link objects (clickable).
   LEARN_MORE_LINK,
diff --git a/chrome/browser/ui/views/autofill/save_card_bubble_views.cc b/chrome/browser/ui/views/autofill/save_card_bubble_views.cc
index 0461b7fd..3c620f50 100644
--- a/chrome/browser/ui/views/autofill/save_card_bubble_views.cc
+++ b/chrome/browser/ui/views/autofill/save_card_bubble_views.cc
@@ -4,9 +4,6 @@
 
 #include "chrome/browser/ui/views/autofill/save_card_bubble_views.h"
 
-#include <stddef.h>
-#include <memory>
-
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
 #include "chrome/app/vector_icons/vector_icons.h"
@@ -14,7 +11,6 @@
 #include "chrome/browser/ui/views/autofill/dialog_view_ids.h"
 #include "chrome/browser/ui/views/harmony/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/harmony/chrome_typography.h"
-#include "components/autofill/core/browser/autofill_experiments.h"
 #include "components/autofill/core/browser/autofill_metrics.h"
 #include "components/autofill/core/browser/credit_card.h"
 #include "components/autofill/core/browser/legal_message_line.h"
@@ -51,25 +47,21 @@
 
 const int kGooglePayLogoSeparatorHeight = 12;
 
-const int kTooltipIconSize = 12;
-
 const SkColor kTitleSeparatorColor = SkColorSetRGB(0x9E, 0x9E, 0x9E);
 
-std::unique_ptr<views::StyledLabel> CreateLegalMessageLineLabel(
-    const LegalMessageLine& line,
-    views::StyledLabelListener* listener) {
-  std::unique_ptr<views::StyledLabel> label(
-      new views::StyledLabel(line.text(), listener));
-  label->SetTextContext(CONTEXT_BODY_TEXT_LARGE);
-  label->SetDefaultTextStyle(ChromeTextStyle::STYLE_SECONDARY);
-  for (const LegalMessageLine::Link& link : line.links()) {
-    label->AddStyleRange(link.range,
-                         views::StyledLabel::RangeStyleInfo::CreateForLink());
-  }
-  return label;
+}  // namespace
+
+SaveCardBubbleViews::SyncPromoDelegate::SyncPromoDelegate(
+    SaveCardBubbleController* controller)
+    : controller_(controller) {
+  DCHECK(controller_);
 }
 
-}  // namespace
+void SaveCardBubbleViews::SyncPromoDelegate::OnEnableSync(
+    const AccountInfo& account,
+    bool is_default_promo_account) {
+  controller_->OnSyncPromoAccepted(account, is_default_promo_account);
+}
 
 SaveCardBubbleViews::SaveCardBubbleViews(views::View* anchor_view,
                                          const gfx::Point& anchor_point,
@@ -98,29 +90,12 @@
 }
 
 views::View* SaveCardBubbleViews::CreateFootnoteView() {
-  if (controller_->GetLegalMessageLines().empty())
-    return nullptr;
-
-  // Use BoxLayout to provide insets around the label.
-  footnote_view_ = new View();
-  footnote_view_->SetLayoutManager(
-      std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical));
-  footnote_view_->set_id(DialogViewId::FOOTNOTE_VIEW);
-
-  // Add a StyledLabel for each line of the legal message.
-  for (const LegalMessageLine& line : controller_->GetLegalMessageLines()) {
-    footnote_view_->AddChildView(
-        CreateLegalMessageLineLabel(line, this).release());
-  }
-
-  return footnote_view_;
+  return nullptr;
 }
 
 bool SaveCardBubbleViews::Accept() {
   if (controller_)
-    controller_->OnSaveButton(cardholder_name_textfield_
-                                  ? cardholder_name_textfield_->text()
-                                  : base::string16());
+    controller_->OnSaveButton(base::string16());
   return true;
 }
 
@@ -151,28 +126,6 @@
              : ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL;
 }
 
-base::string16 SaveCardBubbleViews::GetDialogButtonLabel(
-    ui::DialogButton button) const {
-  return l10n_util::GetStringUTF16(button == ui::DIALOG_BUTTON_OK
-                                       ? IDS_AUTOFILL_SAVE_CARD_PROMPT_ACCEPT
-                                       : IDS_NO_THANKS);
-}
-
-bool SaveCardBubbleViews::IsDialogButtonEnabled(ui::DialogButton button) const {
-  if (button == ui::DIALOG_BUTTON_CANCEL)
-    return true;
-
-  DCHECK_EQ(ui::DIALOG_BUTTON_OK, button);
-  if (cardholder_name_textfield_) {
-    // If requesting the user confirm the name, it cannot be blank.
-    base::string16 trimmed_text;
-    base::TrimWhitespace(cardholder_name_textfield_->text(), base::TRIM_ALL,
-                         &trimmed_text);
-    return !trimmed_text.empty();
-  }
-  return true;
-}
-
 gfx::Size SaveCardBubbleViews::CalculatePreferredSize() const {
   const int width = ChromeLayoutProvider::Get()->GetDistanceMetric(
                         DISTANCE_BUBBLE_PREFERRED_WIDTH) -
@@ -181,9 +134,10 @@
 }
 
 void SaveCardBubbleViews::AddedToWidget() {
-  // Use a custom title container if this is a server card. Done when this view
-  // is added to the widget, so the bubble frame view is guaranteed to exist.
-  if (GetCurrentFlowStep() != UPLOAD_SAVE_ONLY_STEP)
+  // Use a custom title container if offering to upload a server card.
+  // Done when this view is added to the widget, so the bubble frame
+  // view is guaranteed to exist.
+  if (!controller_->IsUploadSave())
     return;
   ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
 
@@ -244,51 +198,13 @@
   }
 }
 
-void SaveCardBubbleViews::StyledLabelLinkClicked(views::StyledLabel* label,
-                                                 const gfx::Range& range,
-                                                 int event_flags) {
-  if (!controller_)
-    return;
-
-  // Index of |label| within its parent's view hierarchy is the same as the
-  // legal message line index. DCHECK this assumption to guard against future
-  // layout changes.
-  DCHECK_EQ(static_cast<size_t>(label->parent()->child_count()),
-            controller_->GetLegalMessageLines().size());
-
-  const auto& links =
-      controller_->GetLegalMessageLines()[label->parent()->GetIndexOf(label)]
-          .links();
-  for (const LegalMessageLine::Link& link : links) {
-    if (link.range == range) {
-      controller_->OnLegalMessageLinkClicked(link.url);
-      return;
-    }
-  }
-
-  // |range| was not found.
-  NOTREACHED();
-}
-
-void SaveCardBubbleViews::ContentsChanged(views::Textfield* sender,
-                                          const base::string16& new_contents) {
-  DCHECK_EQ(cardholder_name_textfield_, sender);
-  DialogModelChanged();
-}
-
 views::View* SaveCardBubbleViews::GetFootnoteViewForTesting() {
   return footnote_view_;
 }
 
 SaveCardBubbleViews::~SaveCardBubbleViews() {}
 
-SaveCardBubbleViews::CurrentFlowStep SaveCardBubbleViews::GetCurrentFlowStep()
-    const {
-  // Local save if no legal messages, upload save otherwise.
-  return controller_->GetLegalMessageLines().empty() ? LOCAL_SAVE_ONLY_STEP
-                                                     : UPLOAD_SAVE_ONLY_STEP;
-}
-
+// Overridden
 std::unique_ptr<views::View> SaveCardBubbleViews::CreateMainContentView() {
   std::unique_ptr<views::View> view = std::make_unique<views::View>();
   ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
@@ -296,9 +212,6 @@
   view->SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::kVertical, gfx::Insets(),
       provider->GetDistanceMetric(views::DISTANCE_UNRELATED_CONTROL_VERTICAL)));
-  view->set_id(GetCurrentFlowStep() == LOCAL_SAVE_ONLY_STEP
-                   ? DialogViewId::MAIN_CONTENT_VIEW_LOCAL
-                   : DialogViewId::MAIN_CONTENT_VIEW_UPLOAD);
 
   // If applicable, add the upload explanation label.  Appears above the card
   // info.
@@ -343,78 +256,14 @@
       card.AbbreviatedExpirationDateForDisplay(), CONTEXT_BODY_TEXT_LARGE,
       ChromeTextStyle::STYLE_SECONDARY));
 
-  // If necessary, add the cardholder name label and textfield to the upload
-  // save dialog.
-  if (controller_->ShouldRequestNameFromUser()) {
-    std::unique_ptr<views::View> cardholder_name_label_row =
-        std::make_unique<views::View>();
-
-    // Set up cardholder name label.
-    // TODO(jsaul): DISTANCE_RELATED_BUTTON_HORIZONTAL isn't the right choice
-    //              here, but DISTANCE_RELATED_CONTROL_HORIZONTAL gives too much
-    //              padding. Make a new Harmony DistanceMetric?
-    cardholder_name_label_row->SetLayoutManager(
-        std::make_unique<views::BoxLayout>(
-            views::BoxLayout::kHorizontal, gfx::Insets(),
-            provider->GetDistanceMetric(
-                views::DISTANCE_RELATED_BUTTON_HORIZONTAL)));
-    std::unique_ptr<views::Label> cardholder_name_label =
-        std::make_unique<views::Label>(
-            l10n_util::GetStringUTF16(
-                IDS_AUTOFILL_SAVE_CARD_PROMPT_CARDHOLDER_NAME),
-            CONTEXT_BODY_TEXT_LARGE, ChromeTextStyle::STYLE_SECONDARY);
-    cardholder_name_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-    cardholder_name_label_row->AddChildView(cardholder_name_label.release());
-
-    // Prepare the prefilled cardholder name.
-    base::string16 prefilled_name;
-    if (!IsAutofillUpstreamBlankCardholderNameFieldExperimentEnabled()) {
-      prefilled_name =
-          base::UTF8ToUTF16(controller_->GetAccountInfo().full_name);
-    }
-
-    // Set up cardholder name label tooltip ONLY if the cardholder name
-    // textfield will be prefilled.
-    if (!prefilled_name.empty()) {
-      std::unique_ptr<views::TooltipIcon> cardholder_name_tooltip =
-          std::make_unique<views::TooltipIcon>(
-              l10n_util::GetStringUTF16(
-                  IDS_AUTOFILL_SAVE_CARD_PROMPT_CARDHOLDER_NAME_TOOLTIP),
-              kTooltipIconSize);
-      cardholder_name_tooltip->set_anchor_point_arrow(
-          views::BubbleBorder::Arrow::TOP_LEFT);
-      cardholder_name_tooltip->set_id(DialogViewId::CARDHOLDER_NAME_TOOLTIP);
-      cardholder_name_label_row->AddChildView(
-          cardholder_name_tooltip.release());
-    }
-
-    // Set up cardholder name textfield.
-    DCHECK(!cardholder_name_textfield_);
-    cardholder_name_textfield_ = new views::Textfield();
-    cardholder_name_textfield_->set_controller(this);
-    cardholder_name_textfield_->set_id(DialogViewId::CARDHOLDER_NAME_TEXTFIELD);
-    cardholder_name_textfield_->SetAccessibleName(l10n_util::GetStringUTF16(
-        IDS_AUTOFILL_SAVE_CARD_PROMPT_CARDHOLDER_NAME));
-    cardholder_name_textfield_->SetTextInputType(
-        ui::TextInputType::TEXT_INPUT_TYPE_TEXT);
-    cardholder_name_textfield_->SetText(prefilled_name);
-    AutofillMetrics::LogSaveCardCardholderNamePrefilled(
-        !prefilled_name.empty());
-
-    // Add cardholder name elements to a single view, then to the final dialog.
-    std::unique_ptr<views::View> cardholder_name_view =
-        std::make_unique<views::View>();
-    cardholder_name_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
-        views::BoxLayout::kVertical, gfx::Insets(),
-        provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL)));
-    cardholder_name_view->AddChildView(cardholder_name_label_row.release());
-    cardholder_name_view->AddChildView(cardholder_name_textfield_);
-    view->AddChildView(cardholder_name_view.release());
-  }
-
   return view;
 }
 
+void SaveCardBubbleViews::SetFootnoteViewForTesting(
+    views::View* footnote_view) {
+  footnote_view_ = footnote_view;
+}
+
 void SaveCardBubbleViews::AssignIdsToDialogClientView() {
   auto* ok_button = GetDialogClientView()->ok_button();
   if (ok_button)
@@ -431,8 +280,8 @@
   // controls; use views::TEXT. For local cards, since there is no explanation,
   // use views::CONTROL instead.
   set_margins(ChromeLayoutProvider::Get()->GetDialogInsetsForContentType(
-      GetCurrentFlowStep() == UPLOAD_SAVE_ONLY_STEP ? views::TEXT
-                                                    : views::CONTROL,
+      controller_->GetExplanatoryMessage().empty() ? views::CONTROL
+                                                   : views::TEXT,
       views::CONTROL));
   AddChildView(CreateMainContentView().release());
 }
diff --git a/chrome/browser/ui/views/autofill/save_card_bubble_views.h b/chrome/browser/ui/views/autofill/save_card_bubble_views.h
index de1b384..ec3cc296 100644
--- a/chrome/browser/ui/views/autofill/save_card_bubble_views.h
+++ b/chrome/browser/ui/views/autofill/save_card_bubble_views.h
@@ -5,31 +5,25 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_AUTOFILL_SAVE_CARD_BUBBLE_VIEWS_H_
 #define CHROME_BROWSER_UI_VIEWS_AUTOFILL_SAVE_CARD_BUBBLE_VIEWS_H_
 
-#include "base/macros.h"
 #include "chrome/browser/ui/autofill/save_card_bubble_view.h"
+#include "chrome/browser/ui/sync/bubble_sync_promo_delegate.h"
 #include "chrome/browser/ui/views/location_bar/location_bar_bubble_delegate_view.h"
 #include "components/autofill/core/browser/ui/save_card_bubble_controller.h"
-#include "ui/views/controls/styled_label_listener.h"
-#include "ui/views/controls/textfield/textfield_controller.h"
 
 namespace content {
 class WebContents;
 }
 
-namespace views {
-class StyledLabel;
-class Textfield;
-}
-
 namespace autofill {
 
-// This class displays the "Save credit card?" bubble that is shown when the
-// user submits a form with a credit card number that Autofill has not
-// previously saved.
+// This class serves as a base view to any of the bubble views that are part of
+// the flow for when the user submits a form with a credit card number that
+// Autofill has not previously saved. The base view establishes the button
+// handlers, the calculated size, the Super G logo, testing methods, the
+// SyncPromoDelegate and the window title (controller eventually handles the
+// title for each sub-class).
 class SaveCardBubbleViews : public SaveCardBubbleView,
-                            public LocationBarBubbleDelegateView,
-                            public views::StyledLabelListener,
-                            public views::TextfieldController {
+                            public LocationBarBubbleDelegateView {
  public:
   // Bubble will be anchored to |anchor_view|.
   SaveCardBubbleViews(views::View* anchor_view,
@@ -48,8 +42,6 @@
   bool Cancel() override;
   bool Close() override;
   int GetDialogButtons() const override;
-  base::string16 GetDialogButtonLabel(ui::DialogButton button) const override;
-  bool IsDialogButtonEnabled(ui::DialogButton button) const override;
 
   // views::View:
   gfx::Size CalculatePreferredSize() const override;
@@ -60,19 +52,48 @@
   base::string16 GetWindowTitle() const override;
   void WindowClosing() override;
 
-  // views::StyledLabelListener:
-  void StyledLabelLinkClicked(views::StyledLabel* label,
-                              const gfx::Range& range,
-                              int event_flags) override;
-
-  // views::TextfieldController:
-  void ContentsChanged(views::Textfield* sender,
-                       const base::string16& new_contents) override;
-
   // Returns the footnote view, so it can be searched for clickable views.
   // Exists for testing (specifically, browsertests).
   views::View* GetFootnoteViewForTesting();
 
+ protected:
+  // Delegate for the personalized sync promo view used when desktop identity
+  // consistency is enabled.
+  class SyncPromoDelegate : public BubbleSyncPromoDelegate {
+   public:
+    explicit SyncPromoDelegate(SaveCardBubbleController* controller);
+
+    // BubbleSyncPromoDelegate:
+    void OnEnableSync(const AccountInfo& account,
+                      bool is_default_promo_account) override;
+
+   private:
+    SaveCardBubbleController* controller_;
+
+    DISALLOW_COPY_AND_ASSIGN(SyncPromoDelegate);
+  };
+
+  // Create the dialog's content view containing everything except for the
+  // footnote.
+  virtual std::unique_ptr<views::View> CreateMainContentView();
+
+  // Set the footnote view so that its accessible for testing.
+  void SetFootnoteViewForTesting(views::View* footnote_view);
+
+  SaveCardBubbleController* controller() {
+    return controller_;
+  };  // Weak reference.
+
+  // Attributes IDs to the DialogClientView and its buttons.
+  void AssignIdsToDialogClientView();
+
+  // views::BubbleDialogDelegateView:
+  void Init() override;
+
+  std::unique_ptr<SyncPromoDelegate> sync_promo_delegate_;
+
+  ~SaveCardBubbleViews() override;
+
  private:
   FRIEND_TEST_ALL_PREFIXES(SaveCardBubbleViewsFullFormBrowserTest,
                            Upload_ClickingCloseClosesBubble);
@@ -80,33 +101,10 @@
       SaveCardBubbleViewsFullFormBrowserTest,
       Upload_DecliningUploadDoesNotLogUserAcceptedCardOriginUMA);
 
-  // The current step of the save card flow.  Accounts for:
-  //  1) Local save vs. Upload save
-  //  2) Upload save can have all information or be missing CVC
-  enum CurrentFlowStep {
-    UNKNOWN_STEP,
-    LOCAL_SAVE_ONLY_STEP,
-    UPLOAD_SAVE_ONLY_STEP,
-  };
-
-  ~SaveCardBubbleViews() override;
-
-  CurrentFlowStep GetCurrentFlowStep() const;
-  // Creates the dialog's content view containing everything except for the
-  // footnote.
-  std::unique_ptr<views::View> CreateMainContentView();
-
-  // Attributes IDs to the DialogClientView and its buttons.
-  void AssignIdsToDialogClientView();
-
-  // views::BubbleDialogDelegateView:
-  void Init() override;
+  views::View* footnote_view_ = nullptr;
 
   SaveCardBubbleController* controller_;  // Weak reference.
 
-  views::View* footnote_view_ = nullptr;
-  views::Textfield* cardholder_name_textfield_ = nullptr;
-
   DISALLOW_COPY_AND_ASSIGN(SaveCardBubbleViews);
 };
 
diff --git a/chrome/browser/ui/views/autofill/save_card_bubble_views_browsertest.cc b/chrome/browser/ui/views/autofill/save_card_bubble_views_browsertest.cc
index 773b20e..76ff5df 100644
--- a/chrome/browser/ui/views/autofill/save_card_bubble_views_browsertest.cc
+++ b/chrome/browser/ui/views/autofill/save_card_bubble_views_browsertest.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/ui/views/autofill/save_card_bubble_views.h"
 #include "chrome/browser/ui/views/autofill/save_card_bubble_views_browsertest_base.h"
 #include "components/autofill/core/browser/autofill_experiments.h"
+#include "components/autofill/core/common/autofill_features.h"
 #include "content/public/test/browser_test_utils.h"
 #include "net/url_request/test_url_fetcher_factory.h"
 #include "ui/views/bubble/bubble_frame_view.h"
@@ -72,6 +73,10 @@
 // successfully causes the bubble to go away.
 IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
                        Local_ClickingSaveClosesBubble) {
+  // Disable the sign-in promo.
+  scoped_feature_list_.InitAndDisableFeature(
+      features::kAutofillSaveCardSignInAfterLocalSave);
+
   // Set up the Payments RPC.
   SetUploadDetailsRpcPaymentsDeclines();
 
@@ -94,6 +99,71 @@
   histogram_tester.ExpectUniqueSample(
       "Autofill.SaveCreditCardPrompt.Local.FirstShow",
       AutofillMetrics::SAVE_CARD_PROMPT_END_ACCEPTED, 1);
+  // The sign-in promo should not be shown because the experiment is disabled.
+  // TODO(crbug/855186): Add test that checks that sign-in promo is not showing.
+}
+
+// Tests the sign in promo bubble. Ensures that clicking the [Save] button
+// on the local save bubble successfully causes the sign in promo to show.
+IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
+                       Local_ClickingSaveShowsSigninPromo) {
+  // Enable the sign-in promo.
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillSaveCardSignInAfterLocalSave);
+
+  // Set up the Payments RPC.
+  SetUploadDetailsRpcPaymentsDeclines();
+
+  // Submitting the form and having Payments decline offering to save should
+  // show the local save bubble.
+  // (Must wait for response from Payments before accessing the controller.)
+  ResetEventWaiterForSequence(
+      {DialogEvent::REQUESTED_UPLOAD_SAVE,
+       DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE,
+       DialogEvent::OFFERED_LOCAL_SAVE});
+  FillAndSubmitForm();
+  WaitForObservedEvent();
+  EXPECT_TRUE(
+      FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_LOCAL)->visible());
+
+  // Click [Save] should close the offer-to-save bubble
+  // and pop up the sign-in promo.
+  // TODO(crbug/855186): Add test that checks that sign-in promo shows after
+  // clicking save.
+}
+
+// Tests the sign in promo bubble. Ensures that the sign-in promo
+// is not shown when the user is signed-in and syncing, even if
+// the local save bubble is shown.
+IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
+                       Local_NoSigninPromoShowsWhenUserIsSyncing) {
+  // Enable the sign-in promo.
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillSaveCardSignInAfterLocalSave);
+
+  // Set up the Payments RPC.
+  SetUploadDetailsRpcPaymentsDeclines();
+
+  // Sign the user in.
+  SignInWithFullName("John Smith");
+
+  // Submitting the form and having Payments decline offering to save should
+  // show the local save bubble.
+  // (Must wait for response from Payments before accessing the controller.)
+  ResetEventWaiterForSequence(
+      {DialogEvent::REQUESTED_UPLOAD_SAVE,
+       DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE,
+       DialogEvent::OFFERED_LOCAL_SAVE});
+  FillAndSubmitForm();
+  WaitForObservedEvent();
+  EXPECT_TRUE(
+      FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_LOCAL)->visible());
+
+  // Click [Save] should close the offer-to-save bubble
+  // but no sign-in promo should show because user is signed in.
+  ClickOnDialogViewWithIdAndWait(DialogViewId::OK_BUTTON);
+  // TODO(crbug/855186): Add test that checks that sign-in promo
+  // is not showing.
 }
 
 // Tests the local save bubble. Ensures that the Harmony version of the bubble
diff --git a/chrome/browser/ui/views/autofill/save_card_bubble_views_browsertest_base.cc b/chrome/browser/ui/views/autofill/save_card_bubble_views_browsertest_base.cc
index f8d4e4c..bb3f4a8 100644
--- a/chrome/browser/ui/views/autofill/save_card_bubble_views_browsertest_base.cc
+++ b/chrome/browser/ui/views/autofill/save_card_bubble_views_browsertest_base.cc
@@ -372,6 +372,7 @@
 
   views::View* specified_view =
       save_card_bubble_views->GetViewByID(static_cast<int>(view_id));
+
   if (!specified_view) {
     // Many of the save card bubble's inner Views are not child views but rather
     // contained by its DialogClientView. If we didn't find what we were looking
@@ -389,6 +390,7 @@
       specified_view = footnote_view->GetViewByID(static_cast<int>(view_id));
     }
   }
+
   return specified_view;
 }
 
diff --git a/chrome/browser/ui/views/autofill/save_card_manage_cards_bubble_views.cc b/chrome/browser/ui/views/autofill/save_card_manage_cards_bubble_views.cc
new file mode 100644
index 0000000..5ea0038
--- /dev/null
+++ b/chrome/browser/ui/views/autofill/save_card_manage_cards_bubble_views.cc
@@ -0,0 +1,102 @@
+// 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 "chrome/browser/ui/views/autofill/save_card_manage_cards_bubble_views.h"
+
+#include "chrome/browser/signin/account_consistency_mode_manager.h"
+#include "chrome/browser/ui/browser_dialogs.h"
+#include "chrome/browser/ui/views/autofill/dialog_view_ids.h"
+#include "chrome/browser/ui/views/harmony/chrome_typography.h"
+#include "chrome/browser/ui/views/sync/bubble_sync_promo_view.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/views/controls/button/md_text_button.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+
+#if BUILDFLAG(ENABLE_DICE_SUPPORT)
+#include "chrome/browser/ui/views/sync/dice_bubble_sync_promo_view.h"
+#endif
+
+namespace autofill {
+
+SaveCardManageCardsBubbleViews::SaveCardManageCardsBubbleViews(
+    views::View* anchor_view,
+    const gfx::Point& anchor_point,
+    content::WebContents* web_contents,
+    SaveCardBubbleController* controller)
+    : SaveCardBubbleViews(anchor_view, anchor_point, web_contents, controller) {
+}
+
+views::View* SaveCardManageCardsBubbleViews::CreateFootnoteView() {
+  if (!controller()->ShouldShowSignInPromo())
+    return nullptr;
+
+  views::View* footnote_view_ = nullptr;
+
+  Profile* profile = controller()->GetProfile();
+  sync_promo_delegate_ =
+      std::make_unique<SaveCardManageCardsBubbleViews::SyncPromoDelegate>(
+          controller());
+  if (AccountConsistencyModeManager::IsDiceEnabledForProfile(profile)) {
+#if BUILDFLAG(ENABLE_DICE_SUPPORT)
+    footnote_view_ = new DiceBubbleSyncPromoView(
+        profile, sync_promo_delegate_.get(),
+        signin_metrics::AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE,
+        IDS_AUTOFILL_SIGNIN_PROMO_MESSAGE, IDS_AUTOFILL_SYNC_PROMO_MESSAGE,
+        /*prominent=*/false, ChromeTextStyle::STYLE_SECONDARY);
+#else
+    NOTREACHED();
+#endif
+  } else {
+    bool is_signin_promo = controller()->GetAccountInfo().IsEmpty();
+    footnote_view_ = new BubbleSyncPromoView(
+        sync_promo_delegate_.get(),
+        signin_metrics::AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE,
+        is_signin_promo ? IDS_AUTOFILL_SIGNIN_PROMO_LINK_DICE_DISABLED
+                        : IDS_AUTOFILL_SYNC_PROMO_LINK_DICE_DISABLED,
+        is_signin_promo ? IDS_AUTOFILL_SIGNIN_PROMO_MESSAGE_DICE_DISABLED
+                        : IDS_AUTOFILL_SYNC_PROMO_MESSAGE_DICE_DISABLED);
+  }
+
+  SetFootnoteViewForTesting(footnote_view_);
+  return footnote_view_;
+}
+
+views::View* SaveCardManageCardsBubbleViews::CreateExtraView() {
+  views::View* manage_cards_button =
+      views::MdTextButton::CreateSecondaryUiButton(
+          this, l10n_util::GetStringUTF16(IDS_AUTOFILL_MANAGE_CARDS));
+  manage_cards_button->set_id(DialogViewId::MANAGE_CARDS_BUTTON);
+  return manage_cards_button;
+}
+
+int SaveCardManageCardsBubbleViews::GetDialogButtons() const {
+  return ui::DIALOG_BUTTON_OK;
+}
+
+base::string16 SaveCardManageCardsBubbleViews::GetDialogButtonLabel(
+    ui::DialogButton button) const {
+  return l10n_util::GetStringUTF16(IDS_AUTOFILL_DONE);
+}
+
+SaveCardManageCardsBubbleViews::~SaveCardManageCardsBubbleViews() {}
+
+std::unique_ptr<views::View>
+SaveCardManageCardsBubbleViews::CreateMainContentView() {
+  std::unique_ptr<views::View> view =
+      SaveCardBubbleViews::CreateMainContentView();
+  view->set_id(DialogViewId::MANAGE_CARDS_VIEW);
+  return view;
+}
+
+void SaveCardManageCardsBubbleViews::ButtonPressed(views::Button* sender,
+                                                   const ui::Event& event) {
+  if (sender->GetViewByID(DialogViewId::MANAGE_CARDS_BUTTON)) {
+    controller()->OnManageCardsClicked();
+    CloseBubble();
+  }
+}
+
+}  // namespace autofill
diff --git a/chrome/browser/ui/views/autofill/save_card_manage_cards_bubble_views.h b/chrome/browser/ui/views/autofill/save_card_manage_cards_bubble_views.h
new file mode 100644
index 0000000..9a6b08c
--- /dev/null
+++ b/chrome/browser/ui/views/autofill/save_card_manage_cards_bubble_views.h
@@ -0,0 +1,49 @@
+// 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 CHROME_BROWSER_UI_VIEWS_AUTOFILL_SAVE_CARD_MANAGE_CARDS_BUBBLE_VIEWS_H_
+#define CHROME_BROWSER_UI_VIEWS_AUTOFILL_SAVE_CARD_MANAGE_CARDS_BUBBLE_VIEWS_H_
+
+#include "chrome/browser/ui/views/autofill/save_card_bubble_views.h"
+#include "ui/views/controls/button/button.h"
+
+namespace autofill {
+
+// This class displays the Manage cards bubble that is shown after the user
+// submits a form with a credit card number that Autofill has not
+// previously saved and chooses to save it. This bubble is accessible by
+// clicking on the omnibox credit card icon. It contains a description of the
+// credit card that was just saved, a [Manage cards] button that links to the
+// Autofill settings page, and a [Done] button that closes the bubble.
+class SaveCardManageCardsBubbleViews : public SaveCardBubbleViews,
+                                       public views::ButtonListener {
+ public:
+  // Bubble will be anchored to |anchor_view|.
+  SaveCardManageCardsBubbleViews(views::View* anchor_view,
+                                 const gfx::Point& anchor_point,
+                                 content::WebContents* web_contents,
+                                 SaveCardBubbleController* controller);
+
+  // views::WidgetDelegate:
+  views::View* CreateFootnoteView() override;
+  views::View* CreateExtraView() override;
+  int GetDialogButtons() const override;
+  base::string16 GetDialogButtonLabel(ui::DialogButton button) const override;
+
+ private:
+  std::unique_ptr<views::View> CreateMainContentView() override;
+
+  // views::ButtonListener:
+  // The button listener method for the extra view that contains
+  // the Manage cards button.
+  void ButtonPressed(views::Button* sender, const ui::Event& event) override;
+
+  ~SaveCardManageCardsBubbleViews() override;
+
+  DISALLOW_COPY_AND_ASSIGN(SaveCardManageCardsBubbleViews);
+};
+
+}  // namespace autofill
+
+#endif  // CHROME_BROWSER_UI_VIEWS_AUTOFILL_SAVE_CARD_MANAGE_CARDS_BUBBLE_VIEWS_H_
diff --git a/chrome/browser/ui/views/autofill/save_card_offer_bubble_views.cc b/chrome/browser/ui/views/autofill/save_card_offer_bubble_views.cc
new file mode 100644
index 0000000..52f803e
--- /dev/null
+++ b/chrome/browser/ui/views/autofill/save_card_offer_bubble_views.cc
@@ -0,0 +1,237 @@
+// 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 "chrome/browser/ui/views/autofill/save_card_offer_bubble_views.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "chrome/app/vector_icons/vector_icons.h"
+#include "chrome/browser/ui/browser_dialogs.h"
+#include "chrome/browser/ui/views/autofill/dialog_view_ids.h"
+#include "chrome/browser/ui/views/harmony/chrome_layout_provider.h"
+#include "chrome/browser/ui/views/harmony/chrome_typography.h"
+#include "components/autofill/core/browser/autofill_experiments.h"
+#include "components/autofill/core/browser/autofill_metrics.h"
+#include "components/autofill/core/browser/credit_card.h"
+#include "components/autofill/core/browser/legal_message_line.h"
+#include "components/autofill/core/browser/ui/save_card_bubble_controller.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/material_design/material_design_controller.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/color_palette.h"
+#include "ui/gfx/geometry/insets.h"
+#include "ui/gfx/image/image_skia_operations.h"
+#include "ui/gfx/paint_vector_icon.h"
+#include "ui/views/border.h"
+#include "ui/views/bubble/bubble_frame_view.h"
+#include "ui/views/bubble/tooltip_icon.h"
+#include "ui/views/controls/button/blue_button.h"
+#include "ui/views/controls/button/label_button.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/controls/separator.h"
+#include "ui/views/controls/styled_label.h"
+#include "ui/views/controls/textfield/textfield.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/style/typography.h"
+#include "ui/views/window/dialog_client_view.h"
+
+namespace autofill {
+
+namespace {
+
+const int kTooltipIconSize = 12;
+
+std::unique_ptr<views::StyledLabel> CreateLegalMessageLineLabel(
+    const LegalMessageLine& line,
+    views::StyledLabelListener* listener) {
+  std::unique_ptr<views::StyledLabel> label(
+      new views::StyledLabel(line.text(), listener));
+  label->SetTextContext(CONTEXT_BODY_TEXT_LARGE);
+  label->SetDefaultTextStyle(ChromeTextStyle::STYLE_SECONDARY);
+  for (const LegalMessageLine::Link& link : line.links()) {
+    label->AddStyleRange(link.range,
+                         views::StyledLabel::RangeStyleInfo::CreateForLink());
+  }
+  return label;
+}
+
+}  // namespace
+
+SaveCardOfferBubbleViews::SaveCardOfferBubbleViews(
+    views::View* anchor_view,
+    const gfx::Point& anchor_point,
+    content::WebContents* web_contents,
+    SaveCardBubbleController* controller)
+    : SaveCardBubbleViews(anchor_view, anchor_point, web_contents, controller) {
+}
+
+views::View* SaveCardOfferBubbleViews::CreateFootnoteView() {
+  if (controller()->GetLegalMessageLines().empty())
+    return nullptr;
+
+  // Use BoxLayout to provide insets around the label.
+  views::View* footnote_view_ = new View();
+  footnote_view_->SetLayoutManager(
+      std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical));
+  footnote_view_->set_id(DialogViewId::FOOTNOTE_VIEW);
+
+  // Add a StyledLabel for each line of the legal message.
+  for (const LegalMessageLine& line : controller()->GetLegalMessageLines()) {
+    footnote_view_->AddChildView(
+        CreateLegalMessageLineLabel(line, this).release());
+  }
+
+  SetFootnoteViewForTesting(footnote_view_);
+  return footnote_view_;
+}
+
+bool SaveCardOfferBubbleViews::Accept() {
+  if (controller())
+    controller()->OnSaveButton(cardholder_name_textfield_
+                                   ? cardholder_name_textfield_->text()
+                                   : base::string16());
+  return true;
+}
+
+base::string16 SaveCardOfferBubbleViews::GetDialogButtonLabel(
+    ui::DialogButton button) const {
+  return l10n_util::GetStringUTF16(button == ui::DIALOG_BUTTON_OK
+                                       ? IDS_AUTOFILL_SAVE_CARD_PROMPT_ACCEPT
+                                       : IDS_NO_THANKS);
+}
+
+bool SaveCardOfferBubbleViews::IsDialogButtonEnabled(
+    ui::DialogButton button) const {
+  if (button == ui::DIALOG_BUTTON_CANCEL)
+    return true;
+
+  DCHECK_EQ(ui::DIALOG_BUTTON_OK, button);
+  if (cardholder_name_textfield_) {
+    // If requesting the user confirm the name, it cannot be blank.
+    base::string16 trimmed_text;
+    base::TrimWhitespace(cardholder_name_textfield_->text(), base::TRIM_ALL,
+                         &trimmed_text);
+    return !trimmed_text.empty();
+  }
+  return true;
+}
+
+void SaveCardOfferBubbleViews::StyledLabelLinkClicked(views::StyledLabel* label,
+                                                      const gfx::Range& range,
+                                                      int event_flags) {
+  if (!controller())
+    return;
+
+  // Index of |label| within its parent's view hierarchy is the same as the
+  // legal message line index. DCHECK this assumption to guard against future
+  // layout changes.
+  DCHECK_EQ(static_cast<size_t>(label->parent()->child_count()),
+            controller()->GetLegalMessageLines().size());
+
+  const auto& links =
+      controller()
+          ->GetLegalMessageLines()[label->parent()->GetIndexOf(label)]
+          .links();
+  for (const LegalMessageLine::Link& link : links) {
+    if (link.range == range) {
+      controller()->OnLegalMessageLinkClicked(link.url);
+      return;
+    }
+  }
+
+  // |range| was not found.
+  NOTREACHED();
+}
+
+void SaveCardOfferBubbleViews::ContentsChanged(
+    views::Textfield* sender,
+    const base::string16& new_contents) {
+  DCHECK_EQ(cardholder_name_textfield_, sender);
+  DialogModelChanged();
+}
+
+SaveCardOfferBubbleViews::~SaveCardOfferBubbleViews() {}
+
+std::unique_ptr<views::View> SaveCardOfferBubbleViews::CreateMainContentView() {
+  std::unique_ptr<views::View> view =
+      SaveCardBubbleViews::CreateMainContentView();
+  ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
+  view->set_id(controller()->IsUploadSave()
+                   ? DialogViewId::MAIN_CONTENT_VIEW_UPLOAD
+                   : DialogViewId::MAIN_CONTENT_VIEW_LOCAL);
+
+  // If necessary, add the cardholder name label and textfield to the upload
+  // save dialog.
+  if (controller()->ShouldRequestNameFromUser()) {
+    std::unique_ptr<views::View> cardholder_name_label_row =
+        std::make_unique<views::View>();
+
+    // Set up cardholder name label.
+    // TODO(jsaul): DISTANCE_RELATED_BUTTON_HORIZONTAL isn't the right choice
+    //              here, but DISTANCE_RELATED_CONTROL_HORIZONTAL gives too much
+    //              padding. Make a new Harmony DistanceMetric?
+    cardholder_name_label_row->SetLayoutManager(
+        std::make_unique<views::BoxLayout>(
+            views::BoxLayout::kHorizontal, gfx::Insets(),
+            provider->GetDistanceMetric(
+                views::DISTANCE_RELATED_BUTTON_HORIZONTAL)));
+    std::unique_ptr<views::Label> cardholder_name_label =
+        std::make_unique<views::Label>(
+            l10n_util::GetStringUTF16(
+                IDS_AUTOFILL_SAVE_CARD_PROMPT_CARDHOLDER_NAME),
+            CONTEXT_BODY_TEXT_LARGE, ChromeTextStyle::STYLE_SECONDARY);
+    cardholder_name_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+    cardholder_name_label_row->AddChildView(cardholder_name_label.release());
+
+    // Prepare the prefilled cardholder name.
+    base::string16 prefilled_name;
+    if (!IsAutofillUpstreamBlankCardholderNameFieldExperimentEnabled()) {
+      prefilled_name =
+          base::UTF8ToUTF16(controller()->GetAccountInfo().full_name);
+    }
+
+    // Set up cardholder name label tooltip ONLY if the cardholder name
+    // textfield will be prefilled.
+    if (!prefilled_name.empty()) {
+      std::unique_ptr<views::TooltipIcon> cardholder_name_tooltip =
+          std::make_unique<views::TooltipIcon>(
+              l10n_util::GetStringUTF16(
+                  IDS_AUTOFILL_SAVE_CARD_PROMPT_CARDHOLDER_NAME_TOOLTIP),
+              kTooltipIconSize);
+      cardholder_name_tooltip->set_anchor_point_arrow(
+          views::BubbleBorder::Arrow::TOP_LEFT);
+      cardholder_name_tooltip->set_id(DialogViewId::CARDHOLDER_NAME_TOOLTIP);
+      cardholder_name_label_row->AddChildView(
+          cardholder_name_tooltip.release());
+    }
+
+    // Set up cardholder name textfield.
+    DCHECK(!cardholder_name_textfield_);
+    cardholder_name_textfield_ = new views::Textfield();
+    cardholder_name_textfield_->set_controller(this);
+    cardholder_name_textfield_->set_id(DialogViewId::CARDHOLDER_NAME_TEXTFIELD);
+    cardholder_name_textfield_->SetAccessibleName(l10n_util::GetStringUTF16(
+        IDS_AUTOFILL_SAVE_CARD_PROMPT_CARDHOLDER_NAME));
+    cardholder_name_textfield_->SetTextInputType(
+        ui::TextInputType::TEXT_INPUT_TYPE_TEXT);
+    cardholder_name_textfield_->SetText(prefilled_name);
+    AutofillMetrics::LogSaveCardCardholderNamePrefilled(
+        !prefilled_name.empty());
+
+    // Add cardholder name elements to a single view, then to the final dialog.
+    std::unique_ptr<views::View> cardholder_name_view =
+        std::make_unique<views::View>();
+    cardholder_name_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
+        views::BoxLayout::kVertical, gfx::Insets(),
+        provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL)));
+    cardholder_name_view->AddChildView(cardholder_name_label_row.release());
+    cardholder_name_view->AddChildView(cardholder_name_textfield_);
+    view->AddChildView(cardholder_name_view.release());
+  }
+
+  return view;
+}
+
+}  // namespace autofill
diff --git a/chrome/browser/ui/views/autofill/save_card_offer_bubble_views.h b/chrome/browser/ui/views/autofill/save_card_offer_bubble_views.h
new file mode 100644
index 0000000..d6f37bdf
--- /dev/null
+++ b/chrome/browser/ui/views/autofill/save_card_offer_bubble_views.h
@@ -0,0 +1,60 @@
+// 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 CHROME_BROWSER_UI_VIEWS_AUTOFILL_SAVE_CARD_OFFER_BUBBLE_VIEWS_H_
+#define CHROME_BROWSER_UI_VIEWS_AUTOFILL_SAVE_CARD_OFFER_BUBBLE_VIEWS_H_
+
+#include "chrome/browser/ui/views/autofill/save_card_bubble_views.h"
+
+#include "ui/views/controls/styled_label_listener.h"
+#include "ui/views/controls/textfield/textfield_controller.h"
+
+namespace content {
+class WebContents;
+}
+
+namespace autofill {
+
+// This class displays the "Save credit card?" bubble that is shown when the
+// user submits a form with a credit card number that Autofill has not
+// previously saved. It includes a description of the card that is being saved
+// and an [Save] button. (Non-material UI's include a [No Thanks] button).
+class SaveCardOfferBubbleViews : public SaveCardBubbleViews,
+                                 public views::StyledLabelListener,
+                                 public views::TextfieldController {
+ public:
+  // Bubble will be anchored to |anchor_view|.
+  SaveCardOfferBubbleViews(views::View* anchor_view,
+                           const gfx::Point& anchor_point,
+                           content::WebContents* web_contents,
+                           SaveCardBubbleController* controller);
+
+  // BubbleDialogDelegateView
+  views::View* CreateFootnoteView() override;
+  bool Accept() override;
+  base::string16 GetDialogButtonLabel(ui::DialogButton button) const override;
+  bool IsDialogButtonEnabled(ui::DialogButton button) const override;
+
+  // views::StyledLabelListener:
+  void StyledLabelLinkClicked(views::StyledLabel* label,
+                              const gfx::Range& range,
+                              int event_flags) override;
+
+  // views::TextfieldController:
+  void ContentsChanged(views::Textfield* sender,
+                       const base::string16& new_contents) override;
+
+ private:
+  std::unique_ptr<views::View> CreateMainContentView() override;
+
+  ~SaveCardOfferBubbleViews() override;
+
+  views::Textfield* cardholder_name_textfield_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(SaveCardOfferBubbleViews);
+};
+
+}  // namespace autofill
+
+#endif  // CHROME_BROWSER_UI_VIEWS_AUTOFILL_SAVE_CARD_OFFER_BUBBLE_VIEWS_H_
diff --git a/chrome/browser/ui/views/autofill/save_card_sign_in_promo_bubble_views.cc b/chrome/browser/ui/views/autofill/save_card_sign_in_promo_bubble_views.cc
new file mode 100644
index 0000000..f0d13f6
--- /dev/null
+++ b/chrome/browser/ui/views/autofill/save_card_sign_in_promo_bubble_views.cc
@@ -0,0 +1,74 @@
+// 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 "chrome/browser/ui/views/autofill/save_card_sign_in_promo_bubble_views.h"
+
+#include "chrome/browser/signin/account_consistency_mode_manager.h"
+#include "chrome/browser/ui/browser_dialogs.h"
+#include "chrome/browser/ui/views/autofill/dialog_view_ids.h"
+#include "chrome/browser/ui/views/harmony/chrome_layout_provider.h"
+#include "chrome/browser/ui/views/sync/bubble_sync_promo_view.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/material_design/material_design_controller.h"
+#include "ui/views/layout/box_layout.h"
+
+#if BUILDFLAG(ENABLE_DICE_SUPPORT)
+#include "chrome/browser/ui/views/sync/dice_bubble_sync_promo_view.h"
+#endif
+
+namespace autofill {
+
+SaveCardSignInPromoBubbleViews::SaveCardSignInPromoBubbleViews(
+    views::View* anchor_view,
+    const gfx::Point& anchor_point,
+    content::WebContents* web_contents,
+    SaveCardBubbleController* controller)
+    : SaveCardBubbleViews(anchor_view, anchor_point, web_contents, controller) {
+}
+
+int SaveCardSignInPromoBubbleViews::GetDialogButtons() const {
+  // The BubbleSyncPromoView takes care of buttons.
+  return ui::DIALOG_BUTTON_NONE;
+}
+
+SaveCardSignInPromoBubbleViews::~SaveCardSignInPromoBubbleViews() = default;
+
+std::unique_ptr<views::View>
+SaveCardSignInPromoBubbleViews::CreateMainContentView() {
+  auto view = std::make_unique<views::View>();
+  ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
+
+  view->SetLayoutManager(std::make_unique<views::BoxLayout>(
+      views::BoxLayout::kVertical, gfx::Insets(),
+      provider->GetDistanceMetric(views::DISTANCE_UNRELATED_CONTROL_VERTICAL)));
+  view->set_id(DialogViewId::SIGN_IN_PROMO_VIEW);
+
+  Profile* profile = controller()->GetProfile();
+  sync_promo_delegate_ =
+      std::make_unique<SaveCardSignInPromoBubbleViews::SyncPromoDelegate>(
+          controller());
+  if (AccountConsistencyModeManager::IsDiceEnabledForProfile(profile)) {
+#if BUILDFLAG(ENABLE_DICE_SUPPORT)
+    view->AddChildView(new DiceBubbleSyncPromoView(
+        profile, sync_promo_delegate_.get(),
+        signin_metrics::AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE));
+#else
+    NOTREACHED();
+#endif
+  } else {
+    bool is_signin_promo = controller()->GetAccountInfo().IsEmpty();
+    view->AddChildView(new BubbleSyncPromoView(
+        sync_promo_delegate_.get(),
+        signin_metrics::AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE,
+        is_signin_promo ? IDS_AUTOFILL_SIGNIN_PROMO_LINK_DICE_DISABLED
+                        : IDS_AUTOFILL_SYNC_PROMO_LINK_DICE_DISABLED,
+        is_signin_promo ? IDS_AUTOFILL_SIGNIN_PROMO_MESSAGE_DICE_DISABLED
+                        : IDS_AUTOFILL_SYNC_PROMO_MESSAGE_DICE_DISABLED));
+  }
+  return view;
+}
+
+}  // namespace autofill
diff --git a/chrome/browser/ui/views/autofill/save_card_sign_in_promo_bubble_views.h b/chrome/browser/ui/views/autofill/save_card_sign_in_promo_bubble_views.h
new file mode 100644
index 0000000..986f051
--- /dev/null
+++ b/chrome/browser/ui/views/autofill/save_card_sign_in_promo_bubble_views.h
@@ -0,0 +1,42 @@
+// 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 CHROME_BROWSER_UI_VIEWS_AUTOFILL_SAVE_CARD_SIGN_IN_PROMO_BUBBLE_VIEWS_H_
+#define CHROME_BROWSER_UI_VIEWS_AUTOFILL_SAVE_CARD_SIGN_IN_PROMO_BUBBLE_VIEWS_H_
+
+#include "base/macros.h"
+#include "chrome/browser/ui/views/autofill/save_card_bubble_views.h"
+
+namespace content {
+class WebContents;
+}
+
+namespace autofill {
+
+// This class displays the Sign-in/Sync promo bubble which is shown after the
+// user saves a credit card locally to Autofill. It contains a title with a
+// promo message to the user (this title is handled by the controller) and
+// either a large sign-in button or a sync-promo button.
+class SaveCardSignInPromoBubbleViews : public SaveCardBubbleViews {
+ public:
+  // Bubble will be anchored to |anchor_view|.
+  SaveCardSignInPromoBubbleViews(views::View* anchor_view,
+                                 const gfx::Point& anchor_point,
+                                 content::WebContents* web_contents,
+                                 SaveCardBubbleController* controller);
+
+  // views::BubbleDialogDelegateView:
+  int GetDialogButtons() const override;
+
+ private:
+  std::unique_ptr<views::View> CreateMainContentView() override;
+
+  ~SaveCardSignInPromoBubbleViews() override;
+
+  DISALLOW_COPY_AND_ASSIGN(SaveCardSignInPromoBubbleViews);
+};
+
+}  // namespace autofill
+
+#endif  // CHROME_BROWSER_UI_VIEWS_AUTOFILL_SAVE_CARD_SIGN_IN_PROMO_BUBBLE_VIEWS_H_
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index aa8439a9..0d4523d 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -44,6 +44,7 @@
 #include "chrome/browser/translate/chrome_translate_client.h"
 #include "chrome/browser/ui/autofill/local_card_migration_bubble.h"
 #include "chrome/browser/ui/autofill/save_card_bubble_view.h"
+#include "chrome/browser/ui/autofill/save_card_ui.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_command_controller.h"
 #include "chrome/browser/ui/browser_commands.h"
@@ -65,6 +66,9 @@
 #include "chrome/browser/ui/views/autofill/local_card_migration_icon_view.h"
 #include "chrome/browser/ui/views/autofill/save_card_bubble_views.h"
 #include "chrome/browser/ui/views/autofill/save_card_icon_view.h"
+#include "chrome/browser/ui/views/autofill/save_card_manage_cards_bubble_views.h"
+#include "chrome/browser/ui/views/autofill/save_card_offer_bubble_views.h"
+#include "chrome/browser/ui/views/autofill/save_card_sign_in_promo_bubble_views.h"
 #include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h"
 #include "chrome/browser/ui/views/bookmarks/bookmark_bubble_view.h"
 #include "chrome/browser/ui/views/confirm_quit_bubble_controller.h"
@@ -1229,8 +1233,29 @@
       anchor_view = toolbar_button_provider()->GetAppMenuButton();
   }
 
-  autofill::SaveCardBubbleViews* bubble = new autofill::SaveCardBubbleViews(
-      anchor_view, gfx::Point(), web_contents, controller);
+  autofill::BubbleType bubble_type = controller->GetBubbleType();
+  autofill::SaveCardBubbleViews* bubble = nullptr;
+
+  switch (bubble_type) {
+    case autofill::BubbleType::LOCAL_SAVE:
+    case autofill::BubbleType::UPLOAD_SAVE:
+      bubble = new autofill::SaveCardOfferBubbleViews(anchor_view, gfx::Point(),
+                                                      web_contents, controller);
+      break;
+    case autofill::BubbleType::SIGN_IN_PROMO:
+      bubble = new autofill::SaveCardSignInPromoBubbleViews(
+          anchor_view, gfx::Point(), web_contents, controller);
+      break;
+    case autofill::BubbleType::MANAGE_CARDS:
+      bubble = new autofill::SaveCardManageCardsBubbleViews(
+          anchor_view, gfx::Point(), web_contents, controller);
+      break;
+    case autofill::BubbleType::INACTIVE:
+      break;
+  }
+
+  DCHECK(bubble);
+
   views::Widget* bubble_widget =
       views::BubbleDialogDelegateView::CreateBubble(bubble);
 
diff --git a/chrome/browser/ui/views/sync/dice_bubble_sync_promo_view.cc b/chrome/browser/ui/views/sync/dice_bubble_sync_promo_view.cc
index 640f999..a9a877b5 100644
--- a/chrome/browser/ui/views/sync/dice_bubble_sync_promo_view.cc
+++ b/chrome/browser/ui/views/sync/dice_bubble_sync_promo_view.cc
@@ -27,7 +27,8 @@
     signin_metrics::AccessPoint access_point,
     int no_accounts_promo_message_resource_id,
     int accounts_promo_message_resource_id,
-    bool signin_button_prominent)
+    bool signin_button_prominent,
+    int text_style)
     : views::View(), delegate_(delegate) {
   DCHECK(AccountConsistencyModeManager::IsDiceEnabledForProfile(profile));
 
@@ -44,11 +45,14 @@
           .bottom());
   SetLayoutManager(std::move(layout));
 
-  base::string16 title_text = l10n_util::GetStringUTF16(title_resource_id);
-  views::Label* title = new views::Label(title_text, CONTEXT_BODY_TEXT_LARGE);
-  title->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
-  title->SetMultiLine(true);
-  AddChildView(title);
+  if (title_resource_id) {
+    base::string16 title_text = l10n_util::GetStringUTF16(title_resource_id);
+    views::Label* title =
+        new views::Label(title_text, CONTEXT_BODY_TEXT_LARGE, text_style);
+    title->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
+    title->SetMultiLine(true);
+    AddChildView(title);
+  }
 
   if (accounts.empty()) {
     signin_button_view_ =
diff --git a/chrome/browser/ui/views/sync/dice_bubble_sync_promo_view.h b/chrome/browser/ui/views/sync/dice_bubble_sync_promo_view.h
index d1ab9488..187f519 100644
--- a/chrome/browser/ui/views/sync/dice_bubble_sync_promo_view.h
+++ b/chrome/browser/ui/views/sync/dice_bubble_sync_promo_view.h
@@ -32,18 +32,21 @@
   // Creates a personalized sync promo view.
   // |delegate| is not owned by DiceBubbleSyncPromoView.
   // The promo message is set to |no_accounts_promo_message_resource_id| when
-  // Chrome has no accounts.
+  // Chrome has no accounts. If no value is given, then no message is shown.
   // The promo message is set to |accounts_promo_message_resource_id| when
   // Chrome has at least one account.
   // If |signin_button_prominent| is false and a non-personalized signin button
   // is shown, the button is set to non-prominent. Otherwise the button remains
   // prominent.
+  // The promo message is set in a font given by |text_style|. It is defaulted
+  // to a primary style font.
   DiceBubbleSyncPromoView(Profile* profile,
                           BubbleSyncPromoDelegate* delegate,
                           signin_metrics::AccessPoint access_point,
-                          int no_accounts_promo_message_resource_id,
-                          int accounts_promo_message_resource_id,
-                          bool signin_button_prominent = true);
+                          int no_accounts_promo_message_resource_id = 0,
+                          int accounts_promo_message_resource_id = 0,
+                          bool signin_button_prominent = true,
+                          int text_style = views::style::STYLE_PRIMARY);
   ~DiceBubbleSyncPromoView() override;
 
   // views::ButtonListener:
diff --git a/chrome/browser/ui/views/tabs/tab.cc b/chrome/browser/ui/views/tabs/tab.cc
index f5d4a5c..363b545 100644
--- a/chrome/browser/ui/views/tabs/tab.cc
+++ b/chrome/browser/ui/views/tabs/tab.cc
@@ -1458,14 +1458,16 @@
   // TODO(pkasting): This whole function should go away, and we should simply
   // compute child visibility state in Layout().
 
-  // Don't adjust whether we're centering the favicon during tab closure; let it
-  // stay however it was prior to closing the tab.  This prevents the icon from
-  // sliding left at the end of closing a non-narrow tab.
-  if (!closing_)
+  // Don't adjust whether we're centering the favicon or adding extra padding
+  // during tab closure; let it stay however it was prior to closing the tab.
+  // This prevents the icon and text from sliding left at the end of closing
+  // a non-narrow tab.
+  if (!closing_) {
     center_favicon_ = false;
+    extra_padding_before_content_ = false;
+  }
 
   showing_icon_ = showing_alert_indicator_ = false;
-  extra_padding_before_content_ = false;
   extra_alert_indicator_padding_ = false;
 
   if (height() < GetLayoutConstant(TAB_HEIGHT))
@@ -1546,20 +1548,25 @@
     }
   }
 
-  // The extra padding is intended to visually balance the close button, so only
-  // include it when the close button is shown or will be shown on hover. We
-  // also check this for active tabs so that the extra padding doesn't pop in
-  // and out as you switch tabs.
-  extra_padding_before_content_ = large_enough_for_close_button;
+  // Don't update padding while the tab is closing, to avoid glitchy-looking
+  // behaviour when the close animation causes the tab to get very small
+  if (!closing_) {
+    // The extra padding is intended to visually balance the close button, so
+    // only include it when the close button is shown or will be shown on hover.
+    // We also check this for active tabs so that the extra padding doesn't pop
+    // in and out as you switch tabs.
+    extra_padding_before_content_ = large_enough_for_close_button;
 
-  if (DCHECK_IS_ON()) {
-    const int extra_left_padding =
-        MD::IsRefreshUi() ? kRefreshExtraLeftPaddingToBalanceCloseButtonPadding
-                          : kExtraLeftPaddingToBalanceCloseButtonPadding;
-    DCHECK(!extra_padding_before_content_ ||
-           extra_left_padding <= available_width);
-    if (extra_padding_before_content_)
-      available_width -= extra_left_padding;
+    if (DCHECK_IS_ON()) {
+      const int extra_left_padding =
+          MD::IsRefreshUi()
+              ? kRefreshExtraLeftPaddingToBalanceCloseButtonPadding
+              : kExtraLeftPaddingToBalanceCloseButtonPadding;
+      DCHECK(!extra_padding_before_content_ ||
+             extra_left_padding <= available_width);
+      if (extra_padding_before_content_)
+        available_width -= extra_left_padding;
+    }
   }
 
   if (MD::IsRefreshUi()) {
diff --git a/chrome/browser/ui/webui/interstitials/interstitial_ui.cc b/chrome/browser/ui/webui/interstitials/interstitial_ui.cc
index cb82114..815cc63 100644
--- a/chrome/browser/ui/webui/interstitials/interstitial_ui.cc
+++ b/chrome/browser/ui/webui/interstitials/interstitial_ui.cc
@@ -280,8 +280,8 @@
       threat_type = safe_browsing::SB_THREAT_TYPE_URL_CLIENT_SIDE_MALWARE;
     } else if (type_param == "clientside_phishing") {
       threat_type = safe_browsing::SB_THREAT_TYPE_URL_CLIENT_SIDE_PHISHING;
-    } else if (type_param == "trick_to_bill") {
-      threat_type = safe_browsing::SB_THREAT_TYPE_TRICK_TO_BILL;
+    } else if (type_param == "billing") {
+      threat_type = safe_browsing::SB_THREAT_TYPE_BILLING;
     }
   }
   safe_browsing::SafeBrowsingBlockingPage::UnsafeResource resource;
diff --git a/chrome/browser/ui/webui/interstitials/interstitial_ui_browsertest.cc b/chrome/browser/ui/webui/interstitials/interstitial_ui_browsertest.cc
index f4be76c6..6edcf4b8 100644
--- a/chrome/browser/ui/webui/interstitials/interstitial_ui_browsertest.cc
+++ b/chrome/browser/ui/webui/interstitials/interstitial_ui_browsertest.cc
@@ -125,10 +125,9 @@
       "Security error");
 }
 
-IN_PROC_BROWSER_TEST_F(InterstitialUITest, TrickToBillInterstitial) {
-  TestInterstitial(
-      GURL("chrome://interstitials/safebrowsing?type=trick_to_bill"),
-      "Security error");
+IN_PROC_BROWSER_TEST_F(InterstitialUITest, BillingInterstitial) {
+  TestInterstitial(GURL("chrome://interstitials/safebrowsing?type=billing"),
+                   "Security error");
 }
 
 IN_PROC_BROWSER_TEST_F(InterstitialUITest, CaptivePortalInterstitial) {
diff --git a/chrome/browser/vr/BUILD.gn b/chrome/browser/vr/BUILD.gn
index 7f6d869e..f08e8dd 100644
--- a/chrome/browser/vr/BUILD.gn
+++ b/chrome/browser/vr/BUILD.gn
@@ -244,10 +244,10 @@
     "sample_queue.h",
     "service/browser_xr_runtime.cc",
     "service/browser_xr_runtime.h",
-    "service/vr_display_host.cc",
-    "service/vr_display_host.h",
     "service/vr_service_impl.cc",
     "service/vr_service_impl.h",
+    "service/xr_device_impl.cc",
+    "service/xr_device_impl.h",
     "service/xr_runtime_manager.cc",
     "service/xr_runtime_manager.h",
     "sliding_average.cc",
diff --git a/chrome/browser/vr/service/browser_xr_runtime.cc b/chrome/browser/vr/service/browser_xr_runtime.cc
index 0ae4690..7dec6ed 100644
--- a/chrome/browser/vr/service/browser_xr_runtime.cc
+++ b/chrome/browser/vr/service/browser_xr_runtime.cc
@@ -4,7 +4,7 @@
 
 #include "chrome/browser/vr/service/browser_xr_runtime.h"
 
-#include "chrome/browser/vr/service/vr_display_host.h"
+#include "chrome/browser/vr/service/xr_device_impl.h"
 #include "device/vr/vr_device.h"
 
 namespace vr {
@@ -36,31 +36,31 @@
 void BrowserXRRuntime::OnDisplayInfoChanged(
     device::mojom::VRDisplayInfoPtr vr_device_info) {
   display_info_ = std::move(vr_device_info);
-  for (VRDisplayHost* display : displays_) {
-    display->OnChanged();
+  for (XRDeviceImpl* device : renderer_device_connections_) {
+    device->OnChanged();
   }
 }
 
 void BrowserXRRuntime::StopImmersiveSession() {
   if (immersive_session_controller_) {
     immersive_session_controller_ = nullptr;
-    presenting_display_host_ = nullptr;
+    presenting_renderer_device_ = nullptr;
   }
 }
 
 void BrowserXRRuntime::OnExitPresent() {
-  if (presenting_display_host_) {
-    presenting_display_host_->OnExitPresent();
-    presenting_display_host_ = nullptr;
+  if (presenting_renderer_device_) {
+    presenting_renderer_device_->OnExitPresent();
+    presenting_renderer_device_ = nullptr;
   }
 }
 
 void BrowserXRRuntime::OnDeviceActivated(
     device::mojom::VRDisplayEventReason reason,
     base::OnceCallback<void(bool)> on_handled) {
-  if (listening_for_activation_display_host_) {
-    listening_for_activation_display_host_->OnActivate(reason,
-                                                       std::move(on_handled));
+  if (listening_for_activation_renderer_device_) {
+    listening_for_activation_renderer_device_->OnActivate(
+        reason, std::move(on_handled));
   } else {
     std::move(on_handled).Run(true /* will_not_present */);
   }
@@ -68,57 +68,57 @@
 
 void BrowserXRRuntime::OnDeviceIdle(
     device::mojom::VRDisplayEventReason reason) {
-  for (VRDisplayHost* display : displays_) {
-    display->OnDeactivate(reason);
+  for (XRDeviceImpl* device : renderer_device_connections_) {
+    device->OnDeactivate(reason);
   }
 }
 
-void BrowserXRRuntime::OnDisplayHostAdded(VRDisplayHost* display) {
-  displays_.insert(display);
+void BrowserXRRuntime::OnRendererDeviceAdded(XRDeviceImpl* device) {
+  renderer_device_connections_.insert(device);
 }
 
-void BrowserXRRuntime::OnDisplayHostRemoved(VRDisplayHost* display) {
-  DCHECK(display);
-  displays_.erase(display);
-  if (display == presenting_display_host_) {
-    ExitPresent(display);
-    DCHECK(presenting_display_host_ == nullptr);
+void BrowserXRRuntime::OnRendererDeviceRemoved(XRDeviceImpl* device) {
+  DCHECK(device);
+  renderer_device_connections_.erase(device);
+  if (device == presenting_renderer_device_) {
+    ExitPresent(device);
+    DCHECK(presenting_renderer_device_ == nullptr);
   }
-  if (display == listening_for_activation_display_host_) {
+  if (device == listening_for_activation_renderer_device_) {
     // Not listening for activation.
-    listening_for_activation_display_host_ = nullptr;
+    listening_for_activation_renderer_device_ = nullptr;
     runtime_->SetListeningForActivate(false);
   }
 }
 
-void BrowserXRRuntime::ExitPresent(VRDisplayHost* display) {
-  if (display == presenting_display_host_) {
+void BrowserXRRuntime::ExitPresent(XRDeviceImpl* device) {
+  if (device == presenting_renderer_device_) {
     StopImmersiveSession();
   }
 }
 
 void BrowserXRRuntime::RequestSession(
-    VRDisplayHost* display,
+    XRDeviceImpl* device,
     const device::mojom::XRRuntimeSessionOptionsPtr& options,
-    device::mojom::VRDisplayHost::RequestSessionCallback callback) {
+    device::mojom::XRDevice::RequestSessionCallback callback) {
   // base::Unretained is safe because we won't be called back after runtime_ is
   // destroyed.
   runtime_->RequestSession(
       options->Clone(),
       base::BindOnce(&BrowserXRRuntime::OnRequestSessionResult,
-                     base::Unretained(this), display->GetWeakPtr(),
+                     base::Unretained(this), device->GetWeakPtr(),
                      options->Clone(), std::move(callback)));
 }
 
 void BrowserXRRuntime::OnRequestSessionResult(
-    base::WeakPtr<VRDisplayHost> display,
+    base::WeakPtr<XRDeviceImpl> device,
     device::mojom::XRRuntimeSessionOptionsPtr options,
-    device::mojom::VRDisplayHost::RequestSessionCallback callback,
+    device::mojom::XRDevice::RequestSessionCallback callback,
     device::mojom::XRSessionPtr session,
     device::mojom::XRSessionControllerPtr immersive_session_controller) {
-  if (session && display) {
+  if (session && device) {
     if (options->immersive) {
-      presenting_display_host_ = display.get();
+      presenting_renderer_device_ = device.get();
       immersive_session_controller_ = std::move(immersive_session_controller);
     }
 
@@ -134,14 +134,14 @@
   }
 }
 
-void BrowserXRRuntime::UpdateListeningForActivate(VRDisplayHost* display) {
-  if (display->ListeningForActivate() && display->InFocusedFrame()) {
-    bool was_listening = !!listening_for_activation_display_host_;
-    listening_for_activation_display_host_ = display;
+void BrowserXRRuntime::UpdateListeningForActivate(XRDeviceImpl* device) {
+  if (device->ListeningForActivate() && device->InFocusedFrame()) {
+    bool was_listening = !!listening_for_activation_renderer_device_;
+    listening_for_activation_renderer_device_ = device;
     if (!was_listening)
       OnListeningForActivate(true);
-  } else if (listening_for_activation_display_host_ == display) {
-    listening_for_activation_display_host_ = nullptr;
+  } else if (listening_for_activation_renderer_device_ == device) {
+    listening_for_activation_renderer_device_ = nullptr;
     OnListeningForActivate(false);
   }
 }
diff --git a/chrome/browser/vr/service/browser_xr_runtime.h b/chrome/browser/vr/service/browser_xr_runtime.h
index d29ab3d..8b69b4f 100644
--- a/chrome/browser/vr/service/browser_xr_runtime.h
+++ b/chrome/browser/vr/service/browser_xr_runtime.h
@@ -12,11 +12,11 @@
 
 namespace vr {
 
-class VRDisplayHost;
+class XRDeviceImpl;
 
 // This class wraps a physical device's interfaces, and registers for events.
 // There is one BrowserXRRuntime per physical device runtime.
-// It manages browser-side handling of state, like which VRDisplayHost is
+// It manages browser-side handling of state, like which XRDeviceImpl is
 // listening for device activation.
 class BrowserXRRuntime : public device::mojom::XRRuntimeEventListener {
  public:
@@ -26,16 +26,17 @@
 
   device::mojom::XRRuntime* GetRuntime() { return runtime_.get(); }
 
-  // Methods called by VRDisplayHost to interact with the runtime's device.
-  void OnDisplayHostAdded(VRDisplayHost* display);
-  void OnDisplayHostRemoved(VRDisplayHost* display);
-  void ExitPresent(VRDisplayHost* display);
-  void RequestSession(
-      VRDisplayHost* display,
-      const device::mojom::XRRuntimeSessionOptionsPtr& options,
-      device::mojom::VRDisplayHost::RequestSessionCallback callback);
-  VRDisplayHost* GetPresentingDisplayHost() { return presenting_display_host_; }
-  void UpdateListeningForActivate(VRDisplayHost* display);
+  // Methods called by XRDeviceImpl to interact with the runtime's device.
+  void OnRendererDeviceAdded(XRDeviceImpl* device);
+  void OnRendererDeviceRemoved(XRDeviceImpl* device);
+  void ExitPresent(XRDeviceImpl* device);
+  void RequestSession(XRDeviceImpl* device,
+                      const device::mojom::XRRuntimeSessionOptionsPtr& options,
+                      device::mojom::XRDevice::RequestSessionCallback callback);
+  XRDeviceImpl* GetPresentingRendererDevice() {
+    return presenting_renderer_device_;
+  }
+  void UpdateListeningForActivate(XRDeviceImpl* device);
   device::mojom::VRDisplayInfoPtr GetVRDisplayInfo() {
     return display_info_.Clone();
   }
@@ -54,20 +55,20 @@
   void StopImmersiveSession();
   void OnListeningForActivate(bool is_listening);
   void OnRequestSessionResult(
-      base::WeakPtr<VRDisplayHost> display,
+      base::WeakPtr<XRDeviceImpl> device,
       device::mojom::XRRuntimeSessionOptionsPtr options,
-      device::mojom::VRDisplayHost::RequestSessionCallback callback,
+      device::mojom::XRDevice::RequestSessionCallback callback,
       device::mojom::XRSessionPtr session,
       device::mojom::XRSessionControllerPtr immersive_session_controller);
 
   device::mojom::XRRuntimePtr runtime_;
   device::mojom::XRSessionControllerPtr immersive_session_controller_;
 
-  std::set<VRDisplayHost*> displays_;
+  std::set<XRDeviceImpl*> renderer_device_connections_;
   device::mojom::VRDisplayInfoPtr display_info_;
 
-  VRDisplayHost* listening_for_activation_display_host_ = nullptr;
-  VRDisplayHost* presenting_display_host_ = nullptr;
+  XRDeviceImpl* listening_for_activation_renderer_device_ = nullptr;
+  XRDeviceImpl* presenting_renderer_device_ = nullptr;
 
   mojo::Binding<device::mojom::XRRuntimeEventListener> binding_;
 
diff --git a/chrome/browser/vr/service/vr_service_impl.cc b/chrome/browser/vr/service/vr_service_impl.cc
index c8b8a065..04e0a3f 100644
--- a/chrome/browser/vr/service/vr_service_impl.cc
+++ b/chrome/browser/vr/service/vr_service_impl.cc
@@ -9,7 +9,7 @@
 #include "base/bind.h"
 
 #include "chrome/browser/vr/service/browser_xr_runtime.h"
-#include "chrome/browser/vr/service/vr_display_host.h"
+#include "chrome/browser/vr/service/xr_device_impl.h"
 #include "chrome/browser/vr/service/xr_runtime_manager.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_widget_host.h"
@@ -37,10 +37,10 @@
 }
 
 VRServiceImpl::~VRServiceImpl() {
-  // Destroy VRDisplayHost before calling RemoveService below. RemoveService
+  // Destroy XRDeviceImpl before calling RemoveService below. RemoveService
   // might implicitly destory the XRRuntimeManager, and therefore the
-  // BrowserXRRuntime that VRDisplayHost needs to access in its dtor.
-  display_ = nullptr;
+  // BrowserXRRuntime that XRDeviceImpl needs to access in its dtor.
+  device_ = nullptr;
   XRRuntimeManager::GetInstance()->RemoveService(this);
 }
 
@@ -67,32 +67,32 @@
 }
 
 void VRServiceImpl::InitializationComplete() {
-  // display_ is added after all providers have initialized.  This means that we
+  // device_ is added after all providers have initialized.  This means that we
   // can correctly answer SupportsSession, and can provide correct display
   // capabilities.
-  display_ = std::make_unique<VRDisplayHost>(render_frame_host_, client_.get());
+  device_ = std::make_unique<XRDeviceImpl>(render_frame_host_, client_.get());
   base::ResetAndReturn(&set_client_callback_).Run();
 }
 
 void VRServiceImpl::ConnectRuntime(BrowserXRRuntime* runtime) {
-  // display_ is initialized on IntializationComplete.  Devices may be added
-  // before that, but they'll be picked up during display_'s constructor.
-  // We just need to notify display_ when new capabilities were added after
+  // device_ is initialized on IntializationComplete.  Devices may be added
+  // before that, but they'll be picked up during device_'s constructor.
+  // We just need to notify device_ when new capabilities were added after
   // initialization.
-  if (display_) {
-    display_->OnRuntimeAvailable(runtime);
+  if (device_) {
+    device_->OnRuntimeAvailable(runtime);
   }
 }
 
 void VRServiceImpl::RemoveRuntime(BrowserXRRuntime* runtime) {
-  if (display_) {
-    display_->OnRuntimeRemoved(runtime);
+  if (device_) {
+    device_->OnRuntimeRemoved(runtime);
   }
 }
 
 void VRServiceImpl::SetListeningForActivate(bool listening) {
-  if (display_)
-    display_->SetListeningForActivate(listening);
+  if (device_)
+    device_->SetListeningForActivate(listening);
 }
 
 void VRServiceImpl::OnWebContentsFocused(content::RenderWidgetHost* host) {
@@ -120,8 +120,8 @@
       render_frame_host_->GetView()->GetRenderWidgetHost() != host) {
     return;
   }
-  if (display_)
-    display_->SetInFocusedFrame(focused);
+  if (device_)
+    device_->SetInFocusedFrame(focused);
 }
 
 }  // namespace vr
diff --git a/chrome/browser/vr/service/vr_service_impl.h b/chrome/browser/vr/service/vr_service_impl.h
index 6360129..52f622f 100644
--- a/chrome/browser/vr/service/vr_service_impl.h
+++ b/chrome/browser/vr/service/vr_service_impl.h
@@ -18,7 +18,7 @@
 
 namespace vr {
 
-class VRDisplayHost;
+class XRDeviceImpl;
 class BrowserXRRuntime;
 
 // Browser process representation of a WebVR site session. Instantiated through
@@ -65,7 +65,7 @@
 
   void OnWebContentsFocusChanged(content::RenderWidgetHost* host, bool focused);
 
-  std::unique_ptr<VRDisplayHost> display_;
+  std::unique_ptr<XRDeviceImpl> device_;
   SetClientCallback set_client_callback_;
   device::mojom::VRServiceClientPtr client_;
   content::RenderFrameHost* render_frame_host_;
diff --git a/chrome/browser/vr/service/vr_display_host.cc b/chrome/browser/vr/service/xr_device_impl.cc
similarity index 77%
rename from chrome/browser/vr/service/vr_display_host.cc
rename to chrome/browser/vr/service/xr_device_impl.cc
index 4e77671..8c535dbd 100644
--- a/chrome/browser/vr/service/vr_display_host.cc
+++ b/chrome/browser/vr/service/xr_device_impl.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/vr/service/vr_display_host.h"
+#include "chrome/browser/vr/service/xr_device_impl.h"
 
 #include "base/bind.h"
 #include "base/command_line.h"
@@ -53,13 +53,13 @@
 
 }  // namespace
 
-device::mojom::VRDisplayInfoPtr VRDisplayHost::GetCurrentVRDisplayInfo() {
+device::mojom::VRDisplayInfoPtr XRDeviceImpl::GetCurrentVRDisplayInfo() {
   // Get an immersive_runtime_ device if there is one.
   if (!immersive_runtime_) {
     immersive_runtime_ = XRRuntimeManager::GetInstance()->GetImmersiveRuntime();
     if (immersive_runtime_) {
       // Listen to changes for this device.
-      immersive_runtime_->OnDisplayHostAdded(this);
+      immersive_runtime_->OnRendererDeviceAdded(this);
     }
   }
 
@@ -71,11 +71,11 @@
         XRRuntimeManager::GetInstance()->GetRuntimeForOptions(&options);
     if (ar_runtime_) {
       // Listen to  changes for this device.
-      ar_runtime_->OnDisplayHostAdded(this);
+      ar_runtime_->OnRendererDeviceAdded(this);
     }
   }
 
-  // If there is neither, use the magic window device.
+  // If there is neither, use the generic non-immersive device.
   if (!ar_runtime_ && !immersive_runtime_) {
     if (!non_immersive_runtime_) {
       device::mojom::XRSessionOptions options = {};
@@ -83,12 +83,12 @@
           XRRuntimeManager::GetInstance()->GetRuntimeForOptions(&options);
       if (non_immersive_runtime_) {
         // Listen to changes for this device.
-        non_immersive_runtime_->OnDisplayHostAdded(this);
+        non_immersive_runtime_->OnRendererDeviceAdded(this);
       }
     }
 
-    // If we don't have an AR or immersive device, return the magic-window's
-    // DisplayInfo if we have it.
+    // If we don't have an AR or immersive device, return the generic non-
+    // immersive device's DisplayInfo if we have it.
     return non_immersive_runtime_ ? non_immersive_runtime_->GetVRDisplayInfo()
                                   : nullptr;
   }
@@ -103,10 +103,10 @@
   return device_info;
 }
 
-VRDisplayHost::VRDisplayHost(content::RenderFrameHost* render_frame_host,
-                             device::mojom::VRServiceClient* service_client)
+XRDeviceImpl::XRDeviceImpl(content::RenderFrameHost* render_frame_host,
+                           device::mojom::VRServiceClient* service_client)
     :  // TODO(https://crbug.com/846392): render_frame_host can be null because
-       // of a test, not because a VRDisplayHost can be created without it.
+       // of a test, not because a XRDeviceImpl can be created without it.
       in_focused_frame_(
           render_frame_host ? render_frame_host->GetView()->HasFocus() : false),
       render_frame_host_(render_frame_host),
@@ -118,15 +118,15 @@
   }
 
   // Tell blink that we are available.
-  device::mojom::VRDisplayHostPtr display_host;
-  binding_.Bind(mojo::MakeRequest(&display_host));
-  service_client->OnDisplayConnected(std::move(display_host),
+  device::mojom::XRDevicePtr device_ptr;
+  binding_.Bind(mojo::MakeRequest(&device_ptr));
+  service_client->OnDisplayConnected(std::move(device_ptr),
                                      mojo::MakeRequest(&client_),
                                      std::move(display_info));
 }
 
-void VRDisplayHost::OnMagicWindowSessionCreated(
-    device::mojom::VRDisplayHost::RequestSessionCallback callback,
+void XRDeviceImpl::OnMagicWindowSessionCreated(
+    device::mojom::XRDevice::RequestSessionCallback callback,
     device::mojom::XRSessionPtr session,
     device::mojom::XRSessionControllerPtr controller) {
   if (!session) {
@@ -142,19 +142,19 @@
   std::move(callback).Run(std::move(session));
 }
 
-VRDisplayHost::~VRDisplayHost() {
+XRDeviceImpl::~XRDeviceImpl() {
   if (immersive_runtime_)
-    immersive_runtime_->OnDisplayHostRemoved(this);
+    immersive_runtime_->OnRendererDeviceRemoved(this);
   if (non_immersive_runtime_)
-    non_immersive_runtime_->OnDisplayHostRemoved(this);
+    non_immersive_runtime_->OnRendererDeviceRemoved(this);
   if (ar_runtime_)
-    ar_runtime_->OnDisplayHostRemoved(this);
+    ar_runtime_->OnRendererDeviceRemoved(this);
 }
 
-void VRDisplayHost::RequestSession(
+void XRDeviceImpl::RequestSession(
     device::mojom::XRSessionOptionsPtr options,
     bool triggered_by_displayactive,
-    device::mojom::VRDisplayHost::RequestSessionCallback callback) {
+    device::mojom::XRDevice::RequestSessionCallback callback) {
   DCHECK(options);
 
   // Check that the request satisifies secure context requirements.
@@ -172,8 +172,8 @@
   BrowserXRRuntime* presenting_runtime =
       XRRuntimeManager::GetInstance()->GetImmersiveRuntime();
   if (presenting_runtime &&
-      presenting_runtime->GetPresentingDisplayHost() != this &&
-      presenting_runtime->GetPresentingDisplayHost() != nullptr) {
+      presenting_runtime->GetPresentingRendererDevice() != this &&
+      presenting_runtime->GetPresentingRendererDevice() != nullptr) {
     // Can't create sessions while an immersive session exists.
     std::move(callback).Run(nullptr);
     return;
@@ -204,26 +204,26 @@
     base::OnceCallback<void(device::mojom::XRSessionPtr,
                             device::mojom::XRSessionControllerPtr)>
         magic_window_callback =
-            base::BindOnce(&VRDisplayHost::OnMagicWindowSessionCreated,
+            base::BindOnce(&XRDeviceImpl::OnMagicWindowSessionCreated,
                            weak_ptr_factory_.GetWeakPtr(), std::move(callback));
     runtime->GetRuntime()->RequestSession(std::move(runtime_options),
                                           std::move(magic_window_callback));
   }
 }
 
-void VRDisplayHost::SupportsSession(device::mojom::XRSessionOptionsPtr options,
-                                    SupportsSessionCallback callback) {
+void XRDeviceImpl::SupportsSession(device::mojom::XRSessionOptionsPtr options,
+                                   SupportsSessionCallback callback) {
   std::move(callback).Run(InternalSupportsSession(options.get()));
 }
 
-bool VRDisplayHost::InternalSupportsSession(
+bool XRDeviceImpl::InternalSupportsSession(
     device::mojom::XRSessionOptions* options) {
   // Return whether we can get a device that supports the specified options.
   return XRRuntimeManager::GetInstance()->GetRuntimeForOptions(options) !=
          nullptr;
 }
 
-void VRDisplayHost::ReportRequestPresent() {
+void XRDeviceImpl::ReportRequestPresent() {
   content::WebContents* web_contents =
       content::WebContents::FromRenderFrameHost(render_frame_host_);
   SessionMetricsHelper* metrics_helper =
@@ -237,12 +237,12 @@
   metrics_helper->ReportRequestPresent();
 }
 
-void VRDisplayHost::ExitPresent() {
+void XRDeviceImpl::ExitPresent() {
   if (immersive_runtime_)
     immersive_runtime_->ExitPresent(this);
 }
 
-void VRDisplayHost::SetListeningForActivate(bool listening) {
+void XRDeviceImpl::SetListeningForActivate(bool listening) {
   listening_for_activate_ = listening;
   if (!immersive_runtime_) {
     return;
@@ -250,7 +250,7 @@
   immersive_runtime_->UpdateListeningForActivate(this);
 }
 
-void VRDisplayHost::SetInFocusedFrame(bool in_focused_frame) {
+void XRDeviceImpl::SetInFocusedFrame(bool in_focused_frame) {
   in_focused_frame_ = in_focused_frame;
   SetListeningForActivate(listening_for_activate_);  // No change, except focus.
 
@@ -260,13 +260,13 @@
       });
 }
 
-void VRDisplayHost::OnChanged() {
+void XRDeviceImpl::OnChanged() {
   device::mojom::VRDisplayInfoPtr display_info = GetCurrentVRDisplayInfo();
   if (display_info && client_)
     client_->OnChanged(std::move(display_info));
 }
 
-void VRDisplayHost::OnRuntimeRemoved(BrowserXRRuntime* runtime) {
+void XRDeviceImpl::OnRuntimeRemoved(BrowserXRRuntime* runtime) {
   if (runtime == immersive_runtime_) {
     immersive_runtime_ = nullptr;
   }
@@ -283,33 +283,33 @@
   OnChanged();
 }
 
-void VRDisplayHost::OnRuntimeAvailable(BrowserXRRuntime* runtime) {
+void XRDeviceImpl::OnRuntimeAvailable(BrowserXRRuntime* runtime) {
   // Try to update our VRDisplayInfo.  That may use the new device.
   OnChanged();
 }
 
-void VRDisplayHost::OnExitPresent() {
+void XRDeviceImpl::OnExitPresent() {
   client_->OnExitPresent();
 }
 
-void VRDisplayHost::OnBlur() {
+void XRDeviceImpl::OnBlur() {
   client_->OnBlur();
 }
 
-void VRDisplayHost::OnFocus() {
+void XRDeviceImpl::OnFocus() {
   client_->OnFocus();
 }
 
-void VRDisplayHost::OnActivate(device::mojom::VRDisplayEventReason reason,
-                               base::OnceCallback<void(bool)> on_handled) {
+void XRDeviceImpl::OnActivate(device::mojom::VRDisplayEventReason reason,
+                              base::OnceCallback<void(bool)> on_handled) {
   client_->OnActivate(reason, std::move(on_handled));
 }
 
-void VRDisplayHost::OnDeactivate(device::mojom::VRDisplayEventReason reason) {
+void XRDeviceImpl::OnDeactivate(device::mojom::VRDisplayEventReason reason) {
   client_->OnDeactivate(reason);
 }
 
-bool VRDisplayHost::IsSecureContextRequirementSatisfied() {
+bool XRDeviceImpl::IsSecureContextRequirementSatisfied() {
   // We require secure connections unless both the webvr flag and the
   // http flag are enabled.
   bool requires_secure_context =
diff --git a/chrome/browser/vr/service/vr_display_host.h b/chrome/browser/vr/service/xr_device_impl.h
similarity index 78%
rename from chrome/browser/vr/service/vr_display_host.h
rename to chrome/browser/vr/service/xr_device_impl.h
index 87af900..aff7bce 100644
--- a/chrome/browser/vr/service/vr_display_host.h
+++ b/chrome/browser/vr/service/xr_device_impl.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_VR_SERVICE_VR_DISPLAY_HOST_H_
-#define CHROME_BROWSER_VR_SERVICE_VR_DISPLAY_HOST_H_
+#ifndef CHROME_BROWSER_VR_SERVICE_XR_DEVICE_IMPL_H_
+#define CHROME_BROWSER_VR_SERVICE_XR_DEVICE_IMPL_H_
 
 #include <map>
 #include <memory>
@@ -29,17 +29,17 @@
 
 // The browser-side host for a device::VRDisplayImpl. Controls access to VR
 // APIs like poses and presentation.
-class VRDisplayHost : public device::mojom::VRDisplayHost {
+class XRDeviceImpl : public device::mojom::XRDevice {
  public:
-  VRDisplayHost(content::RenderFrameHost* render_frame_host,
-                device::mojom::VRServiceClient* service_client);
-  ~VRDisplayHost() override;
+  XRDeviceImpl(content::RenderFrameHost* render_frame_host,
+               device::mojom::VRServiceClient* service_client);
+  ~XRDeviceImpl() override;
 
-  // device::mojom::VRDisplayHost
+  // device::mojom::XRDevice
   void RequestSession(
       device::mojom::XRSessionOptionsPtr options,
       bool triggered_by_displayactive,
-      device::mojom::VRDisplayHost::RequestSessionCallback callback) override;
+      device::mojom::XRDevice::RequestSessionCallback callback) override;
   void SupportsSession(device::mojom::XRSessionOptionsPtr options,
                        SupportsSessionCallback callback) override;
   void ExitPresent() override;
@@ -62,7 +62,7 @@
   bool ListeningForActivate() { return listening_for_activate_; }
   bool InFocusedFrame() { return in_focused_frame_; }
 
-  base::WeakPtr<VRDisplayHost> GetWeakPtr() {
+  base::WeakPtr<XRDeviceImpl> GetWeakPtr() {
     return weak_ptr_factory_.GetWeakPtr();
   }
 
@@ -72,7 +72,7 @@
 
   bool InternalSupportsSession(device::mojom::XRSessionOptions* options);
   void OnMagicWindowSessionCreated(
-      device::mojom::VRDisplayHost::RequestSessionCallback callback,
+      device::mojom::XRDevice::RequestSessionCallback callback,
       device::mojom::XRSessionPtr session,
       device::mojom::XRSessionControllerPtr controller);
 
@@ -86,7 +86,7 @@
   bool listening_for_activate_ = false;
 
   content::RenderFrameHost* render_frame_host_;
-  mojo::Binding<device::mojom::VRDisplayHost> binding_;
+  mojo::Binding<device::mojom::XRDevice> binding_;
   device::mojom::VRDisplayClientPtr client_;
 
   mojo::InterfacePtrSet<device::mojom::XRSessionController>
@@ -99,11 +99,11 @@
   BrowserXRRuntime* non_immersive_runtime_ = nullptr;
   BrowserXRRuntime* ar_runtime_ = nullptr;
 
-  base::WeakPtrFactory<VRDisplayHost> weak_ptr_factory_;
+  base::WeakPtrFactory<XRDeviceImpl> weak_ptr_factory_;
 
-  DISALLOW_COPY_AND_ASSIGN(VRDisplayHost);
+  DISALLOW_COPY_AND_ASSIGN(XRDeviceImpl);
 };
 
 }  // namespace vr
 
-#endif  // CHROME_BROWSER_VR_SERVICE_VR_DISPLAY_HOST_H_
+#endif  // CHROME_BROWSER_VR_SERVICE_XR_DEVICE_IMPL_H_
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index b52c321..55d4642 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -382,10 +382,6 @@
 #endif
 };
 
-// Enables or disables Mirroring Service.
-const base::Feature kMirroringService{"MirroringService",
-                                      base::FEATURE_DISABLED_BY_DEFAULT};
-
 // Enables or disables modal permission prompts.
 const base::Feature kModalPermissionPrompts{"ModalPermissionPrompts",
                                             base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 61d3ca1..312c44fb 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -208,8 +208,6 @@
 
 extern const base::Feature kMaterialDesignIncognitoNTP;
 
-extern const base::Feature kMirroringService;
-
 extern const base::Feature kModalPermissionPrompts;
 
 #if BUILDFLAG(ENABLE_NATIVE_NOTIFICATIONS)
diff --git a/chrome/common/trace_event_args_whitelist.cc b/chrome/common/trace_event_args_whitelist.cc
index 4df9e440..469195f 100644
--- a/chrome/common/trace_event_args_whitelist.cc
+++ b/chrome/common/trace_event_args_whitelist.cc
@@ -32,6 +32,7 @@
     {"ipc", "SyncChannel::Send", nullptr},
     {"toplevel", "*", nullptr},
     {"latencyInfo", "*", kInputLatencyAllowedArgs},
+    {"omnibox", "HistoryQuickProvider::Start", nullptr},
     // Redefined the string since MemoryDumpManager::kTraceCategory causes
     // static initialization of this struct.
     {TRACE_DISABLED_BY_DEFAULT("memory-infra"), "*", kMemoryDumpAllowedArgs},
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 7b72032..18030aeb 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2441,8 +2441,6 @@
     "../browser/history/history_tab_helper_unittest.cc",
     "../browser/infobars/mock_infobar_service.cc",
     "../browser/infobars/mock_infobar_service.h",
-    "../browser/install_verification/win/loaded_module_verification_unittest.cc",
-    "../browser/install_verification/win/module_ids_unittest.cc",
     "../browser/install_verification/win/module_info_unittest.cc",
     "../browser/install_verification/win/module_list_unittest.cc",
     "../browser/install_verification/win/module_verification_test.cc",
@@ -4271,9 +4269,13 @@
   if (is_win || is_mac || is_chromeos) {
     sources += [ "../browser/extensions/api/networking_private/networking_private_crypto_unittest.cc" ]
   }
-  if (enable_rlz) {
+  if (enable_rlz_support) {
     sources += [ "../browser/rlz/chrome_rlz_tracker_delegate_unittest.cc" ]
-    deps += [ "//rlz:test_support" ]
+    deps += [
+      "//chrome/browser:rlz",
+      "//components/rlz",
+      "//rlz:test_support",
+    ]
   }
   if (is_win) {
     if (!is_component_build) {
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeJUnit4ClassRunner.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeJUnit4ClassRunner.java
index 55744e4..be08a72 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeJUnit4ClassRunner.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeJUnit4ClassRunner.java
@@ -85,11 +85,10 @@
             }
         }
 
-        private boolean isDonEnabled() {
-            // We can't directly check whether the VR DON flow is enabled since
-            // we don't have permission to read the VrCore settings file. Instead,
-            // pass a flag.
-            return CommandLine.getInstance().hasSwitch("don-enabled");
+        private boolean isVrSettingsServiceEnabled() {
+            // We can't directly check whether the VR settings service is enabled since we don't
+            // have permission to read the VrCore settings file. Instead, pass a flag.
+            return CommandLine.getInstance().hasSwitch("vr-settings-service-enabled");
         }
 
         @Override
@@ -133,8 +132,9 @@
                     return true;
                 }
             }
-            if (TextUtils.equals(restriction, ChromeRestriction.RESTRICTION_TYPE_DON_ENABLED)) {
-                return !isDonEnabled();
+            if (TextUtils.equals(
+                        restriction, ChromeRestriction.RESTRICTION_TYPE_VR_SETTINGS_SERVICE)) {
+                return !isVrSettingsServiceEnabled();
             }
             return false;
         }
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ChromeRestriction.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ChromeRestriction.java
index 05237f5f..48cd683c7 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ChromeRestriction.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ChromeRestriction.java
@@ -21,6 +21,6 @@
     public static final String RESTRICTION_TYPE_VIEWER_DAYDREAM = "Daydream_View";
     /** Specifies the test is only valid if the current VR viewer is not Daydream View */
     public static final String RESTRICTION_TYPE_VIEWER_NON_DAYDREAM = "Non_Daydream_View";
-    /** Specifies the test is only valid if the DON flow is not skipped */
-    public static final String RESTRICTION_TYPE_DON_ENABLED = "DON_Enabled";
+    /** Specifies the test is only valid if the VR settings service is enabled */
+    public static final String RESTRICTION_TYPE_VR_SETTINGS_SERVICE = "VR_Settings_Service";
 }
diff --git a/chrome/test/base/testing_profile.cc b/chrome/test/base/testing_profile.cc
index 1d76e193..743e40d 100644
--- a/chrome/test/base/testing_profile.cc
+++ b/chrome/test/base/testing_profile.cc
@@ -320,6 +320,7 @@
     std::unique_ptr<sync_preferences::PrefServiceSyncable> prefs,
     TestingProfile* parent,
     bool guest_session,
+    base::Optional<bool> is_new_profile,
     const std::string& supervised_user_id,
     std::unique_ptr<policy::PolicyService> policy_service,
     const TestingFactories& factories,
@@ -330,6 +331,7 @@
       force_incognito_(false),
       original_profile_(parent),
       guest_session_(guest_session),
+      is_new_profile_(std::move(is_new_profile)),
       supervised_user_id_(supervised_user_id),
       last_session_exited_cleanly_(true),
 #if BUILDFLAG(ENABLE_EXTENSIONS)
@@ -634,6 +636,10 @@
   guest_session_ = guest;
 }
 
+void TestingProfile::SetIsNewProfile(bool is_new_profile) {
+  is_new_profile_ = is_new_profile;
+}
+
 base::FilePath TestingProfile::GetPath() const {
   return profile_path_;
 }
@@ -990,6 +996,12 @@
   return guest_session_;
 }
 
+bool TestingProfile::IsNewProfile() {
+  if (is_new_profile_.has_value())
+    return is_new_profile_.value();
+  return Profile::IsNewProfile();
+}
+
 Profile::ExitType TestingProfile::GetLastSessionExitType() {
   return last_session_exited_cleanly_ ? EXIT_NORMAL : EXIT_CRASHED;
 }
@@ -1036,6 +1048,10 @@
   guest_session_ = true;
 }
 
+void TestingProfile::Builder::OverrideIsNewProfile(bool is_new_profile) {
+  is_new_profile_ = is_new_profile;
+}
+
 void TestingProfile::Builder::SetSupervisedUserId(
     const std::string& supervised_user_id) {
   supervised_user_id_ = supervised_user_id;
@@ -1065,7 +1081,8 @@
 #if BUILDFLAG(ENABLE_EXTENSIONS)
       extension_policy_,
 #endif
-      std::move(pref_service_), NULL, guest_session_, supervised_user_id_,
+      std::move(pref_service_), NULL, guest_session_,
+      std::move(is_new_profile_), supervised_user_id_,
       std::move(policy_service_), testing_factories_, profile_name_));
 }
 
@@ -1081,7 +1098,7 @@
                             extension_policy_,
 #endif
                             std::move(pref_service_), original_profile,
-                            guest_session_, supervised_user_id_,
-                            std::move(policy_service_), testing_factories_,
-                            profile_name_);
+                            guest_session_, std::move(is_new_profile_),
+                            supervised_user_id_, std::move(policy_service_),
+                            testing_factories_, profile_name_);
 }
diff --git a/chrome/test/base/testing_profile.h b/chrome/test/base/testing_profile.h
index 97ba3f6..51267aa 100644
--- a/chrome/test/base/testing_profile.h
+++ b/chrome/test/base/testing_profile.h
@@ -13,6 +13,7 @@
 #include "base/files/scoped_temp_dir.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/optional.h"
 #include "build/build_config.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/domain_reliability/clear_mode.h"
@@ -113,6 +114,10 @@
     // Makes the Profile being built a guest profile.
     void SetGuestSession();
 
+    // Override the default behavior of is_new_profile to return the provided
+    // value.
+    void OverrideIsNewProfile(bool is_new_profile);
+
     // Sets the supervised user ID (which is empty by default). If it is set to
     // a non-empty string, the profile is supervised.
     void SetSupervisedUserId(const std::string& supervised_user_id);
@@ -144,6 +149,7 @@
     base::FilePath path_;
     Delegate* delegate_;
     bool guest_session_;
+    base::Optional<bool> is_new_profile_;
     std::string supervised_user_id_;
     std::unique_ptr<policy::PolicyService> policy_service_;
     TestingFactories testing_factories_;
@@ -175,6 +181,7 @@
                  std::unique_ptr<sync_preferences::PrefServiceSyncable> prefs,
                  TestingProfile* parent,
                  bool guest_session,
+                 base::Optional<bool> is_new_profile,
                  const std::string& supervised_user_id,
                  std::unique_ptr<policy::PolicyService> policy_service,
                  const TestingFactories& factories,
@@ -220,6 +227,9 @@
   // Allow setting a profile as Guest after-the-fact to simplify some tests.
   void SetGuestSession(bool guest);
 
+  // Allow setting the return value of IsNewProfile.
+  void SetIsNewProfile(bool is_new_profile);
+
   sync_preferences::TestingPrefServiceSyncable* GetTestingPrefService();
 
   // Called on the parent of an incognito |profile|. Usually called from the
@@ -322,6 +332,7 @@
   void set_last_selected_directory(const base::FilePath& path) override;
   bool WasCreatedByVersionOrLater(const std::string& version) override;
   bool IsGuestSession() const override;
+  bool IsNewProfile() override;
   void SetExitType(ExitType exit_type) override {}
   ExitType GetLastSessionExitType() override;
   network::mojom::NetworkContextPtr CreateMainNetworkContext() override;
@@ -394,6 +405,8 @@
 
   bool guest_session_;
 
+  base::Optional<bool> is_new_profile_;
+
   std::string supervised_user_id_;
 
   // Did the last session exit cleanly? Default is true.
diff --git a/chrome/test/chromedriver/test/run_java_tests.py b/chrome/test/chromedriver/test/run_java_tests.py
index da0a29d..edd660e 100755
--- a/chrome/test/chromedriver/test/run_java_tests.py
+++ b/chrome/test/chromedriver/test/run_java_tests.py
@@ -93,7 +93,7 @@
       chrome_wrapper_path = os.path.join(java_tests_src_dir, 'chrome-wrapper-no-sandbox')
       with open(chrome_wrapper_path, 'w') as f:
         f.write('#!/bin/sh\n')
-        f.write('exec %s --no-sandbox --disable-gpu "$@"\n' %
+        f.write('exec "%s" --no-sandbox --disable-gpu "$@"\n' %
             os.path.abspath(chrome_path))
       st = os.stat(chrome_wrapper_path)
       os.chmod(chrome_wrapper_path, st.st_mode | stat.S_IEXEC)
@@ -103,7 +103,7 @@
       chrome_wrapper_path = os.path.join(java_tests_src_dir, 'chrome-wrapper')
       with open(chrome_wrapper_path, 'w') as f:
         f.write('#!/bin/sh\n')
-        f.write('exec %s --force-color-profile=srgb "$@"\n' %
+        f.write('exec "%s" --force-color-profile=srgb "$@"\n' %
             os.path.abspath(chrome_path))
       st = os.stat(chrome_wrapper_path)
       os.chmod(chrome_wrapper_path, st.st_mode | stat.S_IEXEC)
diff --git a/chrome/test/chromedriver/test/run_py_tests.py b/chrome/test/chromedriver/test/run_py_tests.py
index 04cbc89..96be8222 100755
--- a/chrome/test/chromedriver/test/run_py_tests.py
+++ b/chrome/test/chromedriver/test/run_py_tests.py
@@ -83,7 +83,10 @@
 ]
 
 _VERSION_SPECIFIC_FILTER = {}
-_VERSION_SPECIFIC_FILTER['HEAD'] = []
+_VERSION_SPECIFIC_FILTER['HEAD'] = [
+    # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2532
+    'ChromeDriverPageLoadTimeoutTest.testRefreshWithPageLoadTimeout',
+]
 
 _VERSION_SPECIFIC_FILTER['69'] = [
     # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2515
diff --git a/chrome/test/chromedriver/test/test_expectations b/chrome/test/chromedriver/test/test_expectations
index 5a9d49e6..94650b3f 100644
--- a/chrome/test/chromedriver/test/test_expectations
+++ b/chrome/test/chromedriver/test/test_expectations
@@ -177,6 +177,8 @@
     'TypingTest.testNumericShiftKeys',
     'TypingTest.testAllPrintableKeys',
     'TypingTest.testChordControlCutAndPaste',
+    # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2532
+    'SlowLoadingPageTest.testRefreshShouldBlockUntilPageLoads',
 ]
 
 _OS_NEGATIVE_FILTER = {}
diff --git a/chrome/test/data/extensions/api_test/mime_handler_view/index.js b/chrome/test/data/extensions/api_test/mime_handler_view/index.js
index 72d5e7c9..ec1f767 100644
--- a/chrome/test/data/extensions/api_test/mime_handler_view/index.js
+++ b/chrome/test/data/extensions/api_test/mime_handler_view/index.js
@@ -58,6 +58,10 @@
   chrome.test.assertTrue(streamDetails.tabId != -1);
 }
 
+// The following helper methods are used in BrowserPlugin-specific tests.
+function dummyTouchStartHandler(e) {
+}
+
 var tests = [
   function testBasic() {
     checkStreamDetails('testBasic.csv', false);
diff --git a/chrome/test/data/xr/e2e_test_files/vr_settings_files/ddview_donenabled.txt b/chrome/test/data/xr/e2e_test_files/vr_settings_files/ddview_donenabled.txt
new file mode 100644
index 0000000..2981ed7
--- /dev/null
+++ b/chrome/test/data/xr/e2e_test_files/vr_settings_files/ddview_donenabled.txt
@@ -0,0 +1,16 @@
+# Settings file that enables the DON flow during tests.
+
+# Enable DON flow.
+b VrSkipDon false
+# Mark first time setup as complete.
+b DaydreamSetupComplete true
+# Pair 2016 Daydream View.
+s VrDeviceParams CgxHb29nbGUsIEluYy4SDURheWRyZWFtIFZpZXcdCfkgPSUB3oI9KhAAAFxCAABcQgAAXEIAAFxCNd9PDT06CLgexT7Zzhc_WABgAJqRYBoIARIKDQAAAAAV9P3UPBIKDQAAAAAV9P3UvA
+# Don't use the VrCore-side emulated controller.
+b UseAutomatedController false
+s PairedControllerDriver DRIVER_P6
+s PairedControllerAddress AA:AA:AA:AA:AA:AA
+# Disable the VrCore head tracking service.
+b EnableVrCoreHeadTracking false
+# Ensure that the VR settings service used to apply these files remains enabled.
+b EnableDeveloperService true
diff --git a/chromecast/BUILD.gn b/chromecast/BUILD.gn
index 3bd431ab..c9ce2b4 100644
--- a/chromecast/BUILD.gn
+++ b/chromecast/BUILD.gn
@@ -583,6 +583,7 @@
     "ENABLE_CHROMECAST_EXTENSIONS=$enable_chromecast_extensions",
     "ENABLE_CAST_FRAGMENT=$enable_cast_fragment",
     "IS_ANDROID_THINGS_NON_PUBLIC=$is_android_things_non_public",
+    "USE_CHROMECAST_CDMS=$use_chromecast_cdms",
   ]
 }
 
diff --git a/chromecast/base/BUILD.gn b/chromecast/base/BUILD.gn
index da7be61..04d7696 100644
--- a/chromecast/base/BUILD.gn
+++ b/chromecast/base/BUILD.gn
@@ -348,9 +348,15 @@
     java_test_dir = "//chromecast/base/java/test"
     java_files = [
       "$java_test_dir/org/chromium/chromecast/base/BothTest.java",
+      "$java_test_dir/org/chromium/chromecast/base/ControllerTest.java",
       "$java_test_dir/org/chromium/chromecast/base/CircularBufferTest.java",
       "$java_test_dir/org/chromium/chromecast/base/ItertoolsTest.java",
-      "$java_test_dir/org/chromium/chromecast/base/ObservableAndControllerTest.java",
+      "$java_test_dir/org/chromium/chromecast/base/ObservableAndTest.java",
+      "$java_test_dir/org/chromium/chromecast/base/ObservableAndThenTest.java",
+      "$java_test_dir/org/chromium/chromecast/base/ObservableFilterTest.java",
+      "$java_test_dir/org/chromium/chromecast/base/ObservableMapTest.java",
+      "$java_test_dir/org/chromium/chromecast/base/ObservableMiscellaneousTest.java",
+      "$java_test_dir/org/chromium/chromecast/base/ObservableNotTest.java",
       "$java_test_dir/org/chromium/chromecast/base/ReactiveRecorderTest.java",
       "$java_test_dir/org/chromium/chromecast/base/ObserversTest.java",
       "$java_test_dir/org/chromium/chromecast/base/UnitTest.java",
diff --git a/chromecast/base/java/src/org/chromium/chromecast/base/Both.java b/chromecast/base/java/src/org/chromium/chromecast/base/Both.java
index 18cfb10..f5c7c38 100644
--- a/chromecast/base/java/src/org/chromium/chromecast/base/Both.java
+++ b/chromecast/base/java/src/org/chromium/chromecast/base/Both.java
@@ -4,6 +4,10 @@
 
 package org.chromium.chromecast.base;
 
+import android.annotation.SuppressLint;
+
+import java.util.Objects;
+
 /**
  * Represents a structure containing an instance of both A and B.
  *
@@ -45,6 +49,21 @@
                 .toString();
     }
 
+    @Override
+    public boolean equals(Object other) {
+        if (other instanceof Both) {
+            Both<?, ?> that = (Both<?, ?>) other;
+            return this.first.equals(that.first) && this.second.equals(that.second);
+        }
+        return false;
+    }
+
+    @SuppressLint("NewApi")
+    @Override
+    public int hashCode() {
+        return Objects.hash(this.first, this.second);
+    }
+
     /**
      * Constructs a Both object containing both `a` and `b`.
      */
diff --git a/chromecast/base/java/test/org/chromium/chromecast/base/BothTest.java b/chromecast/base/java/test/org/chromium/chromecast/base/BothTest.java
index d344c69..040c116 100644
--- a/chromecast/base/java/test/org/chromium/chromecast/base/BothTest.java
+++ b/chromecast/base/java/test/org/chromium/chromecast/base/BothTest.java
@@ -50,6 +50,15 @@
     }
 
     @Test
+    public void testBothEquals() {
+        assertTrue(Both.both("a", "b").equals(Both.both("a", "b")));
+        assertFalse(Both.both("a", "b").equals(Both.both("b", "b")));
+        assertFalse(Both.both("a", "b").equals(Both.both("a", "a")));
+        assertFalse(Both.both(1, 2).equals(Both.both("a", "b")));
+        assertFalse(Both.both("hi", 0).equals(new Object()));
+    }
+
+    @Test
     public void testUseGetFirstAsMethodReference() {
         Both<Integer, String> x = Both.both(1, "one");
         Function<Both<Integer, String>, Integer> getFirst = Both::getFirst;
diff --git a/chromecast/base/java/test/org/chromium/chromecast/base/ControllerTest.java b/chromecast/base/java/test/org/chromium/chromecast/base/ControllerTest.java
new file mode 100644
index 0000000..d5be651
--- /dev/null
+++ b/chromecast/base/java/test/org/chromium/chromecast/base/ControllerTest.java
@@ -0,0 +1,248 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chromecast.base;
+
+import static org.hamcrest.Matchers.contains;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for behavior specific to Controller.
+ */
+@RunWith(BlockJUnit4ClassRunner.class)
+public class ControllerTest {
+    // Convenience method to create a scope that mutates a list of strings on state transitions.
+    // When entering the state, it will append "enter ${id} ${data}" to the result list, where
+    // `data` is the String that is associated with the state activation. When exiting the state,
+    // it will append "exit ${id}" to the result list. This provides a readable way to track and
+    // verify the behavior of observers in response to the Observables they are linked to.
+    public static <T> Observer<T> report(List<String> result, String id) {
+        // Did you know that lambdas are awesome.
+        return (T data) -> {
+            result.add("enter " + id + ": " + data);
+            return () -> result.add("exit " + id);
+        };
+    }
+
+    @Test
+    public void testNoStateTransitionAfterRegisteringWithInactiveController() {
+        Controller<String> controller = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(controller);
+        recorder.verify().end();
+    }
+
+    @Test
+    public void testStateIsopenedWhenControllerIsSet() {
+        Controller<String> controller = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(controller);
+        // Activate the state by setting the controller.
+        controller.set("cool");
+        recorder.verify().opened("cool").end();
+    }
+
+    @Test
+    public void testBasicStateFromController() {
+        Controller<String> controller = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(controller);
+        controller.set("fun");
+        // Deactivate the state by resetting the controller.
+        controller.reset();
+        recorder.verify().opened("fun").closed("fun").end();
+    }
+
+    @Test
+    public void testSetStateTwicePerformsImplicitReset() {
+        Controller<String> controller = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(controller);
+        // Activate the state for the first time.
+        controller.set("first");
+        // Activate the state for the second time.
+        controller.set("second");
+        // If set() is called without a reset() in-between, the tracking state exits, then re-enters
+        // with the new data. So we expect to find an "exit" call between the two enter calls.
+        recorder.verify().opened("first").closed("first").opened("second").end();
+    }
+
+    @Test
+    public void testResetWhileStateIsNotopenedIsNoOp() {
+        Controller<String> controller = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(controller);
+        controller.reset();
+        recorder.verify().end();
+    }
+
+    @Test
+    public void testMultipleStatesObservingSingleController() {
+        // Construct two states that watch the same Controller. Verify both observers' events are
+        // triggered.
+        Controller<String> controller = new Controller<>();
+        ReactiveRecorder recorder1 = ReactiveRecorder.record(controller);
+        ReactiveRecorder recorder2 = ReactiveRecorder.record(controller);
+        // Activate the controller, which should propagate a state transition to both states.
+        // Both states should be updated, so we should get two enter events.
+        controller.set("neat");
+        controller.reset();
+        recorder1.verify().opened("neat").closed("neat").end();
+        recorder2.verify().opened("neat").closed("neat").end();
+    }
+
+    @Test
+    public void testNewStateIsActivatedImmediatelyIfObservingAlreadyActiveObservable() {
+        Controller<String> controller = new Controller<>();
+        controller.set("surprise");
+        ReactiveRecorder recorder = ReactiveRecorder.record(controller);
+        recorder.verify().opened("surprise").end();
+    }
+
+    @Test
+    public void testNewStateIsNotActivatedIfObservingObservableThatHasBeenDeactivated() {
+        Controller<String> controller = new Controller<>();
+        controller.set("surprise");
+        controller.reset();
+        ReactiveRecorder recorder = ReactiveRecorder.record(controller);
+        recorder.verify().end();
+    }
+
+    @Test
+    public void testResetWhileAlreadyDeactivatedIsANoOp() {
+        Controller<String> controller = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(controller);
+        controller.set("radical");
+        controller.reset();
+        // Resetting again after already resetting should not notify the observer.
+        controller.reset();
+        recorder.verify().opened("radical").closed("radical").end();
+    }
+
+    @Test
+    public void testClosedWatchScopeDoesNotGetNotifiedOfFutureActivations() {
+        Controller<String> a = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(a);
+        a.set("during temp");
+        a.reset();
+        recorder.unsubscribe();
+        a.set("after temp");
+        recorder.verify().opened("during temp").closed("during temp").end();
+    }
+
+    @Test
+    public void testClosedWatchScopeIsImplicitlyDeactivated() {
+        Controller<String> a = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(a);
+        a.set("implicitly reset this");
+        recorder.unsubscribe();
+        recorder.verify().opened("implicitly reset this").closed("implicitly reset this").end();
+    }
+
+    @Test
+    public void testCloseWatchScopeAfterDeactivatingSourceStateDoesNotCallExitHAndlerAgain() {
+        Controller<String> a = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(a);
+        a.set("and a one");
+        a.reset();
+        recorder.unsubscribe();
+        recorder.verify().opened("and a one").closed("and a one").end();
+    }
+
+    @Test
+    public void testSetControllerWithNullImplicitlyResets() {
+        Controller<String> a = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(a);
+        a.set("not null");
+        a.set(null);
+        recorder.verify().opened("not null").closed("not null").end();
+    }
+
+    @Test
+    public void testResetControllerInActivationHandler() {
+        Controller<String> a = new Controller<>();
+        List<String> result = new ArrayList<>();
+        a.watch((String s) -> {
+            result.add("enter " + s);
+            a.reset();
+            result.add("after reset");
+            return () -> {
+                result.add("exit");
+            };
+        });
+        a.set("immediately retracted");
+        assertThat(result, contains("enter immediately retracted", "after reset", "exit"));
+    }
+
+    @Test
+    public void testSetControllerInActivationHandler() {
+        Controller<String> a = new Controller<>();
+        List<String> result = new ArrayList<>();
+        a.watch(report(result, "weirdness"));
+        a.watch((String s) -> {
+            // If the activation handler always calls set() on the source controller, you will have
+            // an infinite loop, which is not cool. However, if the activation handler only
+            // conditionally calls set() on its source controller, then the case where set() is not
+            // called will break the loop. It is the responsibility of the programmer to solve the
+            // halting problem for activation handlers.
+            if (s.equals("first")) {
+                a.set("second");
+            }
+            return () -> {
+                result.add("haha");
+            };
+        });
+        a.set("first");
+        assertThat(result,
+                contains("enter weirdness: first", "haha", "exit weirdness",
+                        "enter weirdness: second"));
+    }
+
+    @Test
+    public void testResetControllerInDeactivationHandler() {
+        Controller<String> a = new Controller<>();
+        List<String> result = new ArrayList<>();
+        a.watch(report(result, "bizzareness"));
+        a.watch((String s) -> () -> a.reset());
+        a.set("yo");
+        a.reset();
+        // The reset() called by the deactivation handler should be a no-op.
+        assertThat(result, contains("enter bizzareness: yo", "exit bizzareness"));
+    }
+
+    @Test
+    public void testSetControllerInDeactivationHandler() {
+        Controller<String> a = new Controller<>();
+        List<String> result = new ArrayList<>();
+        a.watch(report(result, "astoundingness"));
+        a.watch((String s) -> () -> a.set("never mind"));
+        a.set("retract this");
+        a.reset();
+        // The set() called by the deactivation handler should immediately set the controller back.
+        assertThat(result,
+                contains("enter astoundingness: retract this", "exit astoundingness",
+                        "enter astoundingness: never mind"));
+    }
+
+    @Test
+    public void testSetWithDuplicateValueIsNoOp() {
+        Controller<String> controller = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(controller);
+        controller.set("stop copying me");
+        controller.set("stop copying me");
+        recorder.verify().opened("stop copying me").end();
+    }
+
+    @Test
+    public void testSetUnitControllerInActivatedStateIsNoOp() {
+        Controller<Unit> controller = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(controller);
+        controller.set(Unit.unit());
+        recorder.verify().opened(Unit.unit()).end();
+        controller.set(Unit.unit());
+        recorder.verify().end();
+    }
+}
diff --git a/chromecast/base/java/test/org/chromium/chromecast/base/ObservableAndControllerTest.java b/chromecast/base/java/test/org/chromium/chromecast/base/ObservableAndControllerTest.java
deleted file mode 100644
index ad98341..0000000
--- a/chromecast/base/java/test/org/chromium/chromecast/base/ObservableAndControllerTest.java
+++ /dev/null
@@ -1,684 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chromecast.base;
-
-import static org.hamcrest.Matchers.contains;
-import static org.hamcrest.Matchers.emptyIterable;
-import static org.junit.Assert.assertThat;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.BlockJUnit4ClassRunner;
-
-import org.chromium.chromecast.base.Inheritance.Base;
-import org.chromium.chromecast.base.Inheritance.Derived;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Tests for Observable and Controller.
- */
-@RunWith(BlockJUnit4ClassRunner.class)
-public class ObservableAndControllerTest {
-    // Convenience method to create a scope that mutates a list of strings on state transitions.
-    // When entering the state, it will append "enter ${id} ${data}" to the result list, where
-    // `data` is the String that is associated with the state activation. When exiting the state,
-    // it will append "exit ${id}" to the result list. This provides a readable way to track and
-    // verify the behavior of observers in response to the Observables they are linked to.
-    public static <T> Observer<T> report(List<String> result, String id) {
-        // Did you know that lambdas are awesome.
-        return (T data) -> {
-            result.add("enter " + id + ": " + data);
-            return () -> result.add("exit " + id);
-        };
-    }
-
-    @Test
-    public void testNoStateTransitionAfterRegisteringWithInactiveController() {
-        Controller<String> controller = new Controller<>();
-        List<String> result = new ArrayList<>();
-        controller.watch(report(result, "a"));
-        assertThat(result, emptyIterable());
-    }
-
-    @Test
-    public void testStateIsEnteredWhenControllerIsSet() {
-        Controller<String> controller = new Controller<>();
-        List<String> result = new ArrayList<>();
-        controller.watch(report(result, "a"));
-        // Activate the state by setting the controller.
-        controller.set("cool");
-        assertThat(result, contains("enter a: cool"));
-    }
-
-    @Test
-    public void testBasicStateFromController() {
-        Controller<String> controller = new Controller<>();
-        List<String> result = new ArrayList<>();
-        controller.watch(report(result, "a"));
-        controller.set("fun");
-        // Deactivate the state by resetting the controller.
-        controller.reset();
-        assertThat(result, contains("enter a: fun", "exit a"));
-    }
-
-    @Test
-    public void testSetStateTwicePerformsImplicitReset() {
-        Controller<String> controller = new Controller<>();
-        List<String> result = new ArrayList<>();
-        controller.watch(report(result, "a"));
-        // Activate the state for the first time.
-        controller.set("first");
-        // Activate the state for the second time.
-        controller.set("second");
-        // If set() is called without a reset() in-between, the tracking state exits, then re-enters
-        // with the new data. So we expect to find an "exit" call between the two enter calls.
-        assertThat(result, contains("enter a: first", "exit a", "enter a: second"));
-    }
-
-    @Test
-    public void testResetWhileStateIsNotEnteredIsNoOp() {
-        Controller<String> controller = new Controller<>();
-        List<String> result = new ArrayList<>();
-        controller.watch(report(result, "a"));
-        controller.reset();
-        assertThat(result, emptyIterable());
-    }
-
-    @Test
-    public void testMultipleStatesObservingSingleController() {
-        // Construct two states that watch the same Controller. Verify both observers' events are
-        // triggered.
-        Controller<String> controller = new Controller<>();
-        List<String> result = new ArrayList<>();
-        controller.watch(report(result, "a"));
-        controller.watch(report(result, "b"));
-        // Activate the controller, which should propagate a state transition to both states.
-        // Both states should be updated, so we should get two enter events.
-        controller.set("neat");
-        controller.reset();
-        assertThat(result, contains("enter a: neat", "enter b: neat", "exit b", "exit a"));
-    }
-
-    @Test
-    public void testNewStateIsActivatedImmediatelyIfObservingAlreadyActiveObservable() {
-        Controller<String> controller = new Controller<>();
-        List<String> result = new ArrayList<>();
-        controller.set("surprise");
-        controller.watch(report(result, "a"));
-        assertThat(result, contains("enter a: surprise"));
-    }
-
-    @Test
-    public void testNewStateIsNotActivatedIfObservingObservableThatHasBeenDeactivated() {
-        Controller<String> controller = new Controller<>();
-        List<String> result = new ArrayList<>();
-        controller.set("surprise");
-        controller.reset();
-        controller.watch(report(result, "a"));
-        assertThat(result, emptyIterable());
-    }
-
-    @Test
-    public void testResetWhileAlreadyDeactivatedIsANoOp() {
-        Controller<String> controller = new Controller<>();
-        List<String> result = new ArrayList<>();
-        controller.watch(report(result, "a"));
-        controller.set("radical");
-        controller.reset();
-        // Resetting again after already resetting should not notify the observer.
-        controller.reset();
-        assertThat(result, contains("enter a: radical", "exit a"));
-    }
-
-    @Test
-    public void testClosedWatchScopeDoesNotGetNotifiedOfFutureActivations() {
-        Controller<String> a = new Controller<>();
-        List<String> result = new ArrayList<>();
-        Scope watching = a.watch(report(result, "temp"));
-        a.set("during temp");
-        a.reset();
-        watching.close();
-        a.set("after temp");
-        assertThat(result, contains("enter temp: during temp", "exit temp"));
-    }
-
-    @Test
-    public void testClosedWatchScopeIsImplicitlyDeactivated() {
-        Controller<String> a = new Controller<>();
-        List<String> result = new ArrayList<>();
-        Scope watching = a.watch(report(result, "temp"));
-        a.set("implicitly reset this");
-        watching.close();
-        assertThat(result, contains("enter temp: implicitly reset this", "exit temp"));
-    }
-
-    @Test
-    public void testCloseWatchScopeAfterDeactivatingSourceStateDoesNotCallExitHAndlerAgain() {
-        Controller<String> a = new Controller<>();
-        List<String> result = new ArrayList<>();
-        Scope watching = a.watch(report(result, "temp"));
-        a.set("and a one");
-        a.reset();
-        watching.close();
-        assertThat(result, contains("enter temp: and a one", "exit temp"));
-    }
-
-    @Test
-    public void testBothState_activateFirstDoesNotTrigger() {
-        Controller<String> a = new Controller<>();
-        Controller<String> b = new Controller<>();
-        List<String> result = new ArrayList<>();
-        a.and(b).watch(report(result, "both"));
-        a.set("A");
-        assertThat(result, emptyIterable());
-    }
-
-    @Test
-    public void testBothState_activateSecondDoesNotTrigger() {
-        Controller<String> a = new Controller<>();
-        Controller<String> b = new Controller<>();
-        List<String> result = new ArrayList<>();
-        a.and(b).watch(report(result, "both"));
-        b.set("B");
-        assertThat(result, emptyIterable());
-    }
-
-    @Test
-    public void testBothState_activateBothTriggers() {
-        Controller<String> a = new Controller<>();
-        Controller<String> b = new Controller<>();
-        List<String> result = new ArrayList<>();
-        a.and(b).watch(report(result, "both"));
-        a.set("A");
-        b.set("B");
-        assertThat(result, contains("enter both: A, B"));
-    }
-
-    @Test
-    public void testBothState_deactivateFirstAfterTrigger() {
-        Controller<String> a = new Controller<>();
-        Controller<String> b = new Controller<>();
-        List<String> result = new ArrayList<>();
-        a.and(b).watch(report(result, "both"));
-        a.set("A");
-        b.set("B");
-        a.reset();
-        assertThat(result, contains("enter both: A, B", "exit both"));
-    }
-
-    @Test
-    public void testBothState_deactivateSecondAfterTrigger() {
-        Controller<String> a = new Controller<>();
-        Controller<String> b = new Controller<>();
-        List<String> result = new ArrayList<>();
-        a.and(b).watch(report(result, "both"));
-        a.set("A");
-        b.set("B");
-        b.reset();
-        assertThat(result, contains("enter both: A, B", "exit both"));
-    }
-
-    @Test
-    public void testBothState_resetFirstBeforeSettingSecond_doesNotTrigger() {
-        Controller<String> a = new Controller<>();
-        Controller<String> b = new Controller<>();
-        List<String> result = new ArrayList<>();
-        a.and(b).watch(report(result, "both"));
-        a.set("A");
-        a.reset();
-        b.set("B");
-        assertThat(result, emptyIterable());
-    }
-
-    @Test
-    public void testBothState_resetSecondBeforeSettingFirst_doesNotTrigger() {
-        Controller<String> a = new Controller<>();
-        Controller<String> b = new Controller<>();
-        List<String> result = new ArrayList<>();
-        a.and(b).watch(report(result, "both"));
-        b.set("B");
-        b.reset();
-        a.set("A");
-        assertThat(result, emptyIterable());
-    }
-
-    @Test
-    public void testBothState_setOneControllerAfterTrigger_implicitlyResetsAndSets() {
-        Controller<String> a = new Controller<>();
-        Controller<String> b = new Controller<>();
-        List<String> result = new ArrayList<>();
-        a.and(b).watch(report(result, "both"));
-        a.set("A1");
-        b.set("B1");
-        a.set("A2");
-        b.set("B2");
-        assertThat(result,
-                contains("enter both: A1, B1", "exit both", "enter both: A2, B1", "exit both",
-                        "enter both: A2, B2"));
-    }
-
-    @Test
-    public void testComposeBoth() {
-        Controller<String> a = new Controller<>();
-        Controller<String> b = new Controller<>();
-        Controller<String> c = new Controller<>();
-        Controller<String> d = new Controller<>();
-        List<String> result = new ArrayList<>();
-        a.and(b).and(c).and(d).watch(report(result, "all four"));
-        a.set("A");
-        b.set("B");
-        c.set("C");
-        d.set("D");
-        a.reset();
-        assertThat(result, contains("enter all four: A, B, C, D", "exit all four"));
-    }
-
-    @Test
-    public void testMap() {
-        Controller<String> original = new Controller<>();
-        Observable<String> lowerCase = original.map(String::toLowerCase);
-        Observable<String> upperCase = lowerCase.map(String::toUpperCase);
-        List<String> result = new ArrayList<>();
-        original.watch(report(result, "unchanged"));
-        lowerCase.watch(report(result, "lower"));
-        upperCase.watch(report(result, "upper"));
-        original.set("sImPlY sTeAmEd KaLe");
-        original.reset();
-        // Note: order of activation doesn't really matter, but deactivation should be in reverse
-        // order or activation.
-        assertThat(result,
-                contains("enter upper: SIMPLY STEAMED KALE", "enter lower: simply steamed kale",
-                        "enter unchanged: sImPlY sTeAmEd KaLe", "exit unchanged", "exit lower",
-                        "exit upper"));
-    }
-
-    @Test
-    public void testMapDropsNullResult() {
-        Controller<Unit> controller = new Controller<>();
-        ReactiveRecorder recorder = ReactiveRecorder.record(controller.map(x -> null));
-        controller.set(Unit.unit());
-        // Recorder should not get any events because the map function returned null.
-        recorder.verify().end();
-    }
-
-    @Test
-    public void testFilter() {
-        Controller<String> a = new Controller<>();
-        List<String> result = new ArrayList<>();
-        a.filter(String::isEmpty).watch(report(result, "empty"));
-        a.filter(s -> s.startsWith("a")).watch(report(result, "starts with a"));
-        a.filter(s -> s.endsWith("a")).watch(report(result, "ends with a"));
-        a.set("");
-        a.set("none");
-        a.set("add");
-        a.set("doa");
-        a.set("ada");
-        assertThat(result,
-                contains("enter empty: ", "exit empty", "enter starts with a: add",
-                        "exit starts with a", "enter ends with a: doa", "exit ends with a",
-                        "enter starts with a: ada", "enter ends with a: ada"));
-    }
-
-    @Test
-    public void testSetControllerWithNullImplicitlyResets() {
-        Controller<String> a = new Controller<>();
-        List<String> result = new ArrayList<>();
-        a.watch(report(result, "controller"));
-        a.set("not null");
-        a.set(null);
-        assertThat(result, contains("enter controller: not null", "exit controller"));
-    }
-
-    @Test
-    public void testResetControllerInActivationHandler() {
-        Controller<String> a = new Controller<>();
-        List<String> result = new ArrayList<>();
-        a.watch((String s) -> {
-            result.add("enter " + s);
-            a.reset();
-            result.add("after reset");
-            return () -> {
-                result.add("exit");
-            };
-        });
-        a.set("immediately retracted");
-        assertThat(result, contains("enter immediately retracted", "after reset", "exit"));
-    }
-
-    @Test
-    public void testSetControllerInActivationHandler() {
-        Controller<String> a = new Controller<>();
-        List<String> result = new ArrayList<>();
-        a.watch(report(result, "weirdness"));
-        a.watch((String s) -> {
-            // If the activation handler always calls set() on the source controller, you will have
-            // an infinite loop, which is not cool. However, if the activation handler only
-            // conditionally calls set() on its source controller, then the case where set() is not
-            // called will break the loop. It is the responsibility of the programmer to solve the
-            // halting problem for activation handlers.
-            if (s.equals("first")) {
-                a.set("second");
-            }
-            return () -> {
-                result.add("haha");
-            };
-        });
-        a.set("first");
-        assertThat(result,
-                contains("enter weirdness: first", "haha", "exit weirdness",
-                        "enter weirdness: second"));
-    }
-
-    @Test
-    public void testResetControllerInDeactivationHandler() {
-        Controller<String> a = new Controller<>();
-        List<String> result = new ArrayList<>();
-        a.watch(report(result, "bizzareness"));
-        a.watch((String s) -> () -> a.reset());
-        a.set("yo");
-        a.reset();
-        // The reset() called by the deactivation handler should be a no-op.
-        assertThat(result, contains("enter bizzareness: yo", "exit bizzareness"));
-    }
-
-    @Test
-    public void testSetControllerInDeactivationHandler() {
-        Controller<String> a = new Controller<>();
-        List<String> result = new ArrayList<>();
-        a.watch(report(result, "astoundingness"));
-        a.watch((String s) -> () -> a.set("never mind"));
-        a.set("retract this");
-        a.reset();
-        // The set() called by the deactivation handler should immediately set the controller back.
-        assertThat(result,
-                contains("enter astoundingness: retract this", "exit astoundingness",
-                        "enter astoundingness: never mind"));
-    }
-
-    @Test
-    public void testBeingTooCleverWithObserversAndInheritance() {
-        Controller<Base> baseController = new Controller<>();
-        Controller<Derived> derivedController = new Controller<>();
-        List<String> result = new ArrayList<>();
-        // Test that the same Observer object can observe Observables of different types, as
-        // long as the Observer type is a superclass of both Observable types.
-        Observer<Base> observer = (Base value) -> {
-            result.add("enter: " + value.toString());
-            return () -> result.add("exit: " + value.toString());
-        };
-        baseController.watch(observer);
-        // Compile error if generics are wrong.
-        derivedController.watch(observer);
-        baseController.set(new Base());
-        // The scope from the previous set() call will not be overridden because this is activating
-        // a different Controller.
-        derivedController.set(new Derived());
-        // The Controller<Base> can be activated with an object that extends Base.
-        baseController.set(new Derived());
-        assertThat(
-                result, contains("enter: Base", "enter: Derived", "exit: Base", "enter: Derived"));
-    }
-
-    @Test
-    public void testNotIsActivatedAtTheStart() {
-        Controller<String> invertThis = new Controller<>();
-        List<String> result = new ArrayList<>();
-        Observable.not(invertThis).watch(x -> {
-            result.add("enter inverted");
-            return () -> result.add("exit inverted");
-        });
-        assertThat(result, contains("enter inverted"));
-    }
-
-    @Test
-    public void testNotIsDeactivatedAtTheStartIfSourceIsAlreadyActivated() {
-        Controller<String> invertThis = new Controller<>();
-        List<String> result = new ArrayList<>();
-        invertThis.set("way ahead of you");
-        Observable.not(invertThis).watch(x -> {
-            result.add("enter inverted");
-            return () -> result.add("exit inverted");
-        });
-        assertThat(result, emptyIterable());
-    }
-
-    @Test
-    public void testNotExitsWhenSourceIsActivated() {
-        Controller<String> invertThis = new Controller<>();
-        List<String> result = new ArrayList<>();
-        Observable.not(invertThis).watch(x -> {
-            result.add("enter inverted");
-            return () -> result.add("exit inverted");
-        });
-        invertThis.set("first");
-        assertThat(result, contains("enter inverted", "exit inverted"));
-    }
-
-    @Test
-    public void testNotReentersWhenSourceIsReset() {
-        Controller<String> invertThis = new Controller<>();
-        List<String> result = new ArrayList<>();
-        Observable.not(invertThis).watch(x -> {
-            result.add("enter inverted");
-            return () -> result.add("exit inverted");
-        });
-        invertThis.set("first");
-        invertThis.reset();
-        assertThat(result, contains("enter inverted", "exit inverted", "enter inverted"));
-    }
-
-    @Test
-    public void testAndThenNotActivatedInitially() {
-        Controller<String> aState = new Controller<>();
-        Controller<String> bState = new Controller<>();
-        List<String> result = new ArrayList<>();
-        aState.andThen(bState).watch(
-                Observers.onEnter((String a, String b) -> { result.add("a=" + a + ", b=" + b); }));
-        assertThat(result, emptyIterable());
-    }
-
-    @Test
-    public void testAndThenNotActivatedIfSecondBeforeFirst() {
-        Controller<String> aState = new Controller<>();
-        Controller<String> bState = new Controller<>();
-        List<String> result = new ArrayList<>();
-        aState.andThen(bState).watch(
-                Observers.onEnter((String a, String b) -> { result.add("a=" + a + ", b=" + b); }));
-        bState.set("b");
-        aState.set("a");
-        assertThat(result, emptyIterable());
-    }
-
-    @Test
-    public void testAndThenActivatedIfFirstThenSecond() {
-        Controller<String> aState = new Controller<>();
-        Controller<String> bState = new Controller<>();
-        List<String> result = new ArrayList<>();
-        aState.andThen(bState).watch(
-                Observers.onEnter((String a, String b) -> { result.add("a=" + a + ", b=" + b); }));
-        aState.set("a");
-        bState.set("b");
-        assertThat(result, contains("a=a, b=b"));
-    }
-
-    @Test
-    public void testAndThenActivated_plusBplusAminusBplusB() {
-        Controller<String> aState = new Controller<>();
-        Controller<String> bState = new Controller<>();
-        List<String> result = new ArrayList<>();
-        aState.andThen(bState).watch(
-                Observers.onEnter((String a, String b) -> { result.add("a=" + a + ", b=" + b); }));
-        bState.set("b");
-        aState.set("a");
-        bState.reset();
-        bState.set("B");
-        assertThat(result, contains("a=a, b=B"));
-    }
-
-    @Test
-    public void testAndThenDeactivated_plusAplusBminusA() {
-        Controller<String> aState = new Controller<>();
-        Controller<String> bState = new Controller<>();
-        List<String> result = new ArrayList<>();
-        aState.andThen(bState).watch(
-                Observers.onExit((String a, String b) -> { result.add("a=" + a + ", b=" + b); }));
-        aState.set("A");
-        bState.set("B");
-        aState.reset();
-        assertThat(result, contains("a=A, b=B"));
-    }
-
-    @Test
-    public void testAndThenDeactivated_plusAplusBminusB() {
-        Controller<String> aState = new Controller<>();
-        Controller<String> bState = new Controller<>();
-        List<String> result = new ArrayList<>();
-        aState.andThen(bState).watch(
-                Observers.onExit((String a, String b) -> { result.add("a=" + a + ", b=" + b); }));
-        aState.set("A");
-        bState.set("B");
-        bState.reset();
-        assertThat(result, contains("a=A, b=B"));
-    }
-
-    @Test
-    public void testComposeAndThen() {
-        Controller<Unit> aState = new Controller<>();
-        Controller<Unit> bState = new Controller<>();
-        Controller<Unit> cState = new Controller<>();
-        Controller<Unit> dState = new Controller<>();
-        Observable<Both<Unit, Unit>> aThenB = aState.andThen(bState);
-        Observable<Both<Both<Unit, Unit>, Unit>> aThenBThenC = aThenB.andThen(cState);
-        Observable<Both<Both<Both<Unit, Unit>, Unit>, Unit>> aThenBThenCThenD =
-                aThenBThenC.andThen(dState);
-        List<String> result = new ArrayList<>();
-        aState.watch(Observers.onEnter(() -> result.add("A")));
-        aThenB.watch(Observers.onEnter(() -> result.add("B")));
-        aThenBThenC.watch(Observers.onEnter(() -> result.add("C")));
-        aThenBThenCThenD.watch(Observers.onEnter(() -> result.add("D")));
-        aState.set(Unit.unit());
-        bState.set(Unit.unit());
-        cState.set(Unit.unit());
-        dState.set(Unit.unit());
-        aState.reset();
-        assertThat(result, contains("A", "B", "C", "D"));
-    }
-
-    @Test
-    public void testUseWatchScopeAsObserver() {
-        Controller<String> aState = new Controller<>();
-        Controller<String> bState = new Controller<>();
-        List<String> result = new ArrayList<>();
-        aState.watch(report(result, "a"));
-        bState.watch(report(result, "b"));
-        // I guess this makes .and() obsolete?
-        aState.watch(a -> bState.watch(b -> {
-            result.add("enter both: " + a + ", " + b);
-            return () -> result.add("exit both");
-        }));
-        aState.set("A");
-        bState.set("B");
-        assertThat(result, contains("enter a: A", "enter b: B", "enter both: A, B"));
-        result.clear();
-        aState.reset();
-        assertThat(result, contains("exit both", "exit a"));
-        result.clear();
-        aState.set("AA");
-        assertThat(result, contains("enter a: AA", "enter both: AA, B"));
-        result.clear();
-        bState.reset();
-        assertThat(result, contains("exit both", "exit b"));
-    }
-
-    @Test
-    public void testPowerUnlimitedPower() {
-        Controller<Unit> aState = new Controller<>();
-        Controller<Unit> bState = new Controller<>();
-        Controller<Unit> cState = new Controller<>();
-        Controller<Unit> dState = new Controller<>();
-        List<String> result = new ArrayList<>();
-        // Praise be to Haskell Curry.
-        aState.watch(a -> bState.watch(b -> cState.watch(c -> dState.watch(d -> {
-            result.add("it worked!");
-            return () -> result.add("exit");
-        }))));
-        aState.set(Unit.unit());
-        bState.set(Unit.unit());
-        cState.set(Unit.unit());
-        dState.set(Unit.unit());
-        assertThat(result, contains("it worked!"));
-        result.clear();
-        aState.reset();
-        assertThat(result, contains("exit"));
-        result.clear();
-        aState.set(Unit.unit());
-        assertThat(result, contains("it worked!"));
-        result.clear();
-        bState.reset();
-        assertThat(result, contains("exit"));
-        result.clear();
-        bState.set(Unit.unit());
-        assertThat(result, contains("it worked!"));
-        result.clear();
-        cState.reset();
-        assertThat(result, contains("exit"));
-        result.clear();
-        cState.set(Unit.unit());
-        assertThat(result, contains("it worked!"));
-        result.clear();
-        dState.reset();
-        assertThat(result, contains("exit"));
-        result.clear();
-        dState.set(Unit.unit());
-        assertThat(result, contains("it worked!"));
-    }
-
-    @Test
-    public void testSetWithDuplicateValueIsNoOp() {
-        Controller<String> controller = new Controller<>();
-        ReactiveRecorder recorder = ReactiveRecorder.record(controller);
-        controller.set("stop copying me");
-        controller.set("stop copying me");
-        recorder.verify().entered("stop copying me").end();
-    }
-
-    @Test
-    public void testSetUnitControllerInActivatedStateIsNoOp() {
-        Controller<Unit> controller = new Controller<>();
-        ReactiveRecorder recorder = ReactiveRecorder.record(controller);
-        controller.set(Unit.unit());
-        recorder.verify().entered(Unit.unit()).end();
-        controller.set(Unit.unit());
-        recorder.verify().end();
-    }
-
-    // Any Scope's constructor whose parameters match the scope can be used as a method reference.
-    private static class TransitionLogger implements Scope {
-        public static final List<String> sResult = new ArrayList<>();
-        private final String mData;
-
-        public TransitionLogger(String data) {
-            mData = data;
-            sResult.add("enter: " + mData);
-        }
-
-        @Override
-        public void close() {
-            sResult.add("exit: " + mData);
-        }
-    }
-
-    @Test
-    public void testObserverWithAutoCloseableConstructor() {
-        Controller<String> controller = new Controller<>();
-        // You can use a constructor method reference in a watch() call.
-        controller.watch(TransitionLogger::new);
-        controller.set("a");
-        controller.reset();
-        assertThat(TransitionLogger.sResult, contains("enter: a", "exit: a"));
-    }
-}
diff --git a/chromecast/base/java/test/org/chromium/chromecast/base/ObservableAndTest.java b/chromecast/base/java/test/org/chromium/chromecast/base/ObservableAndTest.java
new file mode 100644
index 0000000..ca46622
--- /dev/null
+++ b/chromecast/base/java/test/org/chromium/chromecast/base/ObservableAndTest.java
@@ -0,0 +1,123 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chromecast.base;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+
+/**
+ * Tests for Observable#and().
+ */
+@RunWith(BlockJUnit4ClassRunner.class)
+public class ObservableAndTest {
+    @Test
+    public void testBothState_activateFirstDoesNotTrigger() {
+        Controller<String> a = new Controller<>();
+        Controller<String> b = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(a.and(b));
+        a.set("A");
+        recorder.verify().end();
+    }
+
+    @Test
+    public void testBothState_activateSecondDoesNotTrigger() {
+        Controller<String> a = new Controller<>();
+        Controller<String> b = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(a.and(b));
+        b.set("B");
+        recorder.verify().end();
+    }
+
+    @Test
+    public void testBothState_activateBothTriggers() {
+        Controller<String> a = new Controller<>();
+        Controller<String> b = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(a.and(b));
+        a.set("A");
+        b.set("B");
+        recorder.verify().opened(Both.both("A", "B")).end();
+    }
+
+    @Test
+    public void testBothState_deactivateFirstAfterTrigger() {
+        Controller<String> a = new Controller<>();
+        Controller<String> b = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(a.and(b));
+        a.set("A");
+        b.set("B");
+        a.reset();
+        recorder.verify().opened(Both.both("A", "B")).closed(Both.both("A", "B")).end();
+    }
+
+    @Test
+    public void testBothState_deactivateSecondAfterTrigger() {
+        Controller<String> a = new Controller<>();
+        Controller<String> b = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(a.and(b));
+        a.set("A");
+        b.set("B");
+        b.reset();
+        recorder.verify().opened(Both.both("A", "B")).closed(Both.both("A", "B")).end();
+    }
+
+    @Test
+    public void testBothState_resetFirstBeforeSettingSecond_doesNotTrigger() {
+        Controller<String> a = new Controller<>();
+        Controller<String> b = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(a.and(b));
+        a.set("A");
+        a.reset();
+        b.set("B");
+        recorder.verify().end();
+    }
+
+    @Test
+    public void testBothState_resetSecondBeforeSettingFirst_doesNotTrigger() {
+        Controller<String> a = new Controller<>();
+        Controller<String> b = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(a.and(b));
+        b.set("B");
+        b.reset();
+        a.set("A");
+        recorder.verify().end();
+    }
+
+    @Test
+    public void testBothState_setOneControllerAfterTrigger_implicitlyResetsAndSets() {
+        Controller<String> a = new Controller<>();
+        Controller<String> b = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(a.and(b));
+        a.set("A1");
+        b.set("B1");
+        a.set("A2");
+        b.set("B2");
+        recorder.verify()
+                .opened(Both.both("A1", "B1"))
+                .closed(Both.both("A1", "B1"))
+                .opened(Both.both("A2", "B1"))
+                .closed(Both.both("A2", "B1"))
+                .opened(Both.both("A2", "B2"))
+                .end();
+    }
+
+    @Test
+    public void testComposeBoth() {
+        Controller<String> a = new Controller<>();
+        Controller<String> b = new Controller<>();
+        Controller<String> c = new Controller<>();
+        Controller<String> d = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(a.and(b).and(c).and(d));
+        a.set("a");
+        b.set("b");
+        c.set("c");
+        d.set("d");
+        a.reset();
+        recorder.verify()
+                .opened(Both.both(Both.both(Both.both("a", "b"), "c"), "d"))
+                .closed(Both.both(Both.both(Both.both("a", "b"), "c"), "d"))
+                .end();
+    }
+}
diff --git a/chromecast/base/java/test/org/chromium/chromecast/base/ObservableAndThenTest.java b/chromecast/base/java/test/org/chromium/chromecast/base/ObservableAndThenTest.java
new file mode 100644
index 0000000..ea95df3
--- /dev/null
+++ b/chromecast/base/java/test/org/chromium/chromecast/base/ObservableAndThenTest.java
@@ -0,0 +1,119 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chromecast.base;
+
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.emptyIterable;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for Observable#andThen().
+ */
+@RunWith(BlockJUnit4ClassRunner.class)
+public class ObservableAndThenTest {
+    @Test
+    public void testAndThenNotActivatedInitially() {
+        Controller<String> aState = new Controller<>();
+        Controller<String> bState = new Controller<>();
+        List<String> result = new ArrayList<>();
+        aState.andThen(bState).watch(
+                Observers.onEnter((String a, String b) -> { result.add("a=" + a + ", b=" + b); }));
+        assertThat(result, emptyIterable());
+    }
+
+    @Test
+    public void testAndThenNotActivatedIfSecondBeforeFirst() {
+        Controller<String> aState = new Controller<>();
+        Controller<String> bState = new Controller<>();
+        List<String> result = new ArrayList<>();
+        aState.andThen(bState).watch(
+                Observers.onEnter((String a, String b) -> { result.add("a=" + a + ", b=" + b); }));
+        bState.set("b");
+        aState.set("a");
+        assertThat(result, emptyIterable());
+    }
+
+    @Test
+    public void testAndThenActivatedIfFirstThenSecond() {
+        Controller<String> aState = new Controller<>();
+        Controller<String> bState = new Controller<>();
+        List<String> result = new ArrayList<>();
+        aState.andThen(bState).watch(
+                Observers.onEnter((String a, String b) -> { result.add("a=" + a + ", b=" + b); }));
+        aState.set("a");
+        bState.set("b");
+        assertThat(result, contains("a=a, b=b"));
+    }
+
+    @Test
+    public void testAndThenActivated_plusBplusAminusBplusB() {
+        Controller<String> aState = new Controller<>();
+        Controller<String> bState = new Controller<>();
+        List<String> result = new ArrayList<>();
+        aState.andThen(bState).watch(
+                Observers.onEnter((String a, String b) -> { result.add("a=" + a + ", b=" + b); }));
+        bState.set("b");
+        aState.set("a");
+        bState.reset();
+        bState.set("B");
+        assertThat(result, contains("a=a, b=B"));
+    }
+
+    @Test
+    public void testAndThenDeactivated_plusAplusBminusA() {
+        Controller<String> aState = new Controller<>();
+        Controller<String> bState = new Controller<>();
+        List<String> result = new ArrayList<>();
+        aState.andThen(bState).watch(
+                Observers.onExit((String a, String b) -> { result.add("a=" + a + ", b=" + b); }));
+        aState.set("A");
+        bState.set("B");
+        aState.reset();
+        assertThat(result, contains("a=A, b=B"));
+    }
+
+    @Test
+    public void testAndThenDeactivated_plusAplusBminusB() {
+        Controller<String> aState = new Controller<>();
+        Controller<String> bState = new Controller<>();
+        List<String> result = new ArrayList<>();
+        aState.andThen(bState).watch(
+                Observers.onExit((String a, String b) -> { result.add("a=" + a + ", b=" + b); }));
+        aState.set("A");
+        bState.set("B");
+        bState.reset();
+        assertThat(result, contains("a=A, b=B"));
+    }
+
+    @Test
+    public void testComposeAndThen() {
+        Controller<Unit> aState = new Controller<>();
+        Controller<Unit> bState = new Controller<>();
+        Controller<Unit> cState = new Controller<>();
+        Controller<Unit> dState = new Controller<>();
+        Observable<Both<Unit, Unit>> aThenB = aState.andThen(bState);
+        Observable<Both<Both<Unit, Unit>, Unit>> aThenBThenC = aThenB.andThen(cState);
+        Observable<Both<Both<Both<Unit, Unit>, Unit>, Unit>> aThenBThenCThenD =
+                aThenBThenC.andThen(dState);
+        List<String> result = new ArrayList<>();
+        aState.watch(Observers.onEnter(() -> result.add("A")));
+        aThenB.watch(Observers.onEnter(() -> result.add("B")));
+        aThenBThenC.watch(Observers.onEnter(() -> result.add("C")));
+        aThenBThenCThenD.watch(Observers.onEnter(() -> result.add("D")));
+        aState.set(Unit.unit());
+        bState.set(Unit.unit());
+        cState.set(Unit.unit());
+        dState.set(Unit.unit());
+        aState.reset();
+        assertThat(result, contains("A", "B", "C", "D"));
+    }
+}
diff --git a/chromecast/base/java/test/org/chromium/chromecast/base/ObservableFilterTest.java b/chromecast/base/java/test/org/chromium/chromecast/base/ObservableFilterTest.java
new file mode 100644
index 0000000..1ccd3a9
--- /dev/null
+++ b/chromecast/base/java/test/org/chromium/chromecast/base/ObservableFilterTest.java
@@ -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.
+
+package org.chromium.chromecast.base;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+
+/**
+ * Tests for Observable#filter().
+ */
+@RunWith(BlockJUnit4ClassRunner.class)
+public class ObservableFilterTest {
+    @Test
+    public void testFilter() {
+        Controller<String> a = new Controller<>();
+        ReactiveRecorder empty = ReactiveRecorder.record(a.filter(String::isEmpty));
+        ReactiveRecorder startsWithA = ReactiveRecorder.record(a.filter(s -> s.startsWith("a")));
+        ReactiveRecorder endsWithA = ReactiveRecorder.record(a.filter(s -> s.endsWith("a")));
+
+        a.set("");
+        empty.verify().opened("").end();
+        startsWithA.verify().end();
+        endsWithA.verify().end();
+
+        a.reset();
+        empty.verify().closed("").end();
+        startsWithA.verify().end();
+        endsWithA.verify().end();
+
+        a.set("a");
+        empty.verify().end();
+        startsWithA.verify().opened("a").end();
+        endsWithA.verify().opened("a").end();
+
+        a.set("doa");
+        empty.verify().end();
+        startsWithA.verify().closed("a").end();
+        endsWithA.verify().closed("a").opened("doa").end();
+
+        a.set("ada");
+        empty.verify().end();
+        startsWithA.verify().opened("ada").end();
+        endsWithA.verify().closed("doa").opened("ada").end();
+    }
+}
diff --git a/chromecast/base/java/test/org/chromium/chromecast/base/ObservableMapTest.java b/chromecast/base/java/test/org/chromium/chromecast/base/ObservableMapTest.java
new file mode 100644
index 0000000..e47c665
--- /dev/null
+++ b/chromecast/base/java/test/org/chromium/chromecast/base/ObservableMapTest.java
@@ -0,0 +1,64 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chromecast.base;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+
+import org.chromium.chromecast.base.Inheritance.Base;
+import org.chromium.chromecast.base.Inheritance.Derived;
+
+/**
+ * Tests for Observable#map().
+ */
+@RunWith(BlockJUnit4ClassRunner.class)
+public class ObservableMapTest {
+    @Test
+    public void testMapController() {
+        Controller<String> original = new Controller<>();
+        Observable<String> lowerCase = original.map(String::toLowerCase);
+        Observable<String> upperCase = lowerCase.map(String::toUpperCase);
+        ReactiveRecorder recordOriginal = ReactiveRecorder.record(original);
+        ReactiveRecorder recordLowerCase = ReactiveRecorder.record(lowerCase);
+        ReactiveRecorder recordUpperCase = ReactiveRecorder.record(upperCase);
+        original.set("sImPlY sTeAmEd KaLe");
+        original.reset();
+        recordOriginal.verify().opened("sImPlY sTeAmEd KaLe").closed("sImPlY sTeAmEd KaLe").end();
+        recordLowerCase.verify().opened("simply steamed kale").closed("simply steamed kale").end();
+        recordUpperCase.verify().opened("SIMPLY STEAMED KALE").closed("SIMPLY STEAMED KALE").end();
+    }
+
+    @Test
+    public void testMapWithFunctionOfSuper() {
+        Controller<Derived> a = new Controller<>();
+        // Compile error if generics are wrong.
+        Observable<String> r = a.map((Base base) -> base.toString());
+        ReactiveRecorder recorder = ReactiveRecorder.record(r);
+        a.set(new Derived());
+        recorder.verify().opened("Derived").end();
+    }
+
+    @Test
+    public void testMapReturnSubclassOfResultType() {
+        Controller<Unit> a = new Controller<>();
+        Derived d = new Derived();
+        Function<Unit, Derived> f = x -> d;
+        // Compile error if generics are wrong.
+        Observable<Base> r = a.map(f);
+        ReactiveRecorder recorder = ReactiveRecorder.record(r);
+        a.set(Unit.unit());
+        recorder.verify().opened(d).end();
+    }
+
+    @Test
+    public void testMapDropsNullResult() {
+        Controller<Unit> controller = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(controller.map(x -> null));
+        controller.set(Unit.unit());
+        // Recorder should not get any events because the map function returned null.
+        recorder.verify().end();
+    }
+}
diff --git a/chromecast/base/java/test/org/chromium/chromecast/base/ObservableMiscellaneousTest.java b/chromecast/base/java/test/org/chromium/chromecast/base/ObservableMiscellaneousTest.java
new file mode 100644
index 0000000..7b059fc
--- /dev/null
+++ b/chromecast/base/java/test/org/chromium/chromecast/base/ObservableMiscellaneousTest.java
@@ -0,0 +1,142 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chromecast.base;
+
+import static org.hamcrest.Matchers.contains;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+
+import org.chromium.chromecast.base.Inheritance.Base;
+import org.chromium.chromecast.base.Inheritance.Derived;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Miscellaneous tests for Observable.
+ *
+ * This includes advanced behaviors like watch-currying and correct use of generics.
+ */
+@RunWith(BlockJUnit4ClassRunner.class)
+public class ObservableMiscellaneousTest {
+    @Test
+    public void testBeingTooCleverWithObserversAndInheritance() {
+        Controller<Base> baseController = new Controller<>();
+        Controller<Derived> derivedController = new Controller<>();
+        List<String> result = new ArrayList<>();
+        // Test that the same Observer object can observe Observables of different types, as
+        // long as the Observer type is a superclass of both Observable types.
+        Observer<Base> observer = (Base value) -> {
+            result.add("enter: " + value.toString());
+            return () -> result.add("exit: " + value.toString());
+        };
+        baseController.watch(observer);
+        // Compile error if generics are wrong.
+        derivedController.watch(observer);
+        baseController.set(new Base());
+        // The scope from the previous set() call will not be overridden because this is activating
+        // a different Controller.
+        derivedController.set(new Derived());
+        // The Controller<Base> can be activated with an object that extends Base.
+        baseController.set(new Derived());
+        assertThat(
+                result, contains("enter: Base", "enter: Derived", "exit: Base", "enter: Derived"));
+    }
+
+    @Test
+    public void testWatchCurrying() {
+        Controller<String> aState = new Controller<>();
+        Controller<String> bState = new Controller<>();
+        Controller<String> result = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(result);
+        // I guess this makes .and() obsolete?
+        aState.watch(a -> bState.watch(b -> {
+            result.set("" + a + ", " + b);
+            return () -> result.reset();
+        }));
+        aState.set("A");
+        bState.set("B");
+        recorder.verify().opened("A, B").end();
+        aState.reset();
+        recorder.verify().closed("A, B").end();
+        aState.set("AA");
+        recorder.verify().opened("AA, B").end();
+        bState.reset();
+        recorder.verify().closed("AA, B").end();
+    }
+
+    @Test
+    public void testPowerUnlimitedPower() {
+        Controller<Unit> aState = new Controller<>();
+        Controller<Unit> bState = new Controller<>();
+        Controller<Unit> cState = new Controller<>();
+        Controller<Unit> dState = new Controller<>();
+        List<String> result = new ArrayList<>();
+        // Praise be to Haskell Curry.
+        aState.watch(a -> bState.watch(b -> cState.watch(c -> dState.watch(d -> {
+            result.add("it worked!");
+            return () -> result.add("exit");
+        }))));
+        aState.set(Unit.unit());
+        bState.set(Unit.unit());
+        cState.set(Unit.unit());
+        dState.set(Unit.unit());
+        assertThat(result, contains("it worked!"));
+        result.clear();
+        aState.reset();
+        assertThat(result, contains("exit"));
+        result.clear();
+        aState.set(Unit.unit());
+        assertThat(result, contains("it worked!"));
+        result.clear();
+        bState.reset();
+        assertThat(result, contains("exit"));
+        result.clear();
+        bState.set(Unit.unit());
+        assertThat(result, contains("it worked!"));
+        result.clear();
+        cState.reset();
+        assertThat(result, contains("exit"));
+        result.clear();
+        cState.set(Unit.unit());
+        assertThat(result, contains("it worked!"));
+        result.clear();
+        dState.reset();
+        assertThat(result, contains("exit"));
+        result.clear();
+        dState.set(Unit.unit());
+        assertThat(result, contains("it worked!"));
+    }
+
+    // Any Scope implementation with a constructor of one argument can use a method reference to its
+    // constructor as an Observer.
+    private static class TransitionLogger implements Scope {
+        public static final List<String> sResult = new ArrayList<>();
+        private final String mData;
+
+        public TransitionLogger(String data) {
+            mData = data;
+            sResult.add("enter: " + mData);
+        }
+
+        @Override
+        public void close() {
+            sResult.add("exit: " + mData);
+        }
+    }
+
+    @Test
+    public void testObserverWithAutoCloseableConstructor() {
+        Controller<String> controller = new Controller<>();
+        // You can use a constructor method reference in a watch() call.
+        controller.watch(TransitionLogger::new);
+        controller.set("a");
+        controller.reset();
+        assertThat(TransitionLogger.sResult, contains("enter: a", "exit: a"));
+    }
+}
diff --git a/chromecast/base/java/test/org/chromium/chromecast/base/ObservableNotTest.java b/chromecast/base/java/test/org/chromium/chromecast/base/ObservableNotTest.java
new file mode 100644
index 0000000..fb480e41
--- /dev/null
+++ b/chromecast/base/java/test/org/chromium/chromecast/base/ObservableNotTest.java
@@ -0,0 +1,70 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chromecast.base;
+
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.emptyIterable;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for Observable.not().
+ */
+@RunWith(BlockJUnit4ClassRunner.class)
+public class ObservableNotTest {
+    @Test
+    public void testNotIsActivatedAtTheStart() {
+        Controller<String> invertThis = new Controller<>();
+        List<String> result = new ArrayList<>();
+        Observable.not(invertThis).watch(x -> {
+            result.add("enter inverted");
+            return () -> result.add("exit inverted");
+        });
+        assertThat(result, contains("enter inverted"));
+    }
+
+    @Test
+    public void testNotIsDeactivatedAtTheStartIfSourceIsAlreadyActivated() {
+        Controller<String> invertThis = new Controller<>();
+        List<String> result = new ArrayList<>();
+        invertThis.set("way ahead of you");
+        Observable.not(invertThis).watch(x -> {
+            result.add("enter inverted");
+            return () -> result.add("exit inverted");
+        });
+        assertThat(result, emptyIterable());
+    }
+
+    @Test
+    public void testNotExitsWhenSourceIsActivated() {
+        Controller<String> invertThis = new Controller<>();
+        List<String> result = new ArrayList<>();
+        Observable.not(invertThis).watch(x -> {
+            result.add("enter inverted");
+            return () -> result.add("exit inverted");
+        });
+        invertThis.set("first");
+        assertThat(result, contains("enter inverted", "exit inverted"));
+    }
+
+    @Test
+    public void testNotReentersWhenSourceIsReset() {
+        Controller<String> invertThis = new Controller<>();
+        List<String> result = new ArrayList<>();
+        Observable.not(invertThis).watch(x -> {
+            result.add("enter inverted");
+            return () -> result.add("exit inverted");
+        });
+        invertThis.set("first");
+        invertThis.reset();
+        assertThat(result, contains("enter inverted", "exit inverted", "enter inverted"));
+    }
+}
diff --git a/chromecast/base/java/test/org/chromium/chromecast/base/ReactiveRecorder.java b/chromecast/base/java/test/org/chromium/chromecast/base/ReactiveRecorder.java
index 284c1a54..144b416 100644
--- a/chromecast/base/java/test/org/chromium/chromecast/base/ReactiveRecorder.java
+++ b/chromecast/base/java/test/org/chromium/chromecast/base/ReactiveRecorder.java
@@ -5,13 +5,12 @@
 package org.chromium.chromecast.base;
 
 import static org.hamcrest.Matchers.emptyIterable;
+import static org.hamcrest.Matchers.not;
 
 import org.junit.Assert;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 /**
  * Records events emitted by Observables, and provides a fluent interface to perform assertions on
@@ -19,23 +18,22 @@
  */
 public class ReactiveRecorder {
     private final List<Event> mRecord;
-    private final Map<Observable<?>, String> mObservableNames;
+    private final Scope mSubscription;
 
-    public static ReactiveRecorder record(Observable<?>... observables) {
-        return new ReactiveRecorder(observables);
+    public static ReactiveRecorder record(Observable<?> observable) {
+        return new ReactiveRecorder(observable);
     }
 
-    private ReactiveRecorder(Observable<?>... observables) {
+    private ReactiveRecorder(Observable<?> observable) {
         mRecord = new ArrayList<>();
-        mObservableNames = new HashMap<>();
-        int id = 0;
-        for (Observable<?> observable : observables) {
-            mObservableNames.put(observable, "Observable" + id++);
-            observable.watch((Object value) -> {
-                mRecord.add(enterEvent(observable, value));
-                return () -> mRecord.add(exitEvent(observable, value));
-            });
-        }
+        mSubscription = observable.watch((Object value) -> {
+            mRecord.add(openEvent(value));
+            return () -> mRecord.add(closeEvent(value));
+        });
+    }
+
+    public void unsubscribe() {
+        mSubscription.close();
     }
 
     public ReactiveRecorder reset() {
@@ -48,84 +46,54 @@
     }
 
     /**
-     * The fluent interface used to perform assertions. Each entered() or exited() call pops the
+     * The fluent interface used to perform assertions. Each opened() or closed() call pops the
      * least-recently-added event from the record, and verifies that it meets the description
-     * provided by the arguments given to entered() or exited(). Use end() to assert that no more
+     * provided by the arguments given to opened() or closed(). Use end() to assert that no more
      * events were received.
      */
     public class Validator {
         private Validator() {}
 
-        public Validator entered(Observable observable, Object value) {
+        public Validator opened(Object value) {
             Event event = pop();
-            event.checkType("enter");
-            event.checkObservable(observable);
+            event.checkType("open");
             event.checkValue(value);
             return this;
         }
 
-        public Validator entered(Object value) {
+        public Validator closed(Object value) {
             Event event = pop();
-            event.checkType("enter");
+            event.checkType("close");
             event.checkValue(value);
             return this;
         }
 
-        public Validator entered() {
-            Event event = pop();
-            event.checkType("enter");
-            return this;
-        }
-
-        public Validator exited(Observable observable) {
-            Event event = pop();
-            event.checkType("exit");
-            event.checkObservable(observable);
-            return this;
-        }
-
-        public Validator exited() {
-            Event event = pop();
-            event.checkType("exit");
-            return this;
-        }
-
         public void end() {
             Assert.assertThat(mRecord, emptyIterable());
         }
     }
 
     private Event pop() {
+        Assert.assertThat(mRecord, not(emptyIterable()));
         return mRecord.remove(0);
     }
 
-    private String observableName(Observable<?> observable) {
-        String name = mObservableNames.get(observable);
-        if (name == null) {
-            return "(Unknown)";
-        }
-        return name;
-    }
-
-    private Event enterEvent(Observable<?> observable, Object value) {
+    private Event openEvent(Object value) {
         Event result = new Event();
-        result.type = "enter";
-        result.observable = observable;
+        result.type = "open";
         result.value = value;
         return result;
     }
 
-    private Event exitEvent(Observable<?> observable, Object value) {
+    private Event closeEvent(Object value) {
         Event result = new Event();
-        result.type = "exit";
-        result.observable = observable;
+        result.type = "close";
         result.value = value;
         return result;
     }
 
     private class Event {
         public String type;
-        public Observable<?> observable;
         public Object value;
 
         private Event() {}
@@ -134,12 +102,6 @@
             Assert.assertEquals("Event " + this + " is not an " + type + " event", type, this.type);
         }
 
-        public void checkObservable(Observable<?> observable) {
-            Assert.assertEquals("Event " + this + " has wrong observable, expected "
-                            + observableName(observable),
-                    observable, this.observable);
-        }
-
         public void checkValue(Object value) {
             Assert.assertEquals(
                     "Event " + this + " has wrong value, expected " + value, value, this.value);
@@ -147,7 +109,7 @@
 
         @Override
         public String toString() {
-            return "(" + type + " " + observableName(observable) + ": " + value + ")";
+            return "(" + type + ": " + value + ")";
         }
     }
 }
diff --git a/chromecast/base/java/test/org/chromium/chromecast/base/ReactiveRecorderTest.java b/chromecast/base/java/test/org/chromium/chromecast/base/ReactiveRecorderTest.java
index d56a713..eeb38585 100644
--- a/chromecast/base/java/test/org/chromium/chromecast/base/ReactiveRecorderTest.java
+++ b/chromecast/base/java/test/org/chromium/chromecast/base/ReactiveRecorderTest.java
@@ -28,85 +28,56 @@
         controller.set(Unit.unit());
         controller.reset();
         controller.set(Unit.unit());
-        recorder.verify().entered(Unit.unit()).exited().end();
+        recorder.verify().opened(Unit.unit()).closed(Unit.unit()).end();
     }
 
     @Test(expected = AssertionError.class)
-    public void testFailEnteredWrongValue() {
+    public void testFailOpenedWrongValue() {
         Controller<String> controller = new Controller<>();
         ReactiveRecorder recorder = ReactiveRecorder.record(controller);
         controller.set("actual");
-        recorder.verify().entered("expected");
+        recorder.verify().opened("expected");
     }
 
     @Test(expected = AssertionError.class)
-    public void testFailEnteredGotExit() {
+    public void testFailOpenedGotClosed() {
         Controller<String> controller = new Controller<>();
         controller.set("before");
         ReactiveRecorder recorder = ReactiveRecorder.record(controller).reset();
         controller.set("after");
-        recorder.verify().entered("after");
+        recorder.verify().opened("after");
     }
 
     @Test(expected = AssertionError.class)
-    public void testFailExitedGotEnter() {
+    public void testFailClosedGotOpened() {
         Controller<Unit> controller = new Controller<>();
         ReactiveRecorder recorder = ReactiveRecorder.record(controller);
         controller.set(Unit.unit());
-        recorder.verify().exited();
+        recorder.verify().closed(Unit.unit());
     }
 
     @Test(expected = AssertionError.class)
-    public void testEnteredWrongObservable() {
-        Controller<Unit> controller = new Controller<>();
-        Controller<Unit> wrong = new Controller<>();
-        ReactiveRecorder recorder = ReactiveRecorder.record(controller, wrong);
-        controller.set(Unit.unit());
-        recorder.verify().entered(wrong, Unit.unit());
-    }
-
-    @Test(expected = AssertionError.class)
-    public void testExitedWrongObservable() {
-        Controller<Unit> controller = new Controller<>();
-        Controller<Unit> wrong = new Controller<>();
-        ReactiveRecorder recorder = ReactiveRecorder.record(controller, wrong);
-        controller.set(Unit.unit());
-        wrong.set(Unit.unit());
-        recorder.reset();
-        controller.reset();
-        recorder.verify().exited(wrong);
+    public void testFailGetNotificationsAfterUnsubscribe() {
+        Controller<String> controller = new Controller<>();
+        ReactiveRecorder recorder = ReactiveRecorder.record(controller);
+        recorder.unsubscribe();
+        controller.set("unexpected");
+        recorder.verify().opened("unexpected");
     }
 
     @Test
-    public void testHappyPathForOneObservable() {
+    public void testHappyPath() {
         Controller<Unit> controller = new Controller<>();
         ReactiveRecorder recorder = ReactiveRecorder.record(controller);
         controller.set(Unit.unit());
         controller.reset();
         controller.set(Unit.unit());
         controller.reset();
-        recorder.verify().entered().exited().entered().exited().end();
-    }
-
-    @Test
-    public void testHappyPathForManyObservables() {
-        Controller<String> a = new Controller<>();
-        Controller<String> b = new Controller<>();
-        Controller<String> c = new Controller<>();
-        ReactiveRecorder recorder = ReactiveRecorder.record(a, b, c);
-        a.set("a");
-        b.set("b");
-        c.set("c");
-        b.reset();
-        a.reset();
-        c.reset();
         recorder.verify()
-                .entered(a, "a")
-                .entered(b, "b")
-                .entered(c, "c")
-                .exited(b)
-                .exited(a)
-                .exited(c)
+                .opened(Unit.unit())
+                .closed(Unit.unit())
+                .opened(Unit.unit())
+                .closed(Unit.unit())
                 .end();
     }
 }
diff --git a/chromecast/base/reactive_java.md b/chromecast/base/reactive_java.md
index 67da83a..0aabb2d2f 100644
--- a/chromecast/base/reactive_java.md
+++ b/chromecast/base/reactive_java.md
@@ -952,14 +952,14 @@
 ### Testing
 
 One of the most important aspects of using `Observable`s is that they are very
-testable. The `Observable` cleanly separates the concerns of *mutating* program
-state and *responding to* program state. Reactors, or observers, registered in
-`watch()` methods tend to be **functional**, i.e. with no side effects, though
-this isn't a strict requirement (see the above section).
+testable. Although `Observer`s themselves are not pure-functional (i.e. they
+tend to mutate program state), this is done in such a way that the mutations in
+the form of state transitions in `Observable`s are easy to track, and therefore
+easy to test.
 
 If you write a class that implements `Observable` or returns an `Observable` in
 one of its methods, it's easy to test the events it emits by using the
-`ReactiveRecorder` test utility function. This class, which is only allowed in
+`ReactiveRecorder` test utility module. This class, which is only allowed in
 tests, provides a fluent interface for describing the expected output of an
 `Observable`.
 
@@ -988,7 +988,7 @@
         ReactiveRecorder recorder = ReactiveRecorder.record(f);
         f.flip();
         // A single activation should have been emitted.
-        recorder.verify().entered().end();
+        recorder.verify().opened(Unit.unit()).end();
     }
 
     @Test
@@ -998,17 +998,40 @@
         f.flip();
         f.flip();
         // Expect an activation followed by a deactivation.
-        recorder.verify().entered().exited().end();
+        recorder.verify().opened(Unit.unit()).closed(Unit.unit()).end();
     }
 }
 ```
 
-`ReactiveRecorder`'s `entered()` and `exited()` methods can also take arguments
-to perform assertions on the activation data. `ReactiveRecorder.record()` can
-also take arbitrarily many `Observable` arguments and receive the events of all
-of the given `Observable`s. In this case, the `entered()` and `exited()` methods
-have overloads that take an `Observable` as an argument, which can be used to
-assert *which* `Observable` emitted an event.
+The `ReactiveRecorder` class works by calling `watch()` on the given
+`Observable` and storing the activations and deactivations it observes in a
+list. The `verify()` method opens a domain-specific language for performing
+assertions on the activation data, using `opened()` and `closed()` to check
+which data has been activated and deactivated. The transitions recorded must
+occur in the same order as the `opened()` and `closed()` calls to pass the test.
+The `end()` method asserts that no more transitions occurred.
+
+You can test behaviors that should occur when closing a `watch()` scope by
+calling `recorder.unsubscribe()`. For example, every `Observable` implementation
+should close all existing `Scopes` emitted from an `Observer` when that
+`Observer`'s `watch()` scope is closed:
+
+```java
+    @Test
+    public void testUnsubscribeCloses() {
+        FlipFlop f = new FlipFlop();
+        ReactiveRecorder recorder = ReactiveRecorder.record(f);
+        f.flip();
+        // Clear the record; we don't care about the activation from flip().
+        recorder.reset();
+        recorder.unsubscribe();
+        // Unsubscribing should implicitly close the scope.
+        recorder.verify().closed(Unit.unit()).end();
+    }
+```
+
+Once a `ReactiveRecorder` unsubscribes, it will not get any new events from the
+`Observable` it was recording.
 
 ## When to use Observables
 
diff --git a/chromecast/browser/cast_content_browser_client.cc b/chromecast/browser/cast_content_browser_client.cc
index 6997891..0e8fee53 100644
--- a/chromecast/browser/cast_content_browser_client.cc
+++ b/chromecast/browser/cast_content_browser_client.cc
@@ -86,17 +86,17 @@
 #if defined(OS_ANDROID)
 #include "components/cdm/browser/cdm_message_filter_android.h"
 #include "components/crash/content/browser/child_exit_observer_android.h"
-#if !BUILDFLAG(IS_CAST_USING_CMA_BACKEND)
+#if !BUILDFLAG(USE_CHROMECAST_CDMS)
 #include "components/cdm/browser/media_drm_storage_impl.h"
 #include "url/origin.h"
-#endif  // !BUILDFLAG(IS_CAST_USING_CMA_BACKEND)
+#endif  // !BUILDFLAG(USE_CHROMECAST_CDMS)
 #else
 #include "chromecast/browser/memory_pressure_controller_impl.h"
 #endif  // defined(OS_ANDROID)
 
-#if BUILDFLAG(IS_CAST_USING_CMA_BACKEND)
+#if BUILDFLAG(USE_CHROMECAST_CDMS)
 #include "chromecast/media/cdm/cast_cdm_factory.h"
-#endif  // BUILDFLAG(IS_CAST_USING_CMA_BACKEND)
+#endif  // BUILDFLAG(USE_CHROMECAST_CDMS)
 
 #if defined(USE_ALSA)
 #include "chromecast/media/audio/cast_audio_manager_alsa.h"  // nogncheck
@@ -131,7 +131,7 @@
 }
 #endif  // BUILDFLAG(ENABLE_MOJO_MEDIA_IN_BROWSER_PROCESS)
 
-#if defined(OS_ANDROID) && !BUILDFLAG(IS_CAST_USING_CMA_BACKEND)
+#if defined(OS_ANDROID) && !BUILDFLAG(USE_CHROMECAST_CDMS)
 void CreateMediaDrmStorage(content::RenderFrameHost* render_frame_host,
                            ::media::mojom::MediaDrmStorageRequest request) {
   DVLOG(1) << __func__;
@@ -148,7 +148,7 @@
   new cdm::MediaDrmStorageImpl(render_frame_host, pref_service,
                                std::move(request));
 }
-#endif  // defined(OS_ANDROID) && !BUILDFLAG(IS_CAST_USING_CMA_BACKEND)
+#endif  // defined(OS_ANDROID) && !BUILDFLAG(USE_CHROMECAST_CDMS)
 
 }  // namespace
 
@@ -241,7 +241,9 @@
   // See CreateAudioManager().
   return true;
 }
+#endif  // BUILDFLAG(IS_CAST_USING_CMA_BACKEND)
 
+#if BUILDFLAG(USE_CHROMECAST_CDMS)
 std::unique_ptr<::media::CdmFactory>
 CastContentBrowserClient::CreateCdmFactory() {
 #if BUILDFLAG(ENABLE_MOJO_MEDIA_IN_BROWSER_PROCESS)
@@ -250,7 +252,7 @@
 #endif  // BUILDFLAG(ENABLE_MOJO_MEDIA_IN_BROWSER_PROCESS)
   return nullptr;
 }
-#endif  // BUILDFLAG(IS_CAST_USING_CMA_BACKEND)
+#endif  // BUILDFLAG(USE_CHROMECAST_CDMS)
 
 media::MediaCapsImpl* CastContentBrowserClient::media_caps() {
   DCHECK(cast_browser_main_parts_);
@@ -653,7 +655,7 @@
 void CastContentBrowserClient::ExposeInterfacesToMediaService(
     service_manager::BinderRegistry* registry,
     content::RenderFrameHost* render_frame_host) {
-#if defined(OS_ANDROID) && !BUILDFLAG(IS_CAST_USING_CMA_BACKEND)
+#if defined(OS_ANDROID) && !BUILDFLAG(USE_CHROMECAST_CDMS)
   registry->AddInterface(
       base::BindRepeating(&CreateMediaDrmStorage, render_frame_host));
 #endif
diff --git a/chromecast/browser/cast_content_browser_client.h b/chromecast/browser/cast_content_browser_client.h
index 0b3592af..d76018e 100644
--- a/chromecast/browser/cast_content_browser_client.h
+++ b/chromecast/browser/cast_content_browser_client.h
@@ -197,9 +197,9 @@
     return renderer_config_manager_.get();
   }
 
-#if BUILDFLAG(IS_CAST_USING_CMA_BACKEND)
+#if BUILDFLAG(USE_CHROMECAST_CDMS)
   virtual std::unique_ptr<::media::CdmFactory> CreateCdmFactory();
-#endif  // BUILDFLAG(IS_CAST_USING_CMA_BACKEND)
+#endif  // BUILDFLAG(USE_CHROMECAST_CDMS)
 
  protected:
   CastContentBrowserClient();
diff --git a/chromecast/browser/pref_service_helper.cc b/chromecast/browser/pref_service_helper.cc
index 8dd1e9e3..f336c1e 100644
--- a/chromecast/browser/pref_service_helper.cc
+++ b/chromecast/browser/pref_service_helper.cc
@@ -19,9 +19,9 @@
 #include "components/prefs/pref_service_factory.h"
 #include "components/prefs/pref_store.h"
 
-#if defined(OS_ANDROID) && !BUILDFLAG(IS_CAST_USING_CMA_BACKEND)
+#if defined(OS_ANDROID) && !BUILDFLAG(USE_CHROMECAST_CDMS)
 #include "components/cdm/browser/media_drm_storage_impl.h"
-#endif  // defined(OS_ANDROID) && !BUILDFLAG(IS_CAST_USING_CMA_BACKEND)
+#endif  // defined(OS_ANDROID) && !BUILDFLAG(USE_CHROMECAST_CDMS)
 
 namespace chromecast {
 namespace shell {
@@ -60,9 +60,9 @@
   registry->RegisterListPref(prefs::kActiveDCSExperiments);
   registry->RegisterDictionaryPref(prefs::kLatestDCSFeatures);
 
-#if defined(OS_ANDROID) && !BUILDFLAG(IS_CAST_USING_CMA_BACKEND)
+#if defined(OS_ANDROID) && !BUILDFLAG(USE_CHROMECAST_CDMS)
   cdm::MediaDrmStorageImpl::RegisterProfilePrefs(registry);
-#endif  // defined(OS_ANDROID) && !BUILDFLAG(IS_CAST_USING_CMA_BACKEND)
+#endif  // defined(OS_ANDROID) && !BUILDFLAG(USE_CHROMECAST_CDMS)
 
   RegisterPlatformPrefs(registry);
 
diff --git a/chromecast/chromecast.gni b/chromecast/chromecast.gni
index 69116c6b..08dc08a 100644
--- a/chromecast/chromecast.gni
+++ b/chromecast/chromecast.gni
@@ -5,6 +5,7 @@
 import("//build/config/chromecast_build.gni")
 import("//build/config/locales.gni")
 import("//extensions/buildflags/buildflags.gni")
+import("//media/media_options.gni")
 
 # This args block should contain arguments used within the //chromecast
 # directory. Arguments which are used in other Chrome components should
@@ -113,6 +114,12 @@
 
 enable_chromecast_extensions = enable_extensions
 
+# Use Chromecast CDMs for protected content. Some Android platforms use
+# MediaDrm for CDM support.
+declare_args() {
+  use_chromecast_cdms = is_cast_using_cma_backend
+}
+
 foreach(target_type,
         [
           "executable",
diff --git a/chromecast/media/cma/backend/BUILD.gn b/chromecast/media/cma/backend/BUILD.gn
index 638be08d..50870c4 100644
--- a/chromecast/media/cma/backend/BUILD.gn
+++ b/chromecast/media/cma/backend/BUILD.gn
@@ -184,6 +184,7 @@
     "audio_output_redirector.h",
     "buffering_mixer_source.cc",
     "buffering_mixer_source.h",
+    "cast_media_shlib_mixer_audio.cc",
     "direct_mixer_source.cc",
     "direct_mixer_source.h",
     "filter_group.cc",
diff --git a/chromecast/media/cma/backend/alsa/cast_media_shlib.cc b/chromecast/media/cma/backend/alsa/cast_media_shlib.cc
index d2215fa2..0aadf6d 100644
--- a/chromecast/media/cma/backend/alsa/cast_media_shlib.cc
+++ b/chromecast/media/cma/backend/alsa/cast_media_shlib.cc
@@ -190,23 +190,5 @@
   return g_rate_offset_element != nullptr;
 }
 
-void CastMediaShlib::AddLoopbackAudioObserver(LoopbackAudioObserver* observer) {
-  StreamMixer::Get()->AddLoopbackAudioObserver(observer);
-}
-
-void CastMediaShlib::RemoveLoopbackAudioObserver(
-    LoopbackAudioObserver* observer) {
-  StreamMixer::Get()->RemoveLoopbackAudioObserver(observer);
-}
-
-void CastMediaShlib::ResetPostProcessors() {
-  StreamMixer::Get()->ResetPostProcessors();
-}
-
-void CastMediaShlib::SetPostProcessorConfig(const std::string& name,
-                                            const std::string& config) {
-  StreamMixer::Get()->SetPostProcessorConfig(name, config);
-}
-
 }  // namespace media
 }  // namespace chromecast
diff --git a/chromecast/media/cma/backend/cast_media_shlib_mixer_audio.cc b/chromecast/media/cma/backend/cast_media_shlib_mixer_audio.cc
new file mode 100644
index 0000000..42563d5
--- /dev/null
+++ b/chromecast/media/cma/backend/cast_media_shlib_mixer_audio.cc
@@ -0,0 +1,35 @@
+// 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.
+
+// Provides CastMediaShlib functions common to all devices using StreamMixer.
+
+#include "chromecast/public/cast_media_shlib.h"
+
+#include <string>
+
+#include "chromecast/media/cma/backend/stream_mixer.h"
+
+namespace chromecast {
+namespace media {
+
+void CastMediaShlib::AddLoopbackAudioObserver(LoopbackAudioObserver* observer) {
+  StreamMixer::Get()->AddLoopbackAudioObserver(observer);
+}
+
+void CastMediaShlib::RemoveLoopbackAudioObserver(
+    LoopbackAudioObserver* observer) {
+  StreamMixer::Get()->RemoveLoopbackAudioObserver(observer);
+}
+
+void CastMediaShlib::ResetPostProcessors() {
+  StreamMixer::Get()->ResetPostProcessors();
+}
+
+void CastMediaShlib::SetPostProcessorConfig(const std::string& name,
+                                            const std::string& config) {
+  StreamMixer::Get()->SetPostProcessorConfig(name, config);
+}
+
+}  // namespace media
+}  // namespace chromecast
diff --git a/chromecast/media/cma/backend/video/cast_media_shlib_common.cc b/chromecast/media/cma/backend/video/cast_media_shlib_common.cc
index bdca2c8..000cdcc 100644
--- a/chromecast/media/cma/backend/video/cast_media_shlib_common.cc
+++ b/chromecast/media/cma/backend/video/cast_media_shlib_common.cc
@@ -24,14 +24,5 @@
   return false;
 }
 
-void CastMediaShlib::AddLoopbackAudioObserver(LoopbackAudioObserver* observer) {
-  StreamMixer::Get()->AddLoopbackAudioObserver(observer);
-}
-
-void CastMediaShlib::RemoveLoopbackAudioObserver(
-    LoopbackAudioObserver* observer) {
-  StreamMixer::Get()->RemoveLoopbackAudioObserver(observer);
-}
-
 }  // namespace media
 }  // namespace chromecast
diff --git a/chromeos/services/assistant/BUILD.gn b/chromeos/services/assistant/BUILD.gn
index bb7b675e..a2b1ed6 100644
--- a/chromeos/services/assistant/BUILD.gn
+++ b/chromeos/services/assistant/BUILD.gn
@@ -49,6 +49,8 @@
       "assistant_settings_manager_impl.h",
       "platform/audio_input_provider_impl.cc",
       "platform/audio_input_provider_impl.h",
+      "platform/audio_output_provider_impl.cc",
+      "platform/audio_output_provider_impl.h",
       "platform/file_provider_impl.cc",
       "platform/file_provider_impl.h",
       "platform/network_provider_impl.cc",
@@ -106,7 +108,10 @@
   ]
 
   if (enable_cros_libassistant) {
-    sources += [ "platform/system_provider_impl_unittest.cc" ]
+    sources += [
+      "platform/audio_output_provider_impl_unittest.cc",
+      "platform/system_provider_impl_unittest.cc",
+    ]
   }
 }
 
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.h b/chromeos/services/assistant/assistant_manager_service_impl.h
index 9ec1938..2085c9e 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.h
+++ b/chromeos/services/assistant/assistant_manager_service_impl.h
@@ -9,8 +9,6 @@
 #include <string>
 #include <vector>
 
-// TODO(xiaohuic): replace with "base/macros.h" once we remove
-// libassistant/contrib dependency.
 #include "ash/public/interfaces/assistant_controller.mojom.h"
 #include "ash/public/interfaces/voice_interaction_controller.mojom.h"
 #include "base/threading/thread.h"
@@ -21,7 +19,6 @@
 #include "chromeos/services/assistant/assistant_settings_manager_impl.h"
 #include "chromeos/services/assistant/platform_api_impl.h"
 #include "chromeos/services/assistant/public/mojom/assistant.mojom.h"
-#include "libassistant/contrib/core/macros.h"
 #include "libassistant/shared/internal_api/assistant_manager_delegate.h"
 #include "libassistant/shared/public/conversation_state_listener.h"
 #include "libassistant/shared/public/device_state_listener.h"
diff --git a/chromeos/services/assistant/platform/audio_input_provider_impl.cc b/chromeos/services/assistant/platform/audio_input_provider_impl.cc
index bac0eb9..d755f8c 100644
--- a/chromeos/services/assistant/platform/audio_input_provider_impl.cc
+++ b/chromeos/services/assistant/platform/audio_input_provider_impl.cc
@@ -141,21 +141,30 @@
 }
 
 void AudioInputImpl::SetMicState(bool mic_open) {
-  DCHECK(task_runner_->RunsTasksInCurrentSequence());
   if (!default_on_) {
-    if (mic_open)
-      source_->Start();
-    else
-      source_->Stop();
+    if (mic_open) {
+      task_runner_->PostTask(FROM_HERE,
+                             base::BindOnce(&AudioInputImpl::StartRecording,
+                                            weak_factory_.GetWeakPtr()));
+    } else {
+      task_runner_->PostTask(FROM_HERE,
+                             base::BindOnce(&AudioInputImpl::StopRecording,
+                                            weak_factory_.GetWeakPtr()));
+    }
   }
 }
 
 void AudioInputImpl::OnHotwordEnabled(bool enable) {
   default_on_ = enable;
-  if (default_on_)
-    source_->Start();
-  else
-    source_->Stop();
+  if (default_on_) {
+    task_runner_->PostTask(FROM_HERE,
+                           base::BindOnce(&AudioInputImpl::StartRecording,
+                                          weak_factory_.GetWeakPtr()));
+  } else {
+    task_runner_->PostTask(FROM_HERE,
+                           base::BindOnce(&AudioInputImpl::StopRecording,
+                                          weak_factory_.GetWeakPtr()));
+  }
 }
 
 void AudioInputImpl::StartRecording() {
diff --git a/chromeos/services/assistant/platform/audio_output_provider_impl.cc b/chromeos/services/assistant/platform/audio_output_provider_impl.cc
new file mode 100644
index 0000000..a57d141
--- /dev/null
+++ b/chromeos/services/assistant/platform/audio_output_provider_impl.cc
@@ -0,0 +1,298 @@
+// 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/services/assistant/platform/audio_output_provider_impl.h"
+
+#include "libassistant/shared/public/platform_audio_buffer.h"
+#include "media/audio/audio_device_description.h"
+#include "services/service_manager/public/cpp/connector.h"
+
+namespace chromeos {
+namespace assistant {
+
+namespace {
+
+constexpr int kNumberOfBuffersPerSec = 10;
+
+int32_t GetBytesPerFrame(const assistant_client::OutputStreamFormat& format) {
+  int bytes = 0;
+  switch (format.encoding) {
+    case assistant_client::OutputStreamEncoding::STREAM_PCM_S16:
+      bytes = 2;
+      break;
+    case assistant_client::OutputStreamEncoding::STREAM_PCM_S32:
+    case assistant_client::OutputStreamEncoding::STREAM_PCM_F32:
+      bytes = 4;
+      break;
+    default:
+      NOTREACHED();
+      break;
+  }
+  DCHECK(bytes);
+  return bytes * format.pcm_num_channels;
+}
+
+int32_t GetBufferSizeInBytesFromBufferFormat(
+    const assistant_client::OutputStreamFormat& format) {
+  int32_t frame_size_in_bytes = 0;
+
+  switch (format.encoding) {
+    case assistant_client::OutputStreamEncoding::STREAM_PCM_S16:
+      frame_size_in_bytes = 2;
+      break;
+    case assistant_client::OutputStreamEncoding::STREAM_PCM_S32:
+    case assistant_client::OutputStreamEncoding::STREAM_PCM_F32:
+      frame_size_in_bytes = 4;
+      break;
+    default:
+      NOTREACHED();
+      break;
+  }
+
+  return GetBytesPerFrame(format) * format.pcm_sample_rate /
+         kNumberOfBuffersPerSec;
+}
+
+media::AudioParameters GetAudioParametersFromBufferFormat(
+    const assistant_client::OutputStreamFormat& output_format) {
+  DCHECK(output_format.pcm_num_channels <= 2 &&
+         output_format.pcm_num_channels > 0);
+
+  return media::AudioParameters(
+      media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
+      media::GuessChannelLayout(output_format.pcm_num_channels),
+      output_format.pcm_sample_rate,
+      output_format.pcm_sample_rate / kNumberOfBuffersPerSec);
+}
+
+void FillAudioFifoWithDataOfBufferFormat(
+    media::AudioBlockFifo* fifo,
+    const std::vector<uint8_t>& data,
+    const assistant_client::OutputStreamFormat& output_format,
+    int num_bytes) {
+  int bytes_per_frame = GetBytesPerFrame(output_format);
+  int frames = num_bytes / bytes_per_frame;
+  fifo->Push(data.data(), frames, bytes_per_frame);
+}
+
+class AudioOutputImpl : public assistant_client::AudioOutput {
+ public:
+  AudioOutputImpl(service_manager::Connector* connector,
+                  scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+                  assistant_client::OutputStreamType type,
+                  assistant_client::OutputStreamFormat format)
+      : connector_(connector),
+        main_thread_task_runner_(std::move(task_runner)),
+        stream_type_(type),
+        format_(format),
+        device_owner_(std::make_unique<AudioDeviceOwner>(task_runner)) {}
+
+  ~AudioOutputImpl() override {
+    main_thread_task_runner_->DeleteSoon(FROM_HERE, device_owner_.release());
+  }
+
+  // assistant_client::AudioOutput overrides:
+  assistant_client::OutputStreamType GetType() override { return stream_type_; }
+
+  void Start(assistant_client::AudioOutput::Delegate* delegate) override {
+    main_thread_task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(&AudioDeviceOwner::StartOnMainThread,
+                                  base::Unretained(device_owner_.get()),
+                                  delegate, connector_, format_));
+  }
+
+  void Stop() override {
+    main_thread_task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(&AudioDeviceOwner::StopOnMainThread,
+                                  base::Unretained(device_owner_.get())));
+  }
+
+ private:
+  service_manager::Connector* connector_;
+  scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_;
+
+  const assistant_client::OutputStreamType stream_type_;
+  assistant_client::OutputStreamFormat format_;
+
+  std::unique_ptr<AudioDeviceOwner> device_owner_;
+
+  DISALLOW_COPY_AND_ASSIGN(AudioOutputImpl);
+};
+
+}  // namespace
+
+VolumeControlImpl::VolumeControlImpl() = default;
+
+VolumeControlImpl::~VolumeControlImpl() = default;
+
+void VolumeControlImpl::SetAudioFocus(
+    assistant_client::OutputStreamType focused_stream) {}
+
+float VolumeControlImpl::GetSystemVolume() {
+  // TODO(muyuanli): implement.
+  return 100.0f;
+}
+
+void VolumeControlImpl::SetSystemVolume(float new_volume, bool user_initiated) {
+  // TODO(muyuanli): implement.
+}
+
+float VolumeControlImpl::GetAlarmVolume() {
+  // TODO(muyuanli): implement.
+  return 100.0f;
+}
+
+void VolumeControlImpl::SetAlarmVolume(float new_volume, bool user_initiated) {
+  // TODO(muyuanli): implement.
+}
+
+bool VolumeControlImpl::IsSystemMuted() {
+  // TODO(muyuanli): implement.
+  return false;
+}
+
+void VolumeControlImpl::SetSystemMuted(bool muted) {
+  // TODO(muyuanli): implement.
+}
+
+AudioOutputProviderImpl::AudioOutputProviderImpl(
+    service_manager::Connector* connector)
+    : connector_(connector),
+      main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
+
+AudioOutputProviderImpl::~AudioOutputProviderImpl() = default;
+
+assistant_client::AudioOutput* AudioOutputProviderImpl::CreateAudioOutput(
+    assistant_client::OutputStreamType type,
+    const assistant_client::OutputStreamFormat& stream_format) {
+  // Owned by one arbitrary thread inside libassistant. It will be destroyed
+  // once assistant_client::AudioOutput::Delegate::OnStopped() is called.
+  // TODO(muyuanli): Handle encoded stream: OutputStreamEncoding::STREAM_MP3 /
+  // OGG.
+  return new AudioOutputImpl(connector_, main_thread_task_runner_, type,
+                             stream_format);
+}
+
+std::vector<assistant_client::OutputStreamEncoding>
+AudioOutputProviderImpl::GetSupportedStreamEncodings() {
+  // TODO(muyuanli): implement after media decoder is ready.
+  return std::vector<assistant_client::OutputStreamEncoding>{
+      assistant_client::OutputStreamEncoding::STREAM_PCM_S16,
+      assistant_client::OutputStreamEncoding::STREAM_PCM_S32,
+      assistant_client::OutputStreamEncoding::STREAM_PCM_F32,
+  };
+}
+
+assistant_client::AudioInput* AudioOutputProviderImpl::GetReferenceInput() {
+  // TODO(muyuanli): implement.
+  return nullptr;
+}
+
+bool AudioOutputProviderImpl::SupportsPlaybackTimestamp() const {
+  // TODO(muyuanli): implement.
+  return false;
+}
+
+assistant_client::VolumeControl& AudioOutputProviderImpl::GetVolumeControl() {
+  return volume_control_impl_;
+}
+
+void AudioOutputProviderImpl::RegisterAudioEmittingStateCallback(
+    AudioEmittingStateCallback callback) {
+  // TODO(muyuanli): implement.
+}
+
+AudioDeviceOwner::AudioDeviceOwner(
+    scoped_refptr<base::SequencedTaskRunner> task_runner)
+    : main_thread_task_runner_(std::move(task_runner)) {}
+
+AudioDeviceOwner::~AudioDeviceOwner() = default;
+
+void AudioDeviceOwner::StartOnMainThread(
+    assistant_client::AudioOutput::Delegate* delegate,
+    service_manager::Connector* connector,
+    const assistant_client::OutputStreamFormat& format) {
+  DCHECK(!output_device_);
+  DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
+
+  format_ = format;
+  audio_param_ = GetAudioParametersFromBufferFormat(format_);
+
+  // |audio_fifo_| contains 3x the number of frames to render.
+  audio_fifo_ = std::make_unique<media::AudioBlockFifo>(
+      format.pcm_num_channels, audio_param_.frames_per_buffer(), 3);
+  audio_data_.resize(GetBufferSizeInBytesFromBufferFormat(format_));
+  delegate_ = delegate;
+  ScheduleFillLocked(base::TimeTicks::Now());
+
+  // |connector| is null in unittest.
+  if (connector) {
+    output_device_ = std::make_unique<audio::OutputDevice>(
+        connector->Clone(), audio_param_, this,
+        media::AudioDeviceDescription::kDefaultDeviceId);
+    output_device_->Play();
+  }
+}
+
+void AudioDeviceOwner::StopOnMainThread() {
+  DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
+
+  output_device_.reset();
+  delegate_->OnStopped();
+}
+
+int AudioDeviceOwner::Render(base::TimeDelta delay,
+                             base::TimeTicks delay_timestamp,
+                             int prior_frames_skipped,
+                             media::AudioBus* dest) {
+  base::AutoLock lock(lock_);
+
+  if (!is_filling_ && audio_fifo_->GetAvailableFrames() <= 0) {
+    delegate_->OnEndOfStream();
+    return 0;
+  }
+  if (audio_fifo_->GetAvailableFrames() <= 0) {
+    // Wait for the next round of filling. This should only happen at the
+    // very beginning.
+    return 0;
+  }
+  audio_fifo_->Consume()->CopyTo(dest);
+  ScheduleFillLocked(base::TimeTicks::Now());
+  return dest->frames();
+}
+
+void AudioDeviceOwner::OnRenderError() {
+  DVLOG(1) << "OnRenderError()";
+  delegate_->OnError(assistant_client::AudioOutput::Error::FATAL_ERROR);
+}
+
+void AudioDeviceOwner::ScheduleFillLocked(const base::TimeTicks& time) {
+  lock_.AssertAcquired();
+  if (is_filling_)
+    return;
+  is_filling_ = true;
+  // FillBuffer will not be called after delegate_->OnEndOfStream, after which
+  // AudioDeviceOwner will be destroyed. Thus |this| is valid for capture
+  // here.
+  delegate_->FillBuffer(
+      audio_data_.data(),
+      std::min(static_cast<int>(audio_data_.size()),
+               GetBytesPerFrame(format_) * audio_fifo_->GetUnfilledFrames()),
+      time.since_origin().InMicroseconds(),
+      [this](int num) { this->BufferFillDone(num); });
+}
+
+void AudioDeviceOwner::BufferFillDone(int num_bytes) {
+  base::AutoLock lock(lock_);
+  is_filling_ = false;
+  if (num_bytes == 0)
+    return;
+  FillAudioFifoWithDataOfBufferFormat(audio_fifo_.get(), audio_data_, format_,
+                                      num_bytes);
+  if (audio_fifo_->GetUnfilledFrames() > 0)
+    ScheduleFillLocked(base::TimeTicks::Now());
+}
+
+}  // namespace assistant
+}  // namespace chromeos
diff --git a/chromeos/services/assistant/platform/audio_output_provider_impl.h b/chromeos/services/assistant/platform/audio_output_provider_impl.h
new file mode 100644
index 0000000..373c8c9
--- /dev/null
+++ b/chromeos/services/assistant/platform/audio_output_provider_impl.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 CHROMEOS_SERVICES_ASSISTANT_PLATFORM_AUDIO_OUTPUT_PROVIDER_IMPL_H_
+#define CHROMEOS_SERVICES_ASSISTANT_PLATFORM_AUDIO_OUTPUT_PROVIDER_IMPL_H_
+
+#include "base/macros.h"
+#include "base/single_thread_task_runner.h"
+#include "libassistant/shared/public/platform_audio_output.h"
+#include "media/base/audio_block_fifo.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/audio_renderer_sink.h"
+#include "services/audio/public/cpp/output_device.h"
+
+namespace service_manager {
+class Connector;
+}  // namespace service_manager
+
+namespace chromeos {
+namespace assistant {
+
+class VolumeControlImpl : public assistant_client::VolumeControl {
+ public:
+  VolumeControlImpl();
+  ~VolumeControlImpl() override;
+
+  // assistant_client::VolumeControl overrides:
+  void SetAudioFocus(
+      assistant_client::OutputStreamType focused_stream) override;
+  float GetSystemVolume() override;
+  void SetSystemVolume(float new_volume, bool user_initiated) override;
+  float GetAlarmVolume() override;
+  void SetAlarmVolume(float new_volume, bool user_initiated) override;
+  bool IsSystemMuted() override;
+  void SetSystemMuted(bool muted) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(VolumeControlImpl);
+};
+
+class AudioOutputProviderImpl : public assistant_client::AudioOutputProvider {
+ public:
+  explicit AudioOutputProviderImpl(service_manager::Connector* connector);
+  ~AudioOutputProviderImpl() override;
+
+  // assistant_client::AudioOutputProvider overrides:
+  assistant_client::AudioOutput* CreateAudioOutput(
+      assistant_client::OutputStreamType type,
+      const assistant_client::OutputStreamFormat& stream_format) override;
+
+  std::vector<assistant_client::OutputStreamEncoding>
+  GetSupportedStreamEncodings() override;
+
+  assistant_client::AudioInput* GetReferenceInput() override;
+
+  bool SupportsPlaybackTimestamp() const override;
+
+  assistant_client::VolumeControl& GetVolumeControl() override;
+
+  void RegisterAudioEmittingStateCallback(
+      AudioEmittingStateCallback callback) override;
+
+ private:
+  VolumeControlImpl volume_control_impl_;
+  service_manager::Connector* connector_;
+  scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_;
+
+  DISALLOW_COPY_AND_ASSIGN(AudioOutputProviderImpl);
+};
+
+class AudioDeviceOwner : public media::AudioRendererSink::RenderCallback {
+ public:
+  AudioDeviceOwner(scoped_refptr<base::SequencedTaskRunner> task_runner);
+  ~AudioDeviceOwner() override;
+
+  void StartOnMainThread(assistant_client::AudioOutput::Delegate* delegate,
+                         service_manager::Connector* connector,
+                         const assistant_client::OutputStreamFormat& format);
+
+  void StopOnMainThread();
+
+  // media::AudioRenderSink::RenderCallback overrides:
+  int Render(base::TimeDelta delay,
+             base::TimeTicks delay_timestamp,
+             int prior_frames_skipped,
+             media::AudioBus* dest) override;
+
+  void OnRenderError() override;
+
+ private:
+  // Requests assistant to fill buffer with more data.
+  void ScheduleFillLocked(const base::TimeTicks& time);
+
+  // Callback for assistant to notify that it completes the filling.
+  void BufferFillDone(int num_bytes);
+
+  scoped_refptr<base::SequencedTaskRunner> main_thread_task_runner_;
+
+  base::Lock lock_;
+  std::unique_ptr<media::AudioBlockFifo> audio_fifo_;  // guarded by lock_.
+  // Whether assistant is filling the buffer -- delegate_->FillBuffer is called
+  // and BufferFillDone() is not called yet.
+  // guarded by lock_.
+  bool is_filling_ = false;
+
+  assistant_client::AudioOutput::Delegate* delegate_;
+  std::unique_ptr<audio::OutputDevice> output_device_;
+  // Stores audio frames generated by assistant.
+  std::vector<uint8_t> audio_data_;
+  assistant_client::OutputStreamFormat format_;
+  media::AudioParameters audio_param_;
+
+  DISALLOW_COPY_AND_ASSIGN(AudioDeviceOwner);
+};
+
+}  // namespace assistant
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_ASSISTANT_PLATFORM_AUDIO_OUTPUT_PROVIDER_IMPL_H_
diff --git a/chromeos/services/assistant/platform/audio_output_provider_impl_unittest.cc b/chromeos/services/assistant/platform/audio_output_provider_impl_unittest.cc
new file mode 100644
index 0000000..ca15648
--- /dev/null
+++ b/chromeos/services/assistant/platform/audio_output_provider_impl_unittest.cc
@@ -0,0 +1,125 @@
+// 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/services/assistant/platform/audio_output_provider_impl.h"
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread.h"
+#include "libassistant/shared/public/platform_audio_output.h"
+#include "media/base/audio_bus.h"
+#include "media/base/bind_to_current_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+namespace assistant {
+
+class FakeAudioOutputDelegate : public assistant_client::AudioOutput::Delegate {
+ public:
+  FakeAudioOutputDelegate() : thread_("assistant") { thread_.Start(); }
+
+  ~FakeAudioOutputDelegate() override = default;
+
+  // assistant_client::AudioOutput::Delegate overrides:
+  void FillBuffer(void* buffer,
+                  int buffer_size,
+                  int64_t playback_timestamp,
+                  assistant_client::Callback1<int> done_cb) override {
+    // Fill some arbitrary stuff.
+    memset(reinterpret_cast<uint8_t*>(buffer), '1', num_bytes_to_fill_);
+    int filled_bytes = num_bytes_to_fill_;
+    num_bytes_to_fill_ = 0;
+
+    // We'll need to maintain the multi-threaded async semantics as the real
+    // assistant. Otherwise, it'll cause re-entrance of locks.
+    thread_.task_runner()->PostTask(
+        FROM_HERE, base::BindOnce(&FakeAudioOutputDelegate::FillBufferDone,
+                                  base::Unretained(this), std::move(done_cb),
+                                  filled_bytes));
+  }
+
+  void OnEndOfStream() override { end_of_stream_ = true; }
+
+  void OnError(assistant_client::AudioOutput::Error error) override {}
+
+  void OnStopped() override {}
+
+  void FillBufferDone(assistant_client::Callback1<int> cb, int num_bytes) {
+    cb(num_bytes);
+    quit_closure_.Run();
+  }
+
+  bool end_of_stream() { return end_of_stream_; }
+
+  void set_num_of_bytes_to_fill(int bytes) { num_bytes_to_fill_ = bytes; }
+
+  void Reset() {
+    run_loop_.reset(new base::RunLoop());
+    quit_closure_ = media::BindToCurrentLoop(run_loop_->QuitClosure());
+  }
+
+  void Wait() { run_loop_->Run(); }
+
+ private:
+  base::Thread thread_;
+  base::RepeatingClosure quit_closure_;
+  std::unique_ptr<base::RunLoop> run_loop_;
+  int num_bytes_to_fill_ = 0;
+  bool end_of_stream_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeAudioOutputDelegate);
+};
+
+class AudioDeviceOwnerTest : public testing::Test {
+ public:
+  AudioDeviceOwnerTest()
+      : task_env_(base::test::ScopedTaskEnvironment::MainThreadType::DEFAULT,
+                  base::test::ScopedTaskEnvironment::ExecutionMode::QUEUED) {}
+
+  ~AudioDeviceOwnerTest() override { task_env_.RunUntilIdle(); }
+
+ private:
+  base::test::ScopedTaskEnvironment task_env_;
+
+  DISALLOW_COPY_AND_ASSIGN(AudioDeviceOwnerTest);
+};
+
+TEST_F(AudioDeviceOwnerTest, BufferFilling) {
+  FakeAudioOutputDelegate delegate;
+  auto audio_bus = media::AudioBus::Create(2, 4480);
+  assistant_client::OutputStreamFormat format{
+      assistant_client::OutputStreamEncoding::STREAM_PCM_S16,
+      44800,  // pcm_sample rate.
+      2       // pcm_num_channels,
+  };
+
+  delegate.set_num_of_bytes_to_fill(200);
+  delegate.Reset();
+  auto owner =
+      std::make_unique<AudioDeviceOwner>(base::ThreadTaskRunnerHandle::Get());
+  // Upon start, it will start to fill the buffer.
+  owner->StartOnMainThread(&delegate, nullptr, format);
+  delegate.Wait();
+
+  delegate.Reset();
+  audio_bus->Zero();
+  // On first render, it will push the data to |audio_bus|. The fill should
+  // stop by now.
+  owner->Render(base::TimeDelta::FromMicroseconds(0), base::TimeTicks::Now(), 0,
+                audio_bus.get());
+  delegate.Wait();
+  EXPECT_FALSE(audio_bus->AreFramesZero());
+  EXPECT_FALSE(delegate.end_of_stream());
+
+  // The subsequent Render call will detect no data available and notify
+  // delegate for OnEndOfStream().
+  owner->Render(base::TimeDelta::FromMicroseconds(0), base::TimeTicks::Now(), 0,
+                audio_bus.get());
+  EXPECT_TRUE(delegate.end_of_stream());
+}
+
+}  // namespace assistant
+}  // namespace chromeos
diff --git a/chromeos/services/assistant/platform_api_impl.cc b/chromeos/services/assistant/platform_api_impl.cc
index 94de7bb..654235c 100644
--- a/chromeos/services/assistant/platform_api_impl.cc
+++ b/chromeos/services/assistant/platform_api_impl.cc
@@ -76,7 +76,7 @@
     device::mojom::BatteryMonitorPtr battery_monitor,
     bool enable_hotword)
     : audio_input_provider_(connector, enable_hotword),
-      audio_output_provider_(CreateLibAssistantConfig(!enable_hotword), this),
+      audio_output_provider_(connector),
       system_provider_(std::move(battery_monitor)) {}
 
 PlatformApiImpl::~PlatformApiImpl() = default;
diff --git a/chromeos/services/assistant/platform_api_impl.h b/chromeos/services/assistant/platform_api_impl.h
index 06d473e..1a9af2c2 100644
--- a/chromeos/services/assistant/platform_api_impl.h
+++ b/chromeos/services/assistant/platform_api_impl.h
@@ -11,14 +11,11 @@
 #include <vector>
 
 #include "chromeos/services/assistant/platform/audio_input_provider_impl.h"
+#include "chromeos/services/assistant/platform/audio_output_provider_impl.h"
 #include "chromeos/services/assistant/platform/file_provider_impl.h"
 #include "chromeos/services/assistant/platform/network_provider_impl.h"
 #include "chromeos/services/assistant/platform/system_provider_impl.h"
 #include "chromeos/services/assistant/public/mojom/assistant.mojom.h"
-// TODO(xiaohuic): replace with "base/macros.h" once we remove
-// libassistant/contrib dependency.
-#include "libassistant/contrib/core/macros.h"
-#include "libassistant/contrib/platform/audio/output/audio_output_provider_impl.h"
 #include "libassistant/shared/public/platform_api.h"
 #include "libassistant/shared/public/platform_auth.h"
 #include "services/device/public/mojom/battery_monitor.mojom.h"
@@ -86,7 +83,7 @@
   };
 
   AudioInputProviderImpl audio_input_provider_;
-  assistant_contrib::AudioOutputProviderImpl audio_output_provider_;
+  AudioOutputProviderImpl audio_output_provider_;
   DummyAuthProvider auth_provider_;
   FileProviderImpl file_provider_;
   NetworkProviderImpl network_provider_;
diff --git a/components/autofill/core/browser/ui/save_card_bubble_controller.h b/components/autofill/core/browser/ui/save_card_bubble_controller.h
index 371fc523..9bbae4c 100644
--- a/components/autofill/core/browser/ui/save_card_bubble_controller.h
+++ b/components/autofill/core/browser/ui/save_card_bubble_controller.h
@@ -14,10 +14,13 @@
 #include "components/signin/core/browser/account_info.h"
 #include "url/gurl.h"
 
+class Profile;
+
 namespace autofill {
 
 class CreditCard;
 class SaveCardBubbleView;
+enum class BubbleType;
 
 // Interface that exposes controller functionality to SaveCardBubbleView.
 class SaveCardBubbleController {
@@ -35,6 +38,9 @@
   // Returns the account info of the signed-in user.
   virtual const AccountInfo& GetAccountInfo() const = 0;
 
+  // Returns the profile.
+  virtual Profile* GetProfile() const = 0;
+
   // Returns the card that will be uploaded if the user accepts.
   virtual const CreditCard& GetCard() const = 0;
 
@@ -42,19 +48,30 @@
   // to confirm/provide cardholder name.
   virtual bool ShouldRequestNameFromUser() const = 0;
 
+  // Returns whether or not a sign in / sync promo needs to be shown.
+  virtual bool ShouldShowSignInPromo() const = 0;
+
   // Interaction.
+  // OnSyncPromoAccepted is called when the Dice Sign-in promo is clicked.
+  virtual void OnSyncPromoAccepted(const AccountInfo& account,
+                                   bool is_default_promo_account) = 0;
   // OnSaveButton takes in a string value representing the cardholder name
   // confirmed/entered by the user if it was requested, or an empty string
   // otherwise.
   virtual void OnSaveButton(const base::string16& cardholder_name) = 0;
   virtual void OnCancelButton() = 0;
   virtual void OnLegalMessageLinkClicked(const GURL& url) = 0;
+  virtual void OnManageCardsClicked() = 0;
   virtual void OnBubbleClosed() = 0;
 
   // State.
 
   // Returns empty vector if no legal message should be shown.
   virtual const LegalMessageLines& GetLegalMessageLines() const = 0;
+  // Returns true iff is showing or has showed bubble for upload save.
+  virtual bool IsUploadSave() const = 0;
+  // Returns the current state of the bubble.
+  virtual BubbleType GetBubbleType() const = 0;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(SaveCardBubbleController);
diff --git a/components/autofill/core/common/autofill_features.cc b/components/autofill/core/common/autofill_features.cc
index 73772f97..f4c8a8f 100644
--- a/components/autofill/core/common/autofill_features.cc
+++ b/components/autofill/core/common/autofill_features.cc
@@ -90,6 +90,11 @@
     "AutofillRestrictUnownedFieldsToFormlessCheckout",
     base::FEATURE_ENABLED_BY_DEFAULT};
 
+// When enabled, a sign in promo will show up right after the user
+// saves a card locally. This also introduces a "Manage Cards" bubble.
+const base::Feature kAutofillSaveCardSignInAfterLocalSave{
+    "AutofillSaveCardSignInAfterLocalSave", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Controls whether experiment ids should be sent through
 // Google Payments RPCs or not.
 const base::Feature kAutofillSendExperimentIdsInPaymentsRPCs{
diff --git a/components/autofill/core/common/autofill_features.h b/components/autofill/core/common/autofill_features.h
index 7da46eb4..fffbaf7 100644
--- a/components/autofill/core/common/autofill_features.h
+++ b/components/autofill/core/common/autofill_features.h
@@ -24,6 +24,7 @@
 extern const base::Feature kAutofillPrefilledFields;
 extern const base::Feature kAutofillResetFullServerCardsOnAuthError;
 extern const base::Feature kAutofillRestrictUnownedFieldsToFormlessCheckout;
+extern const base::Feature kAutofillSaveCardSignInAfterLocalSave;
 extern const base::Feature kAutofillSendExperimentIdsInPaymentsRPCs;
 extern const base::Feature kAutofillSendOnlyCountryInGetUploadDetails;
 extern const base::Feature kAutofillShowAllSuggestionsOnPrefilledForms;
diff --git a/components/autofill_strings.grdp b/components/autofill_strings.grdp
index d404914..30c8d43f1 100644
--- a/components/autofill_strings.grdp
+++ b/components/autofill_strings.grdp
@@ -23,6 +23,24 @@
   <message name="IDS_AUTOFILL_CREDIT_CARD_SIGNIN_PROMO" desc="Promo text shown alongside credit card suggestions to get people to sign in.">
     To use cards from your Google Account, sign in to Chrome
   </message>
+  <message name="IDS_AUTOFILL_SIGNIN_PROMO_MESSAGE" desc="Promo message for Sign-in Promo bubble (shown after a local save) to get people to sign in.">
+    To get your cards on all devices, sign in and turn on sync.
+  </message>
+  <message name="IDS_AUTOFILL_SYNC_PROMO_MESSAGE" desc="Promo message for Sign-in Promo bubble (shown after a local save) to get people to sync.">
+    To get your cards on all devices, turn on sync.
+  </message>
+  <message name="IDS_AUTOFILL_SIGNIN_PROMO_MESSAGE_DICE_DISABLED" desc="Text of the sign in promo bubble and manage cards footnote.">
+    To get your cards on all your devices, <ph name="SIGN_IN_LINK">$1<ex>sign in</ex></ph>.
+  </message>
+  <message name="IDS_AUTOFILL_SYNC_PROMO_MESSAGE_DICE_DISABLED" desc="Text of the sync promo bubble and manage cards footnote.">
+    To get your cards on all your devices, <ph name="SYNC_LINK">$1<ex>turn on sync</ex></ph>.
+  </message>
+  <message name="IDS_AUTOFILL_SIGNIN_PROMO_LINK_DICE_DISABLED" desc="Text of the link to sign in from the save card sync promo.">
+    sign in to Chrome
+  </message>
+  <message name="IDS_AUTOFILL_SYNC_PROMO_LINK_DICE_DISABLED" desc="Text of the link to sync from the save card sync promo.">
+    turn on sync
+  </message>
   <if expr="_google_chrome">
     <message name="IDS_AUTOFILL_DELETE_AUTOCOMPLETE_SUGGESTION_CONFIRMATION_BODY" desc="Text in a dialog to confirm that the user wants to delete an autocomplete form history suggestion.">
       Remove form suggestion from Chrome?
@@ -198,6 +216,15 @@
   <message name="IDS_AUTOFILL_SAVE_CARD_PROMPT_TITLE_TO_CLOUD_V3" desc="Title text for the Autofill save card prompt when the card is to be saved by uploading it to Google Payments, according to January 2018 UI guidelines. The prompt can be either a bubble or an infobar.">
     Save card?
   </message>
+  <message name="IDS_AUTOFILL_CARD_SAVED" desc="Title text for the Autofill save card manager bubble when the card has been saved either locally or to Google Payments. Also label for icon animation.">
+    Card saved
+  </message>
+  <message name="IDS_AUTOFILL_MANAGE_CARDS" desc="Text to show for the button in the save card manager bubble when the card has been saved either locally or to Google Payments.">
+    Manage cards
+  </message>
+  <message name="IDS_AUTOFILL_DONE" desc="Text to show for the button in the save card manager bubble when the card has been saved either locally or to Google Payments.">
+    Done
+  </message>
   <message name="IDS_AUTOFILL_SAVE_CARD_PROMPT_UPLOAD_EXPLANATION" desc="Explanation of the effect of the Autofill save card prompt when the card is to be saved by uploading it to Google Payments and also saved locally. The prompt can be either a bubble or an infobar.">
     Pay quickly on sites and apps across devices using cards you have saved with Google.
   </message>
diff --git a/components/autofill_strings_grdp/IDS_AUTOFILL_CARD_SAVED.png.sha1 b/components/autofill_strings_grdp/IDS_AUTOFILL_CARD_SAVED.png.sha1
new file mode 100644
index 0000000..575f505
--- /dev/null
+++ b/components/autofill_strings_grdp/IDS_AUTOFILL_CARD_SAVED.png.sha1
@@ -0,0 +1 @@
+19972b6ebc9586ca80abaabcd65a54fa41092beb
\ No newline at end of file
diff --git a/components/autofill_strings_grdp/IDS_AUTOFILL_DONE.png.sha1 b/components/autofill_strings_grdp/IDS_AUTOFILL_DONE.png.sha1
new file mode 100644
index 0000000..575f505
--- /dev/null
+++ b/components/autofill_strings_grdp/IDS_AUTOFILL_DONE.png.sha1
@@ -0,0 +1 @@
+19972b6ebc9586ca80abaabcd65a54fa41092beb
\ No newline at end of file
diff --git a/components/autofill_strings_grdp/IDS_AUTOFILL_MANAGE_CARDS.png.sha1 b/components/autofill_strings_grdp/IDS_AUTOFILL_MANAGE_CARDS.png.sha1
new file mode 100644
index 0000000..575f505
--- /dev/null
+++ b/components/autofill_strings_grdp/IDS_AUTOFILL_MANAGE_CARDS.png.sha1
@@ -0,0 +1 @@
+19972b6ebc9586ca80abaabcd65a54fa41092beb
\ No newline at end of file
diff --git a/components/autofill_strings_grdp/IDS_AUTOFILL_SIGNIN_PROMO_LINK_DICE_DISABLED.png.sha1 b/components/autofill_strings_grdp/IDS_AUTOFILL_SIGNIN_PROMO_LINK_DICE_DISABLED.png.sha1
new file mode 100644
index 0000000..271d295
--- /dev/null
+++ b/components/autofill_strings_grdp/IDS_AUTOFILL_SIGNIN_PROMO_LINK_DICE_DISABLED.png.sha1
@@ -0,0 +1 @@
+8b8a9d5c99f2660d412ce39345e67f3aa81b5f73
\ No newline at end of file
diff --git a/components/autofill_strings_grdp/IDS_AUTOFILL_SIGNIN_PROMO_MESSAGE.png.sha1 b/components/autofill_strings_grdp/IDS_AUTOFILL_SIGNIN_PROMO_MESSAGE.png.sha1
new file mode 100644
index 0000000..fbb4909e
--- /dev/null
+++ b/components/autofill_strings_grdp/IDS_AUTOFILL_SIGNIN_PROMO_MESSAGE.png.sha1
@@ -0,0 +1 @@
+a89c37b7304b73ad2457c4257ba5490bfc85e77e
\ No newline at end of file
diff --git a/components/autofill_strings_grdp/IDS_AUTOFILL_SIGNIN_PROMO_MESSAGE_DICE_DISABLED.png.sha1 b/components/autofill_strings_grdp/IDS_AUTOFILL_SIGNIN_PROMO_MESSAGE_DICE_DISABLED.png.sha1
new file mode 100644
index 0000000..271d295
--- /dev/null
+++ b/components/autofill_strings_grdp/IDS_AUTOFILL_SIGNIN_PROMO_MESSAGE_DICE_DISABLED.png.sha1
@@ -0,0 +1 @@
+8b8a9d5c99f2660d412ce39345e67f3aa81b5f73
\ No newline at end of file
diff --git a/components/autofill_strings_grdp/IDS_AUTOFILL_SYNC_PROMO_MESSAGE.png.sha1 b/components/autofill_strings_grdp/IDS_AUTOFILL_SYNC_PROMO_MESSAGE.png.sha1
new file mode 100644
index 0000000..9143fbe0
--- /dev/null
+++ b/components/autofill_strings_grdp/IDS_AUTOFILL_SYNC_PROMO_MESSAGE.png.sha1
@@ -0,0 +1 @@
+858dc993349162f2ec90e3c4baadf862e60c0047
\ No newline at end of file
diff --git a/components/download/database/download_db_conversions.cc b/components/download/database/download_db_conversions.cc
index 17c2d6c..cbb39371 100644
--- a/components/download/database/download_db_conversions.cc
+++ b/components/download/database/download_db_conversions.cc
@@ -311,4 +311,22 @@
   return proto;
 }
 
+DownloadDBEntry DownloadDBConversions::DownloadDBEntryFromDownloadEntry(
+    const DownloadEntry& entry) {
+  DownloadDBEntry db_entry;
+  DownloadInfo download_info;
+  download_info.guid = entry.guid;
+
+  UkmInfo ukm_info(entry.download_source, entry.ukm_download_id);
+
+  InProgressInfo in_progress_info;
+  in_progress_info.fetch_error_body = entry.fetch_error_body;
+  in_progress_info.request_headers = entry.request_headers;
+
+  download_info.ukm_info = ukm_info;
+  download_info.in_progress_info = in_progress_info;
+  db_entry.download_info = download_info;
+  return db_entry;
+}
+
 }  // namespace download
diff --git a/components/download/database/download_db_conversions.h b/components/download/database/download_db_conversions.h
index 600e6196..a113d86e 100644
--- a/components/download/database/download_db_conversions.h
+++ b/components/download/database/download_db_conversions.h
@@ -64,6 +64,9 @@
 
   static DownloadDBEntry DownloadDBEntryFromProto(
       const download_pb::DownloadDBEntry& proto);
+
+  static DownloadDBEntry DownloadDBEntryFromDownloadEntry(
+      const DownloadEntry& entry);
 };
 
 }  // namespace download
diff --git a/components/download/database/in_progress/in_progress_cache.h b/components/download/database/in_progress/in_progress_cache.h
index 705e371..ddd67e49 100644
--- a/components/download/database/in_progress/in_progress_cache.h
+++ b/components/download/database/in_progress/in_progress_cache.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_DOWNLOAD_DATABASE_IN_PROGRESS_IN_PROGRESS_CACHE_H_
 
 #include <string>
+#include <vector>
 
 #include "base/optional.h"
 #include "components/download/database/in_progress/download_entry.h"
@@ -34,6 +35,9 @@
 
   // Removes an entry.
   virtual void RemoveEntry(const std::string& guid) = 0;
+
+  // Returns all entries.
+  virtual std::vector<DownloadEntry> GetAllEntries() = 0;
 };
 
 }  // namespace download
diff --git a/components/download/database/in_progress/in_progress_cache_impl.cc b/components/download/database/in_progress/in_progress_cache_impl.cc
index 6a662ab..a5e2dd98 100644
--- a/components/download/database/in_progress/in_progress_cache_impl.cc
+++ b/components/download/database/in_progress/in_progress_cache_impl.cc
@@ -212,4 +212,12 @@
                                                    entries_string, file_path_));
 }
 
+std::vector<DownloadEntry> InProgressCacheImpl::GetAllEntries() {
+  if (initialization_status_ != CACHE_INITIALIZED) {
+    LOG(ERROR) << "Cache is not initialized, cannot get all entries.";
+    return std::vector<DownloadEntry>();
+  }
+  return DownloadDBConversions::DownloadEntriesFromProto(entries_);
+}
+
 }  // namespace download
diff --git a/components/download/database/in_progress/in_progress_cache_impl.h b/components/download/database/in_progress/in_progress_cache_impl.h
index e3218c7..64503c9e 100644
--- a/components/download/database/in_progress/in_progress_cache_impl.h
+++ b/components/download/database/in_progress/in_progress_cache_impl.h
@@ -33,6 +33,7 @@
   void AddOrReplaceEntry(const DownloadEntry& entry) override;
   base::Optional<DownloadEntry> RetrieveEntry(const std::string& guid) override;
   void RemoveEntry(const std::string& guid) override;
+  std::vector<DownloadEntry> GetAllEntries() override;
 
  private:
   // States to keep track of initialization status.
diff --git a/components/download/internal/common/download_db_cache.cc b/components/download/internal/common/download_db_cache.cc
index 4dd6d46..abc824bc 100644
--- a/components/download/internal/common/download_db_cache.cc
+++ b/components/download/internal/common/download_db_cache.cc
@@ -5,7 +5,9 @@
 #include "components/download/internal/common/download_db_cache.h"
 
 #include "components/download/database/download_db.h"
+#include "components/download/database/download_db_conversions.h"
 #include "components/download/database/download_db_entry.h"
+#include "components/download/database/in_progress/download_entry.h"
 #include "components/download/public/common/download_utils.h"
 
 namespace download {
@@ -85,18 +87,11 @@
   return in_progress_info->request_headers;
 }
 
-DownloadSource GetDownloadSource(base::Optional<DownloadDBEntry> entry) {
-  if (!entry)
-    return DownloadSource::UNKNOWN;
+UkmInfo GetUkmInfo(base::Optional<DownloadDBEntry> entry) {
+  if (entry && entry->download_info && entry->download_info->ukm_info)
+    return entry->download_info->ukm_info.value();
 
-  if (!entry->download_info)
-    return DownloadSource::UNKNOWN;
-
-  base::Optional<UkmInfo>& ukm_info = entry->download_info->ukm_info;
-  if (!ukm_info)
-    return DownloadSource::UNKNOWN;
-
-  return ukm_info->download_source;
+  return UkmInfo(DownloadSource::UNKNOWN, GetUniqueDownloadId());
 }
 
 void CleanUpInProgressEntry(DownloadDBEntry& entry) {
@@ -201,9 +196,9 @@
   bool fetch_error_body = GetFetchErrorBody(current);
   DownloadUrlParameters::RequestHeadersType request_header_type =
       GetRequestHeadersType(current);
-  DownloadSource download_source = GetDownloadSource(current);
+  UkmInfo ukm_info = GetUkmInfo(current);
   DownloadDBEntry entry = CreateDownloadDBEntryFromItem(
-      *download, download_source, fetch_error_body, request_header_type);
+      *download, ukm_info, fetch_error_body, request_header_type);
   AddOrReplaceEntry(entry);
 }
 
@@ -240,4 +235,17 @@
   update_timer_.SetTaskRunner(task_runner);
 }
 
+void DownloadDBCache::MigrateFromInProgressCache(
+    const std::vector<DownloadEntry>& entries) {
+  DCHECK(initialized_);
+  DCHECK(entries_.empty());
+  std::vector<DownloadDBEntry> db_entries;
+  for (const auto& entry : entries) {
+    entries_[entry.guid] =
+        DownloadDBConversions::DownloadDBEntryFromDownloadEntry(entry);
+    db_entries.emplace_back(entries_[entry.guid]);
+  }
+  download_db_->AddOrReplaceEntries(db_entries);
+}
+
 }  //  namespace download
diff --git a/components/download/internal/common/download_db_cache.h b/components/download/internal/common/download_db_cache.h
index edf0ed7..e4a7dbb 100644
--- a/components/download/internal/common/download_db_cache.h
+++ b/components/download/internal/common/download_db_cache.h
@@ -21,6 +21,7 @@
 
 class DownloadDB;
 struct DownloadDBEntry;
+struct DownloadEntry;
 
 // Responsible for caching the metadata of all in progress downloads.
 class COMPONENTS_DOWNLOAD_EXPORT DownloadDBCache
@@ -39,6 +40,9 @@
   // Remove an entry from the DownloadDB.
   void RemoveEntry(const std::string& guid);
 
+  // Migrate DownloadEntry from in-progress cache.
+  void MigrateFromInProgressCache(const std::vector<DownloadEntry>& entries);
+
  private:
   friend class DownloadDBCacheTest;
   friend class InProgressDownloadManager;
diff --git a/components/download/internal/common/download_db_cache_unittest.cc b/components/download/internal/common/download_db_cache_unittest.cc
index 9c1ed9b4f..6e19335 100644
--- a/components/download/internal/common/download_db_cache_unittest.cc
+++ b/components/download/internal/common/download_db_cache_unittest.cc
@@ -14,11 +14,17 @@
 #include "components/download/database/download_db_conversions.h"
 #include "components/download/database/download_db_entry.h"
 #include "components/download/database/download_db_impl.h"
+#include "components/download/database/in_progress/download_entry.h"
+#include "components/download/public/common/download_item_impl.h"
+#include "components/download/public/common/download_utils.h"
+#include "components/download/public/common/mock_download_item.h"
 #include "components/leveldb_proto/testing/fake_db.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using testing::_;
+using testing::Return;
+using testing::ReturnRefOfCopy;
 
 namespace download {
 
@@ -38,6 +44,44 @@
          "," + guid;
 }
 
+std::unique_ptr<DownloadItem> CreateDownloadItem(const std::string& guid) {
+  std::unique_ptr<MockDownloadItem> item(
+      new ::testing::NiceMock<MockDownloadItem>());
+  ON_CALL(*item, GetGuid()).WillByDefault(ReturnRefOfCopy(guid));
+  ON_CALL(*item, GetUrlChain())
+      .WillByDefault(ReturnRefOfCopy(std::vector<GURL>()));
+  ON_CALL(*item, GetReferrerUrl()).WillByDefault(ReturnRefOfCopy(GURL()));
+  ON_CALL(*item, GetSiteUrl()).WillByDefault(ReturnRefOfCopy(GURL()));
+  ON_CALL(*item, GetTabUrl()).WillByDefault(ReturnRefOfCopy(GURL()));
+  ON_CALL(*item, GetTabReferrerUrl()).WillByDefault(ReturnRefOfCopy(GURL()));
+  ON_CALL(*item, GetETag()).WillByDefault(ReturnRefOfCopy(std::string("etag")));
+  ON_CALL(*item, GetLastModifiedTime())
+      .WillByDefault(ReturnRefOfCopy(std::string("last-modified")));
+  ON_CALL(*item, GetMimeType()).WillByDefault(Return("text/html"));
+  ON_CALL(*item, GetOriginalMimeType()).WillByDefault(Return("text/html"));
+  ON_CALL(*item, GetTotalBytes()).WillByDefault(Return(1000));
+  ON_CALL(*item, GetFullPath())
+      .WillByDefault(ReturnRefOfCopy(base::FilePath()));
+  ON_CALL(*item, GetTargetFilePath())
+      .WillByDefault(ReturnRefOfCopy(base::FilePath()));
+  ON_CALL(*item, GetReceivedBytes()).WillByDefault(Return(1000));
+  ON_CALL(*item, GetStartTime()).WillByDefault(Return(base::Time()));
+  ON_CALL(*item, GetEndTime()).WillByDefault(Return(base::Time()));
+  ON_CALL(*item, GetReceivedSlices())
+      .WillByDefault(
+          ReturnRefOfCopy(std::vector<DownloadItem::ReceivedSlice>()));
+  ON_CALL(*item, GetHash()).WillByDefault(ReturnRefOfCopy(std::string("hash")));
+  ON_CALL(*item, IsTransient()).WillByDefault(Return(false));
+  ON_CALL(*item, GetDangerType())
+      .WillByDefault(Return(DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS));
+  ON_CALL(*item, GetLastReason())
+      .WillByDefault(Return(DOWNLOAD_INTERRUPT_REASON_NONE));
+  ON_CALL(*item, GetState()).WillByDefault(Return(DownloadItem::IN_PROGRESS));
+  ON_CALL(*item, IsPaused()).WillByDefault(Return(false));
+  ON_CALL(*item, GetBytesWasted()).WillByDefault(Return(10));
+  return std::move(item);
+}
+
 }  // namespace
 
 class DownloadDBCacheTest : public testing::Test {
@@ -82,6 +126,10 @@
 
   DownloadDB* GetDownloadDB() { return db_cache_->download_db_.get(); }
 
+  void OnDownloadUpdated(DownloadItem* item) {
+    db_cache_->OnDownloadUpdated(item);
+  }
+
  protected:
   std::map<std::string, download_pb::DownloadDBEntry> db_entries_;
   leveldb_proto::test::FakeDB<download_pb::DownloadDBEntry>* db_;
@@ -233,4 +281,52 @@
   ASSERT_EQ(guid2, loaded_entries[0].GetGuid());
 }
 
+// Tests that Migrating a DownloadEntry from InProgressCache should store
+// a DownloadDBEntry in the DownloadDB.
+TEST_F(DownloadDBCacheTest, MigrateFromInProgressCache) {
+  CreateDBCache();
+  std::vector<DownloadDBEntry> loaded_entries;
+  db_cache_->Initialize(base::BindOnce(&DownloadDBCacheTest::InitCallback,
+                                       base::Unretained(this), &loaded_entries,
+                                       true));
+  db_->InitCallback(true);
+  db_->LoadCallback(true);
+  ASSERT_TRUE(loaded_entries.empty());
+
+  std::vector<DownloadEntry> download_entries;
+  download_entries.emplace_back(
+      "guid1", "foo.com", DownloadSource::DRAG_AND_DROP, true,
+      DownloadUrlParameters::RequestHeadersType(), 100);
+  download_entries.emplace_back(
+      "guid2", "foobar.com", DownloadSource::UNKNOWN, false,
+      DownloadUrlParameters::RequestHeadersType(), 200);
+
+  db_cache_->MigrateFromInProgressCache(download_entries);
+  db_->UpdateCallback(true);
+
+  std::unique_ptr<DownloadItem> item = CreateDownloadItem("guid1");
+  OnDownloadUpdated(item.get());
+
+  ASSERT_EQ(task_runner_->GetPendingTaskCount(), 1u);
+  ASSERT_GT(task_runner_->NextPendingTaskDelay(), base::TimeDelta());
+  task_runner_->FastForwardUntilNoTasksRemain();
+  db_->UpdateCallback(true);
+
+  DownloadDBEntry entry1 = CreateDownloadDBEntryFromItem(
+      *item, UkmInfo(DownloadSource::DRAG_AND_DROP, 100), true,
+      DownloadUrlParameters::RequestHeadersType());
+  DownloadDBEntry entry2 =
+      DownloadDBConversions::DownloadDBEntryFromDownloadEntry(
+          download_entries[1]);
+
+  DownloadDB* download_db = GetDownloadDB();
+  download_db->LoadEntries(base::BindOnce(&DownloadDBCacheTest::InitCallback,
+                                          base::Unretained(this),
+                                          &loaded_entries));
+  db_->LoadCallback(true);
+  ASSERT_EQ(loaded_entries.size(), 2u);
+  ASSERT_TRUE(entry1 == loaded_entries[0]);
+  ASSERT_TRUE(entry2 == loaded_entries[1]);
+}
+
 }  // namespace download
diff --git a/components/download/internal/common/download_utils.cc b/components/download/internal/common/download_utils.cc
index efd6eef..a7d61b0 100644
--- a/components/download/internal/common/download_utils.cc
+++ b/components/download/internal/common/download_utils.cc
@@ -361,7 +361,7 @@
 
 DownloadDBEntry CreateDownloadDBEntryFromItem(
     const DownloadItem& item,
-    DownloadSource download_source,
+    const UkmInfo& ukm_info,
     bool fetch_error_body,
     const DownloadUrlParameters::RequestHeadersType& request_headers) {
   DownloadDBEntry entry;
@@ -397,7 +397,6 @@
 
   download_info.in_progress_info = in_progress_info;
 
-  UkmInfo ukm_info(download_source, GetUniqueDownloadId());
   download_info.ukm_info = ukm_info;
   entry.download_info = download_info;
   return entry;
diff --git a/components/download/internal/common/in_progress_download_manager.cc b/components/download/internal/common/in_progress_download_manager.cc
index ee99a08..55519c67 100644
--- a/components/download/internal/common/in_progress_download_manager.cc
+++ b/components/download/internal/common/in_progress_download_manager.cc
@@ -37,6 +37,10 @@
   if (!entry.download_info)
     return nullptr;
 
+  // DownloadDBEntry migrated from in-progress cache doesn't have Ids.
+  if (!entry.download_info->id)
+    return nullptr;
+
   base::Optional<InProgressInfo> in_progress_info =
       entry.download_info->in_progress_info;
   if (!in_progress_info)
@@ -452,8 +456,8 @@
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
           switches::kEnableDownloadDB)) {
     download_db_cache_->AddOrReplaceEntry(CreateDownloadDBEntryFromItem(
-        *download, info->download_source, info->fetch_error_body,
-        info->request_headers));
+        *download, UkmInfo(info->download_source, GetUniqueDownloadId()),
+        info->fetch_error_body, info->request_headers));
     download->RemoveObserver(download_db_cache_.get());
     download->AddObserver(download_db_cache_.get());
   } else {
@@ -492,6 +496,8 @@
     std::unique_ptr<std::vector<DownloadDBEntry>> entries) {
   for (const auto& entry : *entries) {
     auto item = CreateDownloadItemImpl(this, entry);
+    if (!item)
+      continue;
     item->AddObserver(download_db_cache_.get());
     in_progress_downloads_.emplace_back(std::move(item));
   }
@@ -525,4 +531,5 @@
 InProgressDownloadManager::TakeInProgressDownloads() {
   return std::move(in_progress_downloads_);
 }
+
 }  // namespace download
diff --git a/components/download/public/common/download_utils.h b/components/download/public/common/download_utils.h
index b7d56e4..2f8bf72 100644
--- a/components/download/public/common/download_utils.h
+++ b/components/download/public/common/download_utils.h
@@ -72,7 +72,7 @@
 // Helper functions for DownloadItem -> DownloadDBEntry for DownloadDB.
 COMPONENTS_DOWNLOAD_EXPORT DownloadDBEntry CreateDownloadDBEntryFromItem(
     const DownloadItem& item,
-    DownloadSource download_source,
+    const UkmInfo& ukm_info,
     bool fetch_error_body,
     const DownloadUrlParameters::RequestHeadersType& request_headers);
 
diff --git a/components/exo/test/exo_test_base.cc b/components/exo/test/exo_test_base.cc
index e612b3ab..0905ebd 100644
--- a/components/exo/test/exo_test_base.cc
+++ b/components/exo/test/exo_test_base.cc
@@ -8,6 +8,7 @@
 #include "components/exo/test/exo_test_helper.h"
 #include "components/exo/test/test_client_controlled_state_delegate.h"
 #include "components/exo/wm_helper.h"
+#include "ui/base/ime/input_method_factory.h"
 #include "ui/wm/core/wm_core_switches.h"
 
 namespace exo {
@@ -24,6 +25,7 @@
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
   // Disable window animation when running tests.
   command_line->AppendSwitch(wm::switches::kWindowAnimationsDisabled);
+  ui::SetUpInputMethodFactoryForTesting();
   AshTestBase::SetUp();
   wm_helper_ = std::make_unique<WMHelper>();
   WMHelper::SetInstance(wm_helper_.get());
diff --git a/components/exo/text_input_unittest.cc b/components/exo/text_input_unittest.cc
index a83ff8f..658519c9 100644
--- a/components/exo/text_input_unittest.cc
+++ b/components/exo/text_input_unittest.cc
@@ -13,7 +13,6 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "ui/aura/window_tree_host.h"
 #include "ui/base/ime/composition_text.h"
-#include "ui/base/ime/input_method_factory.h"
 #include "ui/base/ime/input_method_observer.h"
 #include "ui/views/widget/widget.h"
 
@@ -74,7 +73,6 @@
   TextInputTest() = default;
 
   void SetUp() override {
-    ui::SetUpInputMethodFactoryForTesting();
     test::ExoTestBase::SetUp();
     text_input_ =
         std::make_unique<TextInput>(std::make_unique<MockTextInputDelegate>());
diff --git a/components/metrics/net/network_metrics_provider.cc b/components/metrics/net/network_metrics_provider.cc
index 1529a47..8874c5d 100644
--- a/components/metrics/net/network_metrics_provider.cc
+++ b/components/metrics/net/network_metrics_provider.cc
@@ -126,7 +126,7 @@
       min_effective_connection_type_(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN),
       max_effective_connection_type_(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN),
       weak_ptr_factory_(this) {
-  net::NetworkChangeNotifier::AddConnectionTypeObserver(this);
+  net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
   connection_type_ = net::NetworkChangeNotifier::GetConnectionType();
   if (connection_type_ != net::NetworkChangeNotifier::CONNECTION_UNKNOWN)
     network_change_notifier_initialized_ = true;
@@ -157,7 +157,7 @@
 
 NetworkMetricsProvider::~NetworkMetricsProvider() {
   DCHECK(thread_checker_.CalledOnValidThread());
-  net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
+  net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
 
   if (network_quality_estimator_provider_) {
     scoped_refptr<base::SequencedTaskRunner> network_quality_task_runner =
@@ -232,7 +232,7 @@
     WriteWifiAccessPointProto(info, network);
 }
 
-void NetworkMetricsProvider::OnConnectionTypeChanged(
+void NetworkMetricsProvider::OnNetworkChanged(
     net::NetworkChangeNotifier::ConnectionType type) {
   DCHECK(thread_checker_.CalledOnValidThread());
   // To avoid reporting an ambiguous connection type for users on flaky
diff --git a/components/metrics/net/network_metrics_provider.h b/components/metrics/net/network_metrics_provider.h
index f0f6d0e..3b9932f 100644
--- a/components/metrics/net/network_metrics_provider.h
+++ b/components/metrics/net/network_metrics_provider.h
@@ -33,7 +33,7 @@
 // the network environment.
 class NetworkMetricsProvider
     : public MetricsProvider,
-      public net::NetworkChangeNotifier::ConnectionTypeObserver {
+      public net::NetworkChangeNotifier::NetworkChangeObserver {
  public:
   // Class that provides |this| with the network quality estimator.
   class NetworkQualityEstimatorProvider {
@@ -82,8 +82,8 @@
       ChromeUserMetricsExtension* uma_proto) override;
   void ProvideSystemProfileMetrics(SystemProfileProto* system_profile) override;
 
-  // ConnectionTypeObserver:
-  void OnConnectionTypeChanged(
+  // NetworkChangeObserver:
+  void OnNetworkChanged(
       net::NetworkChangeNotifier::ConnectionType type) override;
 
   SystemProfileProto::Network::ConnectionType GetConnectionType() const;
diff --git a/components/metrics/net/network_metrics_provider_unittest.cc b/components/metrics/net/network_metrics_provider_unittest.cc
index 6323a537..b5e6507 100644
--- a/components/metrics/net/network_metrics_provider_unittest.cc
+++ b/components/metrics/net/network_metrics_provider_unittest.cc
@@ -182,7 +182,7 @@
 
   // Even with change in the connection type, effective connection types
   // should be reported as 2G.
-  network_metrics_provider.OnConnectionTypeChanged(
+  network_metrics_provider.OnNetworkChanged(
       net::NetworkChangeNotifier::CONNECTION_2G);
   network_metrics_provider.ProvideSystemProfileMetrics(&system_profile);
   EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G,
@@ -252,7 +252,7 @@
 
   // When a connection type change callback is received, network change notifier
   // should be marked as initialized.
-  network_metrics_provider.OnConnectionTypeChanged(
+  network_metrics_provider.OnNetworkChanged(
       net::NetworkChangeNotifier::CONNECTION_2G);
   EXPECT_EQ(net::NetworkChangeNotifier::CONNECTION_2G,
             network_metrics_provider.connection_type_);
@@ -271,7 +271,7 @@
   EXPECT_EQ(SystemProfileProto::Network::CONNECTION_2G,
             system_profile.network().connection_type());
 
-  network_metrics_provider.OnConnectionTypeChanged(
+  network_metrics_provider.OnNetworkChanged(
       net::NetworkChangeNotifier::CONNECTION_3G);
   EXPECT_TRUE(network_metrics_provider.connection_type_is_ambiguous_);
   EXPECT_TRUE(network_metrics_provider.network_change_notifier_initialized_);
diff --git a/components/mirroring/browser/single_client_video_capture_host_unittest.cc b/components/mirroring/browser/single_client_video_capture_host_unittest.cc
index e5971f8d..fab1f25 100644
--- a/components/mirroring/browser/single_client_video_capture_host_unittest.cc
+++ b/components/mirroring/browser/single_client_video_capture_host_unittest.cc
@@ -166,7 +166,7 @@
  public:
   SingleClientVideoCaptureHostTest() : weak_factory_(this) {
     auto host_impl = std::make_unique<SingleClientVideoCaptureHost>(
-        std::string(), content::MediaStreamType::MEDIA_TAB_VIDEO_CAPTURE,
+        std::string(), content::MediaStreamType::MEDIA_GUM_TAB_VIDEO_CAPTURE,
         base::BindRepeating(
             &SingleClientVideoCaptureHostTest::CreateDeviceLauncher,
             base::Unretained(this)));
diff --git a/components/mirroring/service/BUILD.gn b/components/mirroring/service/BUILD.gn
index 448a0d5..c91410c 100644
--- a/components/mirroring/service/BUILD.gn
+++ b/components/mirroring/service/BUILD.gn
@@ -8,6 +8,8 @@
   sources = [
     "captured_audio_input.cc",
     "captured_audio_input.h",
+    "features.cc",
+    "features.h",
     "media_remoter.cc",
     "media_remoter.h",
     "message_dispatcher.cc",
diff --git a/components/mirroring/service/features.cc b/components/mirroring/service/features.cc
new file mode 100644
index 0000000..7afd63a
--- /dev/null
+++ b/components/mirroring/service/features.cc
@@ -0,0 +1,15 @@
+// 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/features.h"
+
+namespace mirroring {
+namespace features {
+
+// Enables or disables Mirroring Service.
+const base::Feature kMirroringService{"MirroringService",
+                                      base::FEATURE_DISABLED_BY_DEFAULT};
+
+}  // namespace features
+}  // namespace mirroring
diff --git a/components/mirroring/service/features.h b/components/mirroring/service/features.h
new file mode 100644
index 0000000..913d437c
--- /dev/null
+++ b/components/mirroring/service/features.h
@@ -0,0 +1,18 @@
+// 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_FEATURES_H_
+#define COMPONENTS_MIRRORING_SERVICE_FEATURES_H_
+
+#include "base/feature_list.h"
+
+namespace mirroring {
+namespace features {
+
+extern const base::Feature kMirroringService;
+
+}  // namespace features
+}  // namespace mirroring
+
+#endif  // COMPONENTS_MIRRORING_SERVICE_FEATURES_H_
diff --git a/components/offline_pages/content/background_loader/background_loader_contents_unittest.cc b/components/offline_pages/content/background_loader/background_loader_contents_unittest.cc
index 26dabb5..b11bd989 100644
--- a/components/offline_pages/content/background_loader/background_loader_contents_unittest.cc
+++ b/components/offline_pages/content/background_loader/background_loader_contents_unittest.cc
@@ -156,8 +156,8 @@
       content::MediaStreamRequestType::MEDIA_DEVICE_ACCESS /* request_type */,
       std::string() /* requested_audio_device_id */,
       std::string() /* requested_video_device_id */,
-      content::MediaStreamType::MEDIA_TAB_AUDIO_CAPTURE /* audio_type */,
-      content::MediaStreamType::MEDIA_TAB_VIDEO_CAPTURE /* video_type */,
+      content::MediaStreamType::MEDIA_GUM_TAB_AUDIO_CAPTURE /* audio_type */,
+      content::MediaStreamType::MEDIA_GUM_TAB_VIDEO_CAPTURE /* video_type */,
       false /* disable_local_echo */);
   contents()->RequestMediaAccessPermission(
       nullptr /* contents */, request /* request */,
@@ -176,7 +176,7 @@
 TEST_F(BackgroundLoaderContentsTest, CheckMediaAccessPermissionFalse) {
   ASSERT_FALSE(contents()->CheckMediaAccessPermission(
       nullptr /* contents */, GURL::EmptyGURL() /* security_origin */,
-      content::MediaStreamType::MEDIA_TAB_VIDEO_CAPTURE /* type */));
+      content::MediaStreamType::MEDIA_GUM_TAB_VIDEO_CAPTURE /* type */));
 }
 
 TEST_F(BackgroundLoaderContentsTest, AdjustPreviewsState) {
diff --git a/components/safe_browsing/base_blocking_page.cc b/components/safe_browsing/base_blocking_page.cc
index 8062424..1fb534e 100644
--- a/components/safe_browsing/base_blocking_page.cc
+++ b/components/safe_browsing/base_blocking_page.cc
@@ -239,9 +239,8 @@
       return primary_subresource ? "malware_subresource" : "malware";
     case BaseSafeBrowsingErrorUI::SB_REASON_HARMFUL:
       return primary_subresource ? "harmful_subresource" : "harmful";
-    case BaseSafeBrowsingErrorUI::SB_REASON_TRICK_TO_BILL:
-      return primary_subresource ? "trick_to_bill_subresource"
-                                 : "trick_to_bill";
+    case BaseSafeBrowsingErrorUI::SB_REASON_BILLING:
+      return primary_subresource ? "billing_subresource" : "billing";
     case BaseSafeBrowsingErrorUI::SB_REASON_PHISHING:
       ThreatPatternType threat_pattern_type =
           unsafe_resources[0].threat_metadata.threat_pattern_type;
@@ -294,8 +293,8 @@
        iter != unsafe_resources.end(); ++iter) {
     const BaseUIManager::UnsafeResource& resource = *iter;
     safe_browsing::SBThreatType threat_type = resource.threat_type;
-    if (threat_type == SB_THREAT_TYPE_TRICK_TO_BILL)
-      return BaseSafeBrowsingErrorUI::SB_REASON_TRICK_TO_BILL;
+    if (threat_type == SB_THREAT_TYPE_BILLING)
+      return BaseSafeBrowsingErrorUI::SB_REASON_BILLING;
 
     if (threat_type == SB_THREAT_TYPE_URL_MALWARE ||
         threat_type == SB_THREAT_TYPE_URL_CLIENT_SIDE_MALWARE) {
diff --git a/components/safe_browsing/db/v4_protocol_manager_util.h b/components/safe_browsing/db/v4_protocol_manager_util.h
index 36004b92..ef84129 100644
--- a/components/safe_browsing/db/v4_protocol_manager_util.h
+++ b/components/safe_browsing/db/v4_protocol_manager_util.h
@@ -142,7 +142,7 @@
   SB_THREAT_TYPE_ENTERPRISE_PASSWORD_REUSE,
 
   // Deceptive mobile billing practice detected.
-  SB_THREAT_TYPE_TRICK_TO_BILL,
+  SB_THREAT_TYPE_BILLING,
 };
 
 using SBThreatTypeSet = base::flat_set<SBThreatType>;
diff --git a/components/security_interstitials/core/base_safe_browsing_error_ui.h b/components/security_interstitials/core/base_safe_browsing_error_ui.h
index ff74c5a..72f80a6 100644
--- a/components/security_interstitials/core/base_safe_browsing_error_ui.h
+++ b/components/security_interstitials/core/base_safe_browsing_error_ui.h
@@ -23,7 +23,7 @@
     SB_REASON_MALWARE,
     SB_REASON_HARMFUL,
     SB_REASON_PHISHING,
-    SB_REASON_TRICK_TO_BILL,
+    SB_REASON_BILLING,
   };
 
   struct SBErrorDisplayOptions {
diff --git a/components/security_interstitials/core/browser/resources/extended_reporting.js b/components/security_interstitials/core/browser/resources/extended_reporting.js
index 96da18e..136b5b6 100644
--- a/components/security_interstitials/core/browser/resources/extended_reporting.js
+++ b/components/security_interstitials/core/browser/resources/extended_reporting.js
@@ -26,11 +26,11 @@
   $('opt-in-checkbox').checked = loadTimeData.getBoolean(SB_BOX_CHECKED);
   $('extended-reporting-opt-in').classList.remove('hidden');
 
-  var trickToBill = interstitialType == 'SAFEBROWSING' &&
-                    loadTimeData.getBoolean('trick_to_bill');
+  var billing = interstitialType == 'SAFEBROWSING' &&
+                    loadTimeData.getBoolean('billing');
 
   var className = 'ssl-opt-in';
-  if (interstitialType == 'SAFEBROWSING' && !trickToBill) {
+  if (interstitialType == 'SAFEBROWSING' && !billing) {
     className = 'safe-browsing-opt-in';
   }
 
diff --git a/components/security_interstitials/core/browser/resources/interstitial_large.js b/components/security_interstitials/core/browser/resources/interstitial_large.js
index ffa979b..8756cab 100644
--- a/components/security_interstitials/core/browser/resources/interstitial_large.js
+++ b/components/security_interstitials/core/browser/resources/interstitial_large.js
@@ -68,8 +68,8 @@
   var ssl = interstitialType == 'SSL';
   var captivePortal = interstitialType == 'CAPTIVE_PORTAL';
   var badClock = ssl && loadTimeData.getBoolean('bad_clock');
-  var trickToBill = interstitialType == 'SAFEBROWSING' &&
-                    loadTimeData.getBoolean('trick_to_bill');
+  var billing = interstitialType == 'SAFEBROWSING' &&
+                    loadTimeData.getBoolean('billing');
   var hidePrimaryButton = loadTimeData.getBoolean('hide_primary_button');
   var showRecurrentErrorParagraph = loadTimeData.getBoolean(
     'show_recurrent_error_paragraph');
@@ -80,8 +80,8 @@
     $('error-code').classList.remove(HIDDEN_CLASS);
   } else if (captivePortal) {
     $('body').classList.add('captive-portal');
-  } else if (trickToBill) {
-    $('body').classList.add('safe-browsing-trick-to-bill');
+  } else if (billing) {
+    $('body').classList.add('safe-browsing-billing');
   } else {
     $('body').classList.add('safe-browsing');
     // Override the default theme color.
@@ -120,7 +120,7 @@
   }
 
   if (overridable) {
-    var overrideElement = trickToBill ? 'proceed-button' : 'proceed-link';
+    var overrideElement = billing ? 'proceed-button' : 'proceed-link';
     // Captive portal page isn't overridable.
     $(overrideElement).addEventListener('click', function(event) {
       sendCommand(SecurityInterstitialCommandId.CMD_PROCEED);
@@ -128,7 +128,7 @@
 
     if (ssl) {
       $(overrideElement).classList.add('small-link');
-    } else if (trickToBill) {
+    } else if (billing) {
       $(overrideElement).classList.remove(HIDDEN_CLASS);
       $(overrideElement).textContent =
           loadTimeData.getString('proceedButtonText');
@@ -156,8 +156,8 @@
     });
   }
 
-  if (captivePortal || trickToBill) {
-    // Captive portal and trick-to-bill pages don't have details button.
+  if (captivePortal || billing) {
+    // Captive portal and billing pages don't have details button.
     $('details-button').classList.add('hidden');
   } else {
     $('details-button').addEventListener('click', function(event) {
diff --git a/components/security_interstitials/core/browser/resources/interstitial_safebrowsing.css b/components/security_interstitials/core/browser/resources/interstitial_safebrowsing.css
index 77d1e8e8..e93f34a 100644
--- a/components/security_interstitials/core/browser/resources/interstitial_safebrowsing.css
+++ b/components/security_interstitials/core/browser/resources/interstitial_safebrowsing.css
@@ -44,17 +44,17 @@
   }
 }
 
-.safe-browsing-trick-to-bill .small-link {
+.safe-browsing-billing .small-link {
   background-color: white;
   border:none;
   float: none;
 }
 
-.safe-browsing-trick-to-bill .small-link:hover {
+.safe-browsing-billing .small-link:hover {
   box-shadow: none;
 }
 
-.safe-browsing-trick-to-bill .icon {
+.safe-browsing-billing .icon {
   background-image: -webkit-image-set(
       url(images/1x/triangle_red.png) 1x,
       url(images/2x/triangle_red.png) 2x);
diff --git a/components/security_interstitials/core/browser/resources/list_of_interstitials.html b/components/security_interstitials/core/browser/resources/list_of_interstitials.html
index 2826628..ea5dd77e 100644
--- a/components/security_interstitials/core/browser/resources/list_of_interstitials.html
+++ b/components/security_interstitials/core/browser/resources/list_of_interstitials.html
@@ -72,7 +72,7 @@
       <a href="safebrowsing?type=clientside_phishing">Client Side Phishing</a>
     </li>
     <li>
-      <a href="safebrowsing?type=trick_to_bill">Trick To Bill</a>
+      <a href="safebrowsing?type=billing">Billing</a>
     </li>
   </ul>
   <h4>Quiet (WebView)</h4>
diff --git a/components/security_interstitials/core/common/resources/interstitial_common.css b/components/security_interstitials/core/common/resources/interstitial_common.css
index b624669..80b62a4 100644
--- a/components/security_interstitials/core/common/resources/interstitial_common.css
+++ b/components/security_interstitials/core/common/resources/interstitial_common.css
@@ -27,7 +27,7 @@
 .offline button,
 .pdf button,
 .ssl button,
-.safe-browsing-trick-to-bill button {
+.safe-browsing-billing button {
   background: rgb(66, 133, 244);
 }
 
diff --git a/components/security_interstitials/core/safe_browsing_loud_error_ui.cc b/components/security_interstitials/core/safe_browsing_loud_error_ui.cc
index 2251a97c..499fe83 100644
--- a/components/security_interstitials/core/safe_browsing_loud_error_ui.cc
+++ b/components/security_interstitials/core/safe_browsing_loud_error_ui.cc
@@ -94,8 +94,8 @@
       always_show_back_to_safety() ? false : !controller()->CanGoBack());
 
   load_time_data->SetBoolean(
-      "trick_to_bill", interstitial_reason() ==
-                           BaseSafeBrowsingErrorUI::SB_REASON_TRICK_TO_BILL);
+      "billing",
+      interstitial_reason() == BaseSafeBrowsingErrorUI::SB_REASON_BILLING);
 
   switch (interstitial_reason()) {
     case BaseSafeBrowsingErrorUI::SB_REASON_MALWARE:
@@ -107,8 +107,8 @@
     case BaseSafeBrowsingErrorUI::SB_REASON_PHISHING:
       PopulatePhishingLoadTimeData(load_time_data);
       break;
-    case BaseSafeBrowsingErrorUI::SB_REASON_TRICK_TO_BILL:
-      PopulateTrickToBillLoadTimeData(load_time_data);
+    case BaseSafeBrowsingErrorUI::SB_REASON_BILLING:
+      PopulateBillingLoadTimeData(load_time_data);
       break;
   }
 
@@ -316,24 +316,24 @@
                              is_extended_reporting_enabled());
 }
 
-void SafeBrowsingLoudErrorUI::PopulateTrickToBillLoadTimeData(
+void SafeBrowsingLoudErrorUI::PopulateBillingLoadTimeData(
     base::DictionaryValue* load_time_data) {
   load_time_data->SetBoolean("phishing", false);
   load_time_data->SetBoolean("overridable", true);
   load_time_data->SetBoolean("hide_primary_button", false);
 
-  load_time_data->SetString(
-      "heading", l10n_util::GetStringUTF16(IDS_TRICK_TO_BILL_HEADING));
+  load_time_data->SetString("heading",
+                            l10n_util::GetStringUTF16(IDS_BILLING_HEADING));
   load_time_data->SetString(
       "primaryParagraph",
-      l10n_util::GetStringUTF16(IDS_TRICK_TO_BILL_PRIMARY_PARAGRAPH));
+      l10n_util::GetStringUTF16(IDS_BILLING_PRIMARY_PARAGRAPH));
 
   load_time_data->SetString(
       "primaryButtonText",
-      l10n_util::GetStringUTF16(IDS_TRICK_TO_BILL_PRIMARY_BUTTON));
+      l10n_util::GetStringUTF16(IDS_BILLING_PRIMARY_BUTTON));
   load_time_data->SetString(
       "proceedButtonText",
-      l10n_util::GetStringUTF16(IDS_TRICK_TO_BILL_PROCEED_BUTTON));
+      l10n_util::GetStringUTF16(IDS_BILLING_PROCEED_BUTTON));
 
   load_time_data->SetString("openDetails", "");
   load_time_data->SetString("closeDetails", "");
diff --git a/components/security_interstitials/core/safe_browsing_loud_error_ui.h b/components/security_interstitials/core/safe_browsing_loud_error_ui.h
index 46e92ac..f49a241 100644
--- a/components/security_interstitials/core/safe_browsing_loud_error_ui.h
+++ b/components/security_interstitials/core/safe_browsing_loud_error_ui.h
@@ -47,7 +47,7 @@
   void PopulateMalwareLoadTimeData(base::DictionaryValue* load_time_data);
   void PopulateHarmfulLoadTimeData(base::DictionaryValue* load_time_data);
   void PopulatePhishingLoadTimeData(base::DictionaryValue* load_time_data);
-  void PopulateTrickToBillLoadTimeData(base::DictionaryValue* load_time_data);
+  void PopulateBillingLoadTimeData(base::DictionaryValue* load_time_data);
 
   DISALLOW_COPY_AND_ASSIGN(SafeBrowsingLoudErrorUI);
 };
diff --git a/components/security_interstitials_strings.grdp b/components/security_interstitials_strings.grdp
index 96bc094..19a907af 100644
--- a/components/security_interstitials_strings.grdp
+++ b/components/security_interstitials_strings.grdp
@@ -382,17 +382,17 @@
     </message>
   </if>
 
-  <!-- Trick to Bill interstitial -->
-  <message name="IDS_TRICK_TO_BILL_HEADING" desc="The large heading at the top of the trick to bill interstitial.">
+  <!-- Billing interstitial -->
+  <message name="IDS_BILLING_HEADING" desc="The large heading at the top of the billing interstitial.">
     The page ahead may try to charge you money
   </message>
-  <message name="IDS_TRICK_TO_BILL_PRIMARY_PARAGRAPH" desc="The primary explanatory paragraph for the trick to bill interstitial.">
+  <message name="IDS_BILLING_PRIMARY_PARAGRAPH" desc="The primary explanatory paragraph for the billing interstitial.">
     These charges could be one-time or recurring and may not be obvious.
   </message>
-  <message name="IDS_TRICK_TO_BILL_PRIMARY_BUTTON" desc="The text for the trick to bill interstitial primary button.">
+  <message name="IDS_BILLING_PRIMARY_BUTTON" desc="The text for the billing interstitial primary button.">
     Go Back
   </message>
-  <message name="IDS_TRICK_TO_BILL_PROCEED_BUTTON" desc="The text for the trick to bill interstitial proceed button.">
+  <message name="IDS_BILLING_PROCEED_BUTTON" desc="The text for the billing interstitial proceed button.">
     Proceed
   </message>
 </grit-part>
diff --git a/components/security_interstitials_strings_grdp/IDS_TRICK_TO_BILL_HEADING.png.sha1 b/components/security_interstitials_strings_grdp/IDS_BILLING_HEADING.png.sha1
similarity index 100%
rename from components/security_interstitials_strings_grdp/IDS_TRICK_TO_BILL_HEADING.png.sha1
rename to components/security_interstitials_strings_grdp/IDS_BILLING_HEADING.png.sha1
diff --git a/components/security_interstitials_strings_grdp/IDS_TRICK_TO_BILL_PRIMARY_BUTTON.png.sha1 b/components/security_interstitials_strings_grdp/IDS_BILLING_PRIMARY_BUTTON.png.sha1
similarity index 100%
rename from components/security_interstitials_strings_grdp/IDS_TRICK_TO_BILL_PRIMARY_BUTTON.png.sha1
rename to components/security_interstitials_strings_grdp/IDS_BILLING_PRIMARY_BUTTON.png.sha1
diff --git a/components/security_interstitials_strings_grdp/IDS_TRICK_TO_BILL_PRIMARY_PARAGRAPH.png.sha1 b/components/security_interstitials_strings_grdp/IDS_BILLING_PRIMARY_PARAGRAPH.png.sha1
similarity index 100%
rename from components/security_interstitials_strings_grdp/IDS_TRICK_TO_BILL_PRIMARY_PARAGRAPH.png.sha1
rename to components/security_interstitials_strings_grdp/IDS_BILLING_PRIMARY_PARAGRAPH.png.sha1
diff --git a/components/security_interstitials_strings_grdp/IDS_TRICK_TO_BILL_PROCEED_BUTTON.png.sha1 b/components/security_interstitials_strings_grdp/IDS_BILLING_PROCEED_BUTTON.png.sha1
similarity index 100%
rename from components/security_interstitials_strings_grdp/IDS_TRICK_TO_BILL_PROCEED_BUTTON.png.sha1
rename to components/security_interstitials_strings_grdp/IDS_BILLING_PROCEED_BUTTON.png.sha1
diff --git a/components/signin/core/browser/signin_metrics.cc b/components/signin/core/browser/signin_metrics.cc
index 33dae6f6b..5985a8e 100644
--- a/components/signin/core/browser/signin_metrics.cc
+++ b/components/signin/core/browser/signin_metrics.cc
@@ -109,6 +109,10 @@
       base::RecordAction(
           base::UserMetricsAction("Signin_Signin_FromForceSigninWarning"));
       break;
+    case AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE:
+      base::RecordAction(
+          base::UserMetricsAction("Signin_Signin_FromSaveCardBubble"));
+      break;
     case AccessPoint::ACCESS_POINT_MAX:
       NOTREACHED();
       break;
@@ -154,6 +158,10 @@
       base::RecordAction(base::UserMetricsAction(
           "Signin_SigninWithDefault_FromNTPContentSuggestions"));
       break;
+    case AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE:
+      base::RecordAction(base::UserMetricsAction(
+          "Signin_SigninWithDefault_FromSaveCardBubble"));
+      break;
     case AccessPoint::ACCESS_POINT_START_PAGE:
     case AccessPoint::ACCESS_POINT_NTP_LINK:
     case AccessPoint::ACCESS_POINT_MENU:
@@ -219,6 +227,10 @@
       base::RecordAction(base::UserMetricsAction(
           "Signin_SigninNotDefault_FromNTPContentSuggestions"));
       break;
+    case AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE:
+      base::RecordAction(base::UserMetricsAction(
+          "Signin_SigninNotDefault_FromSaveCardBubble"));
+      break;
     case AccessPoint::ACCESS_POINT_START_PAGE:
     case AccessPoint::ACCESS_POINT_NTP_LINK:
     case AccessPoint::ACCESS_POINT_MENU:
@@ -284,6 +296,10 @@
       base::RecordAction(base::UserMetricsAction(
           "Signin_SigninNewAccount_FromNTPContentSuggestions"));
       break;
+    case AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE:
+      base::RecordAction(base::UserMetricsAction(
+          "Signin_SigninNewAccount_FromSaveCardBubble"));
+      break;
     case AccessPoint::ACCESS_POINT_START_PAGE:
     case AccessPoint::ACCESS_POINT_NTP_LINK:
     case AccessPoint::ACCESS_POINT_MENU:
@@ -685,6 +701,10 @@
       base::RecordAction(
           base::UserMetricsAction("Signin_Impression_FromTabSwitcher"));
       break;
+    case AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE:
+      base::RecordAction(
+          base::UserMetricsAction("Signin_Impression_FromSaveCardBubble"));
+      break;
     case AccessPoint::ACCESS_POINT_CONTENT_AREA:
     case AccessPoint::ACCESS_POINT_EXTENSIONS:
     case AccessPoint::ACCESS_POINT_FORCE_SIGNIN_WARNING:
@@ -786,6 +806,15 @@
             "Signin_ImpressionWithNoAccount_FromNTPContentSuggestions"));
       }
       break;
+    case AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE:
+      if (with_account) {
+        base::RecordAction(base::UserMetricsAction(
+            "Signin_ImpressionWithAccount_FromSaveCardBubble"));
+      } else {
+        base::RecordAction(base::UserMetricsAction(
+            "Signin_ImpressionWithNoAccount_FromSaveCardBubble"));
+      }
+      break;
     case AccessPoint::ACCESS_POINT_START_PAGE:
     case AccessPoint::ACCESS_POINT_NTP_LINK:
     case AccessPoint::ACCESS_POINT_MENU:
diff --git a/components/signin/core/browser/signin_metrics.h b/components/signin/core/browser/signin_metrics.h
index 132b33b0..b4af9b0 100644
--- a/components/signin/core/browser/signin_metrics.h
+++ b/components/signin/core/browser/signin_metrics.h
@@ -146,6 +146,7 @@
   ACCESS_POINT_RESIGNIN_INFOBAR,
   ACCESS_POINT_TAB_SWITCHER,
   ACCESS_POINT_FORCE_SIGNIN_WARNING,
+  ACCESS_POINT_SAVE_CARD_BUBBLE,
   ACCESS_POINT_MAX,  // This must be last.
 };
 
diff --git a/components/signin/core/browser/signin_metrics_unittest.cc b/components/signin/core/browser/signin_metrics_unittest.cc
index c8db0db..941f9738 100644
--- a/components/signin/core/browser/signin_metrics_unittest.cc
+++ b/components/signin/core/browser/signin_metrics_unittest.cc
@@ -32,7 +32,8 @@
     AccessPoint::ACCESS_POINT_AUTOFILL_DROPDOWN,
     AccessPoint::ACCESS_POINT_NTP_CONTENT_SUGGESTIONS,
     AccessPoint::ACCESS_POINT_RESIGNIN_INFOBAR,
-    AccessPoint::ACCESS_POINT_TAB_SWITCHER};
+    AccessPoint::ACCESS_POINT_TAB_SWITCHER,
+    AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE};
 
 const AccessPoint kAccessPointsThatSupportPersonalizedPromos[] = {
     AccessPoint::ACCESS_POINT_BOOKMARK_MANAGER,
@@ -43,7 +44,8 @@
     AccessPoint::ACCESS_POINT_AVATAR_BUBBLE_SIGN_IN,
     AccessPoint::ACCESS_POINT_PASSWORD_BUBBLE,
     AccessPoint::ACCESS_POINT_BOOKMARK_BUBBLE,
-    AccessPoint::ACCESS_POINT_NTP_CONTENT_SUGGESTIONS};
+    AccessPoint::ACCESS_POINT_NTP_CONTENT_SUGGESTIONS,
+    AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE};
 
 class SigninMetricsTest : public ::testing::Test {
  public:
@@ -97,6 +99,8 @@
         return "TabSwitcher";
       case AccessPoint::ACCESS_POINT_FORCE_SIGNIN_WARNING:
         return "ForceSigninWarning";
+      case AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE:
+        return "SaveCardBubble";
       case AccessPoint::ACCESS_POINT_MAX:
         NOTREACHED();
         return "";
diff --git a/components/sync_bookmarks/bookmark_remote_updates_handler.cc b/components/sync_bookmarks/bookmark_remote_updates_handler.cc
index 05c1fe0..3c8bd6d4 100644
--- a/components/sync_bookmarks/bookmark_remote_updates_handler.cc
+++ b/components/sync_bookmarks/bookmark_remote_updates_handler.cc
@@ -9,12 +9,10 @@
 #include <unordered_map>
 #include <utility>
 
-#include "base/metrics/histogram_macros.h"
 #include "base/strings/string_piece.h"
 #include "components/bookmarks/browser/bookmark_model.h"
 #include "components/bookmarks/browser/bookmark_node.h"
 #include "components/sync/base/unique_position.h"
-#include "components/sync/model/conflict_resolution.h"
 #include "components/sync/protocol/unique_position.pb.h"
 #include "components/sync_bookmarks/bookmark_specifics_conversions.h"
 
@@ -73,46 +71,6 @@
   return parent->child_count();
 }
 
-void ApplyRemoteUpdate(
-    const syncer::UpdateResponseData& update,
-    const SyncedBookmarkTracker::Entity* tracked_entity,
-    const SyncedBookmarkTracker::Entity* new_parent_tracked_entity,
-    bookmarks::BookmarkModel* model,
-    SyncedBookmarkTracker* tracker) {
-  const syncer::EntityData& update_entity = update.entity.value();
-  const bookmarks::BookmarkNode* node = tracked_entity->bookmark_node();
-  const bookmarks::BookmarkNode* old_parent = node->parent();
-  const bookmarks::BookmarkNode* new_parent =
-      new_parent_tracked_entity->bookmark_node();
-
-  if (update_entity.is_folder != node->is_folder()) {
-    DLOG(ERROR) << "Could not update node. Remote node is a "
-                << (update_entity.is_folder ? "folder" : "bookmark")
-                << " while local node is a "
-                << (node->is_folder() ? "folder" : "bookmark");
-    return;
-  }
-  UpdateBookmarkNodeFromSpecifics(update_entity.specifics.bookmark(), node,
-                                  model);
-  // Compute index information before updating the |tracker|.
-  const int old_index = old_parent->GetIndexOf(node);
-  const int new_index =
-      ComputeChildNodeIndex(new_parent, update_entity.unique_position, tracker);
-  tracker->Update(update_entity.id, update.response_version,
-                  update_entity.modification_time,
-                  update_entity.unique_position, update_entity.specifics);
-
-  if (new_parent == old_parent &&
-      (new_index == old_index || new_index == old_index + 1)) {
-    // Node hasn't moved. No more work to do.
-    return;
-  }
-  // Node has moved to another position under the same parent. Update the model.
-  // BookmarkModel takes care of placing the node in the correct position if the
-  // node is move to the left. (i.e. no need to subtract one from |new_index|).
-  model->Move(node, new_parent, new_index);
-}
-
 }  // namespace
 
 BookmarkRemoteUpdatesHandler::BookmarkRemoteUpdatesHandler(
@@ -127,33 +85,12 @@
     const syncer::UpdateResponseDataList& updates) {
   for (const syncer::UpdateResponseData* update : ReorderUpdates(&updates)) {
     const syncer::EntityData& update_entity = update->entity.value();
-    // Only non deletions and non premanent node should have valid specifics and
-    // unique positions.
-    if (!update_entity.is_deleted() &&
-        update_entity.parent_id != kBookmarksRootId) {
-      if (!IsValidBookmarkSpecifics(update_entity.specifics.bookmark(),
-                                    update_entity.is_folder)) {
-        // Ignore updates with invalid specifics.
-        DLOG(ERROR)
-            << "Couldn't process an update bookmark with an invalid specifics.";
-        continue;
-      }
-      if (!syncer::UniquePosition::FromProto(update_entity.unique_position)
-               .IsValid()) {
-        // Ignore updates with invalid unique position.
-        DLOG(ERROR) << "Couldn't process an update bookmark with an invalid "
-                       "unique position.";
-        continue;
-      }
-    }
+    // TODO(crbug.com/516866): Check |update_entity| for sanity.
+    // 1. Has bookmark specifics or no specifics in case of delete.
+    // 2. All meta info entries in the specifics have unique keys.
+    // 3. Unique position is valid.
     const SyncedBookmarkTracker::Entity* tracked_entity =
         bookmark_tracker_->GetEntityForSyncId(update_entity.id);
-    // TODO(crbug.com/516866): Handle the case of conflict as a result of
-    // re-encryption request.
-    if (tracked_entity && tracked_entity->IsUnsynced()) {
-      ProcessConflict(*update, tracked_entity);
-      continue;
-    }
     if (update_entity.is_deleted()) {
       ProcessRemoteDelete(update_entity, tracked_entity);
       continue;
@@ -275,10 +212,12 @@
                    "should have been merged during intial sync.";
     return;
   }
-
-  DCHECK(IsValidBookmarkSpecifics(update_entity.specifics.bookmark(),
-                                  update_entity.is_folder));
-
+  if (!IsValidBookmarkSpecifics(update_entity.specifics.bookmark(),
+                                update_entity.is_folder)) {
+    // Ignore creations with invalid specifics.
+    DLOG(ERROR) << "Couldn't add bookmark with an invalid specifics.";
+    return;
+  }
   const bookmarks::BookmarkNode* parent_node = GetParentNode(update_entity);
   if (!parent_node) {
     // If we cannot find the parent, we can do nothing.
@@ -316,9 +255,16 @@
             bookmark_tracker_->GetEntityForSyncId(update_entity.id));
   // Must not be a deletion.
   DCHECK(!update_entity.is_deleted());
-
-  DCHECK(IsValidBookmarkSpecifics(update_entity.specifics.bookmark(),
-                                  update_entity.is_folder));
+  if (!IsValidBookmarkSpecifics(update_entity.specifics.bookmark(),
+                                update_entity.is_folder)) {
+    // Ignore updates with invalid specifics.
+    DLOG(ERROR) << "Couldn't update bookmark with an invalid specifics.";
+    return;
+  }
+  if (tracked_entity->IsUnsynced()) {
+    // TODO(crbug.com/516866): Handle conflict resolution.
+    return;
+  }
 
   const bookmarks::BookmarkNode* node = tracked_entity->bookmark_node();
   const bookmarks::BookmarkNode* old_parent = node->parent();
@@ -349,8 +295,33 @@
                               update_entity.specifics);
     return;
   }
-  ApplyRemoteUpdate(update, tracked_entity, new_parent_entity, bookmark_model_,
-                    bookmark_tracker_);
+  if (update_entity.is_folder != node->is_folder()) {
+    DLOG(ERROR) << "Could not update node. Remote node is a "
+                << (update_entity.is_folder ? "folder" : "bookmark")
+                << " while local node is a "
+                << (node->is_folder() ? "folder" : "bookmark");
+    return;
+  }
+  UpdateBookmarkNodeFromSpecifics(update_entity.specifics.bookmark(), node,
+                                  bookmark_model_);
+  // Compute index information before updating the |bookmark_tracker_|.
+  const int old_index = old_parent->GetIndexOf(node);
+  const int new_index = ComputeChildNodeIndex(
+      new_parent, update_entity.unique_position, bookmark_tracker_);
+  bookmark_tracker_->Update(update_entity.id, update.response_version,
+                            update_entity.modification_time,
+                            update_entity.unique_position,
+                            update_entity.specifics);
+
+  if (new_parent == old_parent &&
+      (new_index == old_index || new_index == old_index + 1)) {
+    // Node hasn't moved. No more work to do.
+    return;
+  }
+  // Node has moved to another position under the same parent. Update the model.
+  // BookmarkModel takes care of placing the node in the correct position if the
+  // node is move to the left. (i.e. no need to subtract one from |new_index|).
+  bookmark_model_->Move(node, new_parent, new_index);
 }
 
 void BookmarkRemoteUpdatesHandler::ProcessRemoteDelete(
@@ -382,106 +353,6 @@
   bookmark_model_->Remove(node);
 }
 
-void BookmarkRemoteUpdatesHandler::ProcessConflict(
-    const syncer::UpdateResponseData& update,
-    const SyncedBookmarkTracker::Entity* tracked_entity) {
-  const syncer::EntityData& update_entity = update.entity.value();
-  // TODO(crbug.com/516866): Add basic unit test for this function.
-
-  // Can only conflict with existing nodes.
-  DCHECK(tracked_entity);
-  DCHECK_EQ(tracked_entity,
-            bookmark_tracker_->GetEntityForSyncId(update_entity.id));
-
-  if (tracked_entity->metadata()->is_deleted() && update_entity.is_deleted()) {
-    // Both have been deleted, delete the corresponding entity from the tracker.
-    bookmark_tracker_->Remove(update_entity.id);
-    UMA_HISTOGRAM_ENUMERATION("Sync.ResolveConflict",
-                              syncer::ConflictResolution::CHANGES_MATCH,
-                              syncer::ConflictResolution::TYPE_SIZE);
-    return;
-  }
-
-  if (update_entity.is_deleted()) {
-    // Only remote has been deleted. Local wins. Record that we received the
-    // update from the server but leave the pending commit intact.
-    bookmark_tracker_->UpdateServerVersion(update_entity.id,
-                                           update.response_version);
-    UMA_HISTOGRAM_ENUMERATION("Sync.ResolveConflict",
-                              syncer::ConflictResolution::USE_LOCAL,
-                              syncer::ConflictResolution::TYPE_SIZE);
-    return;
-  }
-
-  if (tracked_entity->metadata()->is_deleted()) {
-    // Only local node has been deleted. It should be restored from the server
-    // data as a remote creation.
-    bookmark_tracker_->Remove(update_entity.id);
-    ProcessRemoteCreate(update);
-    UMA_HISTOGRAM_ENUMERATION("Sync.ResolveConflict",
-                              syncer::ConflictResolution::USE_REMOTE,
-                              syncer::ConflictResolution::TYPE_SIZE);
-    return;
-  }
-
-  // No deletions, there are potentially conflicting updates.
-  const bookmarks::BookmarkNode* node = tracked_entity->bookmark_node();
-  const bookmarks::BookmarkNode* old_parent = node->parent();
-
-  const SyncedBookmarkTracker::Entity* new_parent_entity =
-      bookmark_tracker_->GetEntityForSyncId(update_entity.parent_id);
-  // The |new_parent_entity| could be null in some racy conditions.  For
-  // example, when a client A moves a node and deletes the old parent and
-  // commits, and then updates the node again, and at the same time client B
-  // updates before receiving the move updates. The client B update will arrive
-  // at client A after the parent entity has been deleted already.
-  if (!new_parent_entity) {
-    DLOG(ERROR) << "Could not update node. Parent node doesn't exist: "
-                << update_entity.parent_id;
-    return;
-  }
-  const bookmarks::BookmarkNode* new_parent =
-      new_parent_entity->bookmark_node();
-  // |new_parent| would be null if the parent has been deleted locally and not
-  // committed yet. Deletions are excuted recusively, so a parent deletions
-  // entails child deletion, and if this child has been updated on another
-  // client, this would cause conflict.
-  if (!new_parent) {
-    DLOG(ERROR)
-        << "Could not update node. Parent node has been deleted already.";
-    return;
-  }
-  // Either local and remote data match or server wins, and in both cases we
-  // should squash any pending commits.
-  bookmark_tracker_->AckSequenceNumber(update_entity.id);
-
-  // Node update could be either in the node data (e.g. title or
-  // unique_position), or it could be that the node has moved under another
-  // parent without any data change. Should check both the data and the parent
-  // to confirm that no updates to the model are needed.
-  if (tracked_entity->MatchesDataIgnoringParent(update_entity) &&
-      new_parent == old_parent) {
-    bookmark_tracker_->Update(update_entity.id, update.response_version,
-                              update_entity.modification_time,
-                              update_entity.unique_position,
-                              update_entity.specifics);
-
-    // The changes are identical so there isn't a real conflict.
-    UMA_HISTOGRAM_ENUMERATION("Sync.ResolveConflict",
-                              syncer::ConflictResolution::CHANGES_MATCH,
-                              syncer::ConflictResolution::TYPE_SIZE);
-    return;
-  }
-
-  // Conflict where data don't match and no remote deletion, and hence server
-  // wins. Update the model from server data.
-  UMA_HISTOGRAM_ENUMERATION("Sync.ResolveConflict",
-                            syncer::ConflictResolution::USE_REMOTE,
-                            syncer::ConflictResolution::TYPE_SIZE);
-  ApplyRemoteUpdate(update, tracked_entity, new_parent_entity, bookmark_model_,
-                    bookmark_tracker_);
-}
-
 void BookmarkRemoteUpdatesHandler::RemoveEntityAndChildrenFromTracker(
     const bookmarks::BookmarkNode* node) {
   const SyncedBookmarkTracker::Entity* entity =
diff --git a/components/sync_bookmarks/bookmark_remote_updates_handler.h b/components/sync_bookmarks/bookmark_remote_updates_handler.h
index 9620e3c..c39f8d4 100644
--- a/components/sync_bookmarks/bookmark_remote_updates_handler.h
+++ b/components/sync_bookmarks/bookmark_remote_updates_handler.h
@@ -65,7 +65,7 @@
   void ProcessRemoteUpdate(const syncer::UpdateResponseData& update,
                            const SyncedBookmarkTracker::Entity* tracked_entity);
 
-  // Processes a remote delete of a bookmark node. |update_entity| must not be a
+  // Process a remote delete of a bookmark node. |update_entity| must not be a
   // deletion. |tracked_entity| is the tracked entity for that server_id. It is
   // passed as a dependency instead of performing a lookup inside
   // ProcessRemoteDelete() to avoid wasting CPU cycles for doing another lookup
@@ -73,15 +73,6 @@
   void ProcessRemoteDelete(const syncer::EntityData& update_entity,
                            const SyncedBookmarkTracker::Entity* tracked_entity);
 
-  // Processes a conflict where the bookmark has been changed both locally and
-  // remotely. It applies the general policy the server wins expcet in the case
-  // of remote deletions in which local wins. |tracked_entity| is the tracked
-  // entity for that server_id. It is passed as a dependency instead of
-  // performing a lookup inside ProcessRemoteDelete() to avoid wasting CPU
-  // cycles for doing another lookup (this code runs on the UI thread).
-  void ProcessConflict(const syncer::UpdateResponseData& update,
-                       const SyncedBookmarkTracker::Entity* tracked_entity);
-
   // Recursively removes the entities corresponding to |node| and its children
   // from |bookmark_tracker_|.
   void RemoveEntityAndChildrenFromTracker(const bookmarks::BookmarkNode* node);
diff --git a/components/sync_bookmarks/synced_bookmark_tracker.cc b/components/sync_bookmarks/synced_bookmark_tracker.cc
index e356cac7..22650b66 100644
--- a/components/sync_bookmarks/synced_bookmark_tracker.cc
+++ b/components/sync_bookmarks/synced_bookmark_tracker.cc
@@ -136,7 +136,7 @@
   auto it = sync_id_to_entities_map_.find(sync_id);
   Entity* entity = it->second.get();
   DCHECK(entity);
-  DCHECK_EQ(entity->metadata()->server_id(), sync_id);
+  entity->metadata()->set_server_id(sync_id);
   entity->metadata()->set_server_version(server_version);
   entity->metadata()->set_modification_time(
       syncer::TimeToProtoTime(modification_time));
@@ -146,14 +146,6 @@
   // |ordered_local_tombstones_| as well if it has been locally deleted.
 }
 
-void SyncedBookmarkTracker::UpdateServerVersion(const std::string& sync_id,
-                                                int64_t server_version) {
-  auto it = sync_id_to_entities_map_.find(sync_id);
-  Entity* entity = it->second.get();
-  DCHECK(entity);
-  entity->metadata()->set_server_version(server_version);
-}
-
 void SyncedBookmarkTracker::MarkDeleted(const std::string& sync_id) {
   auto it = sync_id_to_entities_map_.find(sync_id);
   Entity* entity = it->second.get();
@@ -233,7 +225,7 @@
        sync_id_to_entities_map_) {
     Entity* entity = pair.second.get();
     if (entity->metadata()->is_deleted()) {
-      // Deletions are stored sorted in |ordered_local_tombstones_| and will be
+      // Deletion are stored sorted in |ordered_local_tombstones_| and will be
       // added later.
       continue;
     }
@@ -352,18 +344,6 @@
   }
 }
 
-void SyncedBookmarkTracker::AckSequenceNumber(const std::string& sync_id) {
-  auto it = sync_id_to_entities_map_.find(sync_id);
-  Entity* entity =
-      it != sync_id_to_entities_map_.end() ? it->second.get() : nullptr;
-  if (!entity) {
-    DLOG(WARNING) << "Trying to update a non existing entity.";
-    return;
-  }
-  entity->metadata()->set_acked_sequence_number(
-      entity->metadata()->sequence_number());
-}
-
 bool SyncedBookmarkTracker::IsEmpty() const {
   return sync_id_to_entities_map_.empty();
 }
diff --git a/components/sync_bookmarks/synced_bookmark_tracker.h b/components/sync_bookmarks/synced_bookmark_tracker.h
index f101a4f..a8bd878 100644
--- a/components/sync_bookmarks/synced_bookmark_tracker.h
+++ b/components/sync_bookmarks/synced_bookmark_tracker.h
@@ -106,9 +106,6 @@
               const sync_pb::UniquePosition& unique_position,
               const sync_pb::EntitySpecifics& specifics);
 
-  // Updates the server version of an existing entry for the |sync_id|.
-  void UpdateServerVersion(const std::string& sync_id, int64_t server_version);
-
   // This class maintains the order of calls to this method and the same order
   // is gauaranteed when returning local changes in
   // GetEntitiesWithLocalChanges() as well as in BuildBookmarkModelMetadata().
@@ -149,11 +146,6 @@
                                 int64_t acked_sequence_number,
                                 int64_t server_version);
 
-  // Set the value of |EntityMetadata.acked_sequence_number| in the entity with
-  // |sync_id| to be equal to |EntityMetadata.sequence_number| such that it is
-  // not returned in GetEntitiesWithLocalChanges().
-  void AckSequenceNumber(const std::string& sync_id);
-
   // Whether the tracker is empty or not.
   bool IsEmpty() const;
 
diff --git a/components/sync_bookmarks/synced_bookmark_tracker_unittest.cc b/components/sync_bookmarks/synced_bookmark_tracker_unittest.cc
index f368bc2..d609420c 100644
--- a/components/sync_bookmarks/synced_bookmark_tracker_unittest.cc
+++ b/components/sync_bookmarks/synced_bookmark_tracker_unittest.cc
@@ -150,38 +150,6 @@
   // request in a separate test probably.
 }
 
-TEST(SyncedBookmarkTrackerTest, ShouldAckSequenceNumber) {
-  SyncedBookmarkTracker tracker(std::vector<NodeMetadataPair>(),
-                                std::make_unique<sync_pb::ModelTypeState>());
-  const std::string kSyncId = "SYNC_ID";
-  const int64_t kId = 1;
-  const int64_t kServerVersion = 1000;
-  const base::Time kModificationTime(base::Time::Now() -
-                                     base::TimeDelta::FromSeconds(1));
-  const sync_pb::UniquePosition unique_position;
-  const sync_pb::EntitySpecifics specifics =
-      GenerateSpecifics(/*title=*/std::string(), /*url=*/std::string());
-  bookmarks::BookmarkNode node(kId, GURL());
-  tracker.Add(kSyncId, &node, kServerVersion, kModificationTime,
-              unique_position, specifics);
-
-  // Test simple scenario of ack'ing an incrememented sequence number.
-  EXPECT_THAT(tracker.HasLocalChanges(), Eq(false));
-  tracker.IncrementSequenceNumber(kSyncId);
-  EXPECT_THAT(tracker.HasLocalChanges(), Eq(true));
-  tracker.AckSequenceNumber(kSyncId);
-  EXPECT_THAT(tracker.HasLocalChanges(), Eq(false));
-
-  // Test ack'ing of a mutliple times incremented sequence number.
-  tracker.IncrementSequenceNumber(kSyncId);
-  EXPECT_THAT(tracker.HasLocalChanges(), Eq(true));
-  tracker.IncrementSequenceNumber(kSyncId);
-  tracker.IncrementSequenceNumber(kSyncId);
-  EXPECT_THAT(tracker.HasLocalChanges(), Eq(true));
-  tracker.AckSequenceNumber(kSyncId);
-  EXPECT_THAT(tracker.HasLocalChanges(), Eq(false));
-}
-
 TEST(SyncedBookmarkTrackerTest, ShouldUpdateUponCommitResponseWithNewId) {
   SyncedBookmarkTracker tracker(std::vector<NodeMetadataPair>(),
                                 std::make_unique<sync_pb::ModelTypeState>());
diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
index 615d3541..b0c2af3 100644
--- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -1639,6 +1639,10 @@
   RunHtmlTest(FILE_PATH_LITERAL("q.html"));
 }
 
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityReparentCrash) {
+  RunHtmlTest(FILE_PATH_LITERAL("reparent-crash.html"));
+}
+
 IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityReplaceData) {
   RunHtmlTest(FILE_PATH_LITERAL("replace-data.html"));
 }
diff --git a/content/browser/bad_message.h b/content/browser/bad_message.h
index f5e39320..aff08e3 100644
--- a/content/browser/bad_message.h
+++ b/content/browser/bad_message.h
@@ -230,6 +230,8 @@
   PERMISSION_SERVICE_BAD_PERMISSION_DESCRIPTOR = 202,
   RFH_BLOB_URL_TOKEN_FOR_NON_BLOB_URL = 203,
   RFPH_BLOB_URL_TOKEN_FOR_NON_BLOB_URL = 204,
+  RFH_ERROR_PROCESS_NON_ERROR_COMMIT = 205,
+  RFH_ERROR_PROCESS_NON_UNIQUE_ORIGIN_COMMIT = 206,
 
   // Please add new elements here. The naming convention is abbreviated class
   // name (e.g. RenderFrameHost becomes RFH) plus a unique description of the
diff --git a/content/browser/child_process_launcher_helper_mac.cc b/content/browser/child_process_launcher_helper_mac.cc
index 8d4b1bf1..ce5e20ec1 100644
--- a/content/browser/child_process_launcher_helper_mac.cc
+++ b/content/browser/child_process_launcher_helper_mac.cc
@@ -22,6 +22,7 @@
 #include "content/public/common/sandboxed_process_launcher_delegate.h"
 #include "sandbox/mac/seatbelt_exec.h"
 #include "services/service_manager/embedder/result_codes.h"
+#include "services/service_manager/sandbox/mac/audio.sb.h"
 #include "services/service_manager/sandbox/mac/cdm.sb.h"
 #include "services/service_manager/sandbox/mac/common_v2.sb.h"
 #include "services/service_manager/sandbox/mac/gpu_v2.sb.h"
@@ -73,7 +74,8 @@
       service_manager::IsUnsandboxedSandboxType(sandbox_type);
 
   // TODO(kerrnel): Delete this switch once the V2 sandbox is always enabled.
-  bool v2_process = false;
+  bool use_v2 = base::FeatureList::IsEnabled(features::kMacV2Sandbox);
+
   switch (sandbox_type) {
     case service_manager::SANDBOX_TYPE_NO_SANDBOX:
       break;
@@ -84,7 +86,13 @@
     case service_manager::SANDBOX_TYPE_NACL_LOADER:
     case service_manager::SANDBOX_TYPE_PDF_COMPOSITOR:
     case service_manager::SANDBOX_TYPE_PROFILING:
-      v2_process = true;
+      // If the feature experiment is enabled and this process type supports
+      // the v2 sandbox, use it.
+      use_v2 &= true;
+      break;
+    case service_manager::SANDBOX_TYPE_AUDIO:
+      // The audio service only exists with the v2 sandbox.
+      use_v2 |= true;
       break;
     default:
       // This is a 'break' because the V2 sandbox is not enabled for all
@@ -93,9 +101,6 @@
       break;
   }
 
-  bool use_v2 =
-      v2_process && base::FeatureList::IsEnabled(features::kMacV2Sandbox);
-
   if (use_v2 && !no_sandbox) {
     // Generate the profile string.
     std::string profile =
@@ -120,6 +125,9 @@
       case service_manager::SANDBOX_TYPE_PDF_COMPOSITOR:
         profile += service_manager::kSeatbeltPolicyString_pdf_compositor;
         break;
+      case service_manager::SANDBOX_TYPE_AUDIO:
+        profile += service_manager::kSeatbeltPolicyString_audio;
+        break;
       case service_manager::SANDBOX_TYPE_UTILITY:
       case service_manager::SANDBOX_TYPE_PROFILING:
         profile += service_manager::kSeatbeltPolicyString_utility;
@@ -143,6 +151,7 @@
       case service_manager::SANDBOX_TYPE_NACL_LOADER:
       case service_manager::SANDBOX_TYPE_RENDERER:
       case service_manager::SANDBOX_TYPE_PDF_COMPOSITOR:
+      case service_manager::SANDBOX_TYPE_AUDIO:
         SetupCommonSandboxParameters(seatbelt_exec_client_.get());
         break;
       case service_manager::SANDBOX_TYPE_PPAPI:
diff --git a/content/browser/frame_host/frame_tree_browsertest.cc b/content/browser/frame_host/frame_tree_browsertest.cc
index 54e286e..e0be5c945 100644
--- a/content/browser/frame_host/frame_tree_browsertest.cc
+++ b/content/browser/frame_host/frame_tree_browsertest.cc
@@ -31,11 +31,8 @@
 
 namespace {
 
-std::string GetOriginFromRenderer(FrameTreeNode* node) {
-  std::string origin;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      node, "window.domAutomationController.send(document.origin);", &origin));
-  return origin;
+EvalJsResult GetOriginFromRenderer(FrameTreeNode* node) {
+  return EvalJs(node, "document.origin");
 }
 
 }  // namespace
@@ -314,22 +311,24 @@
   // target of the navigation.
   FrameTreeNode* target = root->child_at(0)->child_at(0);
 
-  std::string blob_url_string;
   RenderFrameDeletedObserver deleted_observer(target->current_frame_host());
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      root,
-      "function receiveMessage(event) {"
+  std::string html =
+      "<html><body><div>This is blob content.</div>"
+      "<script>"
+      "window.parent.parent.postMessage('HI', document.origin);"
+      "</script></body></html>";
+  std::string script = JsReplace(
+      "new Promise((resolve) => {"
+      "  window.addEventListener('message', resolve, false);"
+      "  var blob = new Blob([$1], {type: 'text/html'});"
+      "  var blob_url = URL.createObjectURL(blob);"
+      "  frames[0][0].location.href = blob_url;"
+      "}).then((event) => {"
       "  document.body.appendChild(document.createTextNode(event.data));"
-      "  domAutomationController.send(event.source.location.href);"
-      "}"
-      "window.addEventListener('message', receiveMessage, false);"
-      "var blob = new Blob(["
-      "    '<html><body><div>This is blob content.</div><script>"
-      "         window.parent.parent.postMessage(\"HI\", document.origin);"
-      "     </script></body></html>'], {type: 'text/html'});"
-      "var blob_url = URL.createObjectURL(blob);"
-      "frames[0][0].location.href = blob_url;",
-      &blob_url_string));
+      "  return event.source.location.href;"
+      "});",
+      html);
+  std::string blob_url_string = EvalJs(root, script).ExtractString();
   // Wait for the RenderFrame to go away, if this will be cross-process.
   if (AreAllSitesIsolatedForTesting())
     deleted_observer.WaitUntilDeleted();
@@ -338,13 +337,8 @@
   EXPECT_FALSE(target->current_origin().unique());
   EXPECT_EQ("a.com", target->current_origin().host());
   EXPECT_EQ(url::kHttpScheme, target->current_origin().scheme());
-
-  std::string document_body;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      target,
-      "domAutomationController.send(document.body.children[0].innerHTML);",
-      &document_body));
-  EXPECT_EQ("This is blob content.", document_body);
+  EXPECT_EQ("This is blob content.",
+            EvalJs(target, "document.body.children[0].innerHTML"));
   EXPECT_EQ(reference_tree, FrameTreeVisualizer().DepictFrameTree(root));
 }
 
@@ -362,38 +356,34 @@
   FrameTreeNode* initiator = target->parent();
 
   // Give the target a name.
-  EXPECT_TRUE(ExecuteScript(target, "window.name = 'target';"));
+  EXPECT_TRUE(ExecJs(target, "window.name = 'target';"));
 
   // Use window.open(about:blank), then poll the document for access.
-  std::string about_blank_origin;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
+  EvalJsResult about_blank_origin = EvalJs(
       initiator,
-      "var didNavigate = false;"
-      "var intervalID = setInterval(function() {"
-      "  if (!didNavigate) {"
-      "    didNavigate = true;"
-      "    window.open('about:blank', 'target');"
-      "  }"
-      "  // Poll the document until it doesn't throw a SecurityError.\n"
-      "  try {"
-      "    frames[0].document.write('Hi from ' + document.domain);"
-      "  } catch (e) { return; }"
-      "  clearInterval(intervalID);"
-      "  domAutomationController.send(frames[0].document.origin);"
-      "}, 16);",
-      &about_blank_origin));
+      "new Promise(resolve => {"
+      "  var didNavigate = false;"
+      "  var intervalID = setInterval(function() {"
+      "    if (!didNavigate) {"
+      "      didNavigate = true;"
+      "      window.open('about:blank', 'target');"
+      "    }"
+      "    // Poll the document until it doesn't throw a SecurityError.\n"
+      "    try {"
+      "      frames[0].document.write('Hi from ' + document.domain);"
+      "    } catch (e) { return; }"
+      "    clearInterval(intervalID);"
+      "    resolve(frames[0].document.origin);"
+      "  }, 16);"
+      "});");
+  EXPECT_EQ(target->current_origin(), about_blank_origin);
   EXPECT_EQ(GURL(url::kAboutBlankURL), target->current_url());
   EXPECT_EQ(url::kAboutScheme, target->current_url().scheme());
   EXPECT_FALSE(target->current_origin().unique());
   EXPECT_EQ("b.com", target->current_origin().host());
   EXPECT_EQ(url::kHttpScheme, target->current_origin().scheme());
-  EXPECT_EQ(target->current_origin().Serialize(), about_blank_origin);
 
-  std::string document_body;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      target, "domAutomationController.send(document.body.innerHTML);",
-      &document_body));
-  EXPECT_EQ("Hi from b.com", document_body);
+  EXPECT_EQ("Hi from b.com", EvalJs(target, "document.body.innerHTML"));
 }
 
 // Nested iframes, three origins: A(B(C)). Frame A navigates C to about:blank
@@ -414,36 +404,34 @@
   FrameTreeNode* initiator = target->parent()->parent();
 
   // Give the target a name.
-  EXPECT_TRUE(ExecuteScript(target, "window.name = 'target';"));
+  EXPECT_TRUE(ExecJs(target, "window.name = 'target';"));
 
   // Use window.open(about:blank), then poll the document for access.
-  std::string about_blank_origin;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      initiator,
-      "var didNavigate = false;"
-      "var intervalID = setInterval(function() {"
-      "  if (!didNavigate) {"
-      "    didNavigate = true;"
-      "    window.open('about:blank', 'target');"
-      "  }"
-      "  // May raise a SecurityError, that's expected.\n"
-      "  frames[0][0].document.write('Hi from ' + document.domain);"
-      "  clearInterval(intervalID);"
-      "  domAutomationController.send(frames[0][0].document.origin);"
-      "}, 16);",
-      &about_blank_origin));
+  EvalJsResult about_blank_origin =
+      EvalJs(initiator,
+             "new Promise((resolve) => {"
+             "  var didNavigate = false;"
+             "  var intervalID = setInterval(() => {"
+             "    if (!didNavigate) {"
+             "      didNavigate = true;"
+             "      window.open('about:blank', 'target');"
+             "    }"
+             "    // May raise a SecurityError, that's expected.\n"
+             "    try {"
+             "      frames[0][0].document.write('Hi from ' + document.domain);"
+             "    } catch (e) { return; }"
+             "    clearInterval(intervalID);"
+             "    resolve(frames[0][0].document.origin);"
+             "  }, 16);"
+             "});");
+  EXPECT_EQ(target->current_origin(), about_blank_origin);
   EXPECT_EQ(GURL(url::kAboutBlankURL), target->current_url());
   EXPECT_EQ(url::kAboutScheme, target->current_url().scheme());
   EXPECT_FALSE(target->current_origin().unique());
   EXPECT_EQ("a.com", target->current_origin().host());
   EXPECT_EQ(url::kHttpScheme, target->current_origin().scheme());
-  EXPECT_EQ(target->current_origin().Serialize(), about_blank_origin);
 
-  std::string document_body;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      target, "domAutomationController.send(document.body.innerHTML);",
-      &document_body));
-  EXPECT_EQ("Hi from a.com", document_body);
+  EXPECT_EQ("Hi from a.com", EvalJs(target, "document.body.innerHTML"));
 }
 
 // Ensures that iframe with srcdoc is always put in the same origin as its
@@ -458,9 +446,7 @@
   EXPECT_EQ(1U, root->child_count());
 
   FrameTreeNode* child = root->child_at(0);
-  std::string frame_origin;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      child, "domAutomationController.send(document.origin);", &frame_origin));
+  std::string frame_origin = EvalJs(child, "document.origin;").ExtractString();
   EXPECT_TRUE(
       child->current_frame_host()->GetLastCommittedOrigin().IsSameOriginWith(
           url::Origin::Create(GURL(frame_origin))));
@@ -475,18 +461,16 @@
                        "f.srcdoc = 'some content';"
                        "document.body.appendChild(f)");
     TestNavigationObserver observer(shell()->web_contents());
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     EXPECT_EQ(2U, root->child_count());
     observer.Wait();
 
     EXPECT_EQ(GURL(kAboutSrcDocURL), root->child_at(1)->current_url());
-    EXPECT_TRUE(ExecuteScriptAndExtractString(
-        root->child_at(1), "domAutomationController.send(document.origin);",
-        &frame_origin));
+    EvalJsResult frame_origin = EvalJs(root->child_at(1), "document.origin");
     EXPECT_EQ(root->current_frame_host()->GetLastCommittedURL().GetOrigin(),
-              GURL(frame_origin));
+              GURL(frame_origin.ExtractString()));
     EXPECT_NE(child->current_frame_host()->GetLastCommittedURL().GetOrigin(),
-              GURL(frame_origin));
+              GURL(frame_origin.ExtractString()));
   }
 
   // Set srcdoc on the existing cross-site frame. It should navigate the frame
@@ -495,15 +479,14 @@
     std::string script("var f = document.getElementById('child-0');"
                        "f.srcdoc = 'some content';");
     TestNavigationObserver observer(shell()->web_contents());
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     observer.Wait();
 
     EXPECT_EQ(GURL(kAboutSrcDocURL), child->current_url());
-    EXPECT_TRUE(ExecuteScriptAndExtractString(
-        child, "domAutomationController.send(document.origin);",
-        &frame_origin));
-    EXPECT_EQ(root->current_frame_host()->GetLastCommittedURL().GetOrigin(),
-              GURL(frame_origin));
+    EXPECT_EQ(
+        url::Origin::Create(root->current_frame_host()->GetLastCommittedURL())
+            .Serialize(),
+        EvalJs(child, "document.origin"));
   }
 }
 
@@ -687,8 +670,8 @@
   // Open a new window from a subframe.
   ShellAddedObserver new_shell_observer;
   GURL popup_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
-  EXPECT_TRUE(ExecuteScript(root->child_at(0),
-                            JsReplace("window.open($1);", popup_url)));
+  EXPECT_TRUE(
+      ExecJs(root->child_at(0), JsReplace("window.open($1);", popup_url)));
   Shell* new_shell = new_shell_observer.GetShell();
   WebContents* new_contents = new_shell->web_contents();
   WaitForLoadStop(new_contents);
@@ -897,15 +880,15 @@
   // The navigation targets an invalid blob url; that's intentional to trigger
   // an error response. The response should commit in a process dedicated to
   // http://b.is.
-  std::string result;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      root,
-      "var iframe_element = document.getElementsByTagName('iframe')[0];"
-      "iframe_element.onload = () => {"
-      "    domAutomationController.send('done');"
-      "};"
-      "iframe_element.src = 'blob:http://b.is:2932/';",
-      &result));
+  EXPECT_EQ(
+      "done",
+      EvalJs(
+          root,
+          "new Promise((resolve) => {"
+          "  var iframe_element = document.getElementsByTagName('iframe')[0];"
+          "  iframe_element.onload = () => resolve('done');"
+          "  iframe_element.src = 'blob:http://b.is:2932/';"
+          "});"));
   WaitForLoadStop(contents);
 
   // Make sure we did a process transfer back to "b.is".
diff --git a/content/browser/frame_host/navigation_controller_impl_browsertest.cc b/content/browser/frame_host/navigation_controller_impl_browsertest.cc
index 9e0eae5d..818e4d5 100644
--- a/content/browser/frame_host/navigation_controller_impl_browsertest.cc
+++ b/content/browser/frame_host/navigation_controller_impl_browsertest.cc
@@ -260,7 +260,7 @@
       "var xhr = new XMLHttpRequest();\n"
       "xhr.open('GET', url);\n"
       "xhr.send();\n";
-  EXPECT_TRUE(ExecuteScript(shell()->web_contents(), script));
+  EXPECT_TRUE(ExecJs(shell()->web_contents(), script));
   // The renderer may not be killed immediately (if it is indeed killed), so
   // reload, block and verify its liveness.
   ReloadBlockUntilNavigationsComplete(shell(), 1);
@@ -328,7 +328,7 @@
     GURL navigate_url = embedded_test_server()->base_url();
     std::string script = JsReplace("document.location = $1", navigate_url);
     TestNavigationObserver same_tab_observer(shell()->web_contents(), 1);
-    EXPECT_TRUE(ExecuteScript(shell(), script));
+    EXPECT_TRUE(ExecJs(shell(), script));
     same_tab_observer.Wait();
     EXPECT_EQ(2, controller.GetEntryCount());
     NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
@@ -377,7 +377,7 @@
   EXPECT_EQ(data_url, entry->GetURL());
 
   // Passes if renderer is still alive.
-  EXPECT_TRUE(ExecuteScript(shell(), "console.log('Success');"));
+  EXPECT_TRUE(ExecJs(shell(), "console.log('Success');"));
 }
 
 IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, UniqueIDs) {
@@ -392,7 +392,7 @@
 
   // Use JavaScript to click the link and load the iframe.
   std::string script = "document.getElementById('link').click()";
-  EXPECT_TRUE(ExecuteScript(shell(), script));
+  EXPECT_TRUE(ExecJs(shell(), script));
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
   ASSERT_EQ(2, controller.GetEntryCount());
 
@@ -494,21 +494,13 @@
 
 namespace {
 
-int RendererHistoryLength(Shell* shell) {
-  int value = 0;
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      shell, "domAutomationController.send(history.length)", &value));
-  return value;
-}
-
 // Does a renderer-initiated location.replace navigation to |url|, replacing the
 // current entry.
 bool RendererLocationReplace(Shell* shell, const GURL& url) {
   WebContents* web_contents = shell->web_contents();
   WaitForLoadStop(web_contents);
   TestNavigationObserver same_tab_observer(web_contents, 1);
-  EXPECT_TRUE(
-      ExecuteScript(shell, JsReplace("window.location.replace($1)", url)));
+  EXPECT_TRUE(ExecJs(shell, JsReplace("window.location.replace($1)", url)));
   same_tab_observer.Wait();
   if (!IsLastCommittedEntryOfPageType(web_contents, PAGE_TYPE_NORMAL))
     return false;
@@ -527,24 +519,24 @@
   EXPECT_TRUE(NavigateToURL(
       shell(), embedded_test_server()->GetURL("/simple_page.html")));
   EXPECT_EQ(1, controller.GetEntryCount());
-  EXPECT_EQ(1, RendererHistoryLength(shell()));
+  EXPECT_EQ(1, EvalJs(shell(), "history.length"));
 
   EXPECT_TRUE(RendererLocationReplace(
       shell(), embedded_test_server()->GetURL("/title1.html")));
   EXPECT_EQ(1, controller.GetEntryCount());
-  EXPECT_EQ(1, RendererHistoryLength(shell()));
+  EXPECT_EQ(1, EvalJs(shell(), "history.length"));
 
   // Now create two more entries and go back, to test replacing an entry without
   // pruning the forward history.
   EXPECT_TRUE(
       NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html")));
   EXPECT_EQ(2, controller.GetEntryCount());
-  EXPECT_EQ(2, RendererHistoryLength(shell()));
+  EXPECT_EQ(2, EvalJs(shell(), "history.length"));
 
   EXPECT_TRUE(
       NavigateToURL(shell(), embedded_test_server()->GetURL("/title3.html")));
   EXPECT_EQ(3, controller.GetEntryCount());
-  EXPECT_EQ(3, RendererHistoryLength(shell()));
+  EXPECT_EQ(3, EvalJs(shell(), "history.length"));
 
   controller.GoBack();
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
@@ -555,7 +547,7 @@
   EXPECT_TRUE(RendererLocationReplace(
       shell(), embedded_test_server()->GetURL("/simple_page.html?page1b")));
   EXPECT_EQ(3, controller.GetEntryCount());
-  EXPECT_EQ(3, RendererHistoryLength(shell()));
+  EXPECT_EQ(3, EvalJs(shell(), "history.length"));
   EXPECT_TRUE(controller.CanGoForward());
 
   // Note that there's no way to access the renderer's notion of the history
@@ -578,15 +570,13 @@
   ShellAddedObserver observer;
   GURL page_url = embedded_test_server()->GetURL(
       "/navigation_controller/simple_page_1.html");
-  {
-    EXPECT_TRUE(ExecuteScript(
-        shell(), JsReplace("window.open($1, '_blank');", page_url)));
-  }
+  EXPECT_TRUE(
+      ExecJs(shell(), JsReplace("window.open($1, '_blank');", page_url)));
   Shell* shell2 = observer.GetShell();
   EXPECT_TRUE(WaitForLoadStop(shell2->web_contents()));
 
   EXPECT_EQ(1, shell2->web_contents()->GetController().GetEntryCount());
-  EXPECT_EQ(1, RendererHistoryLength(shell2));
+  EXPECT_EQ(1, EvalJs(shell2, "history.length"));
 
   // Again, as above, there's no way to access the renderer's notion of the
   // history offset via JavaScript. Checking just the history length, again,
@@ -852,7 +842,7 @@
   ShellAddedObserver new_shell_observer;
   {
     std::string script = "window.open()";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
   }
   Shell* new_shell = new_shell_observer.GetShell();
   ASSERT_NE(new_shell->web_contents(), shell()->web_contents());
@@ -870,7 +860,7 @@
     std::string script = "var iframe = document.createElement('iframe');"
                          "iframe.src = 'data:text/html,<p>some page</p>';"
                          "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(new_root, script));
+    EXPECT_TRUE(ExecJs(new_root, script));
     capturer.Wait();
   }
   ASSERT_EQ(1U, new_root->child_count());
@@ -882,7 +872,7 @@
   {
     LoadCommittedCapturer capturer(new_shell->web_contents());
     std::string script = JsReplace("location.assign($1);", frame_url);
-    EXPECT_TRUE(ExecuteScript(new_root->child_at(0), script));
+    EXPECT_TRUE(ExecJs(new_root->child_at(0), script));
     capturer.Wait();
   }
 
@@ -898,7 +888,7 @@
     std::string script = "var iframe = document.createElement('iframe');"
                          "iframe.src = '" + grandchild_url.spec() + "';"
                          "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(new_root->child_at(0), script));
+    EXPECT_TRUE(ExecJs(new_root->child_at(0), script));
     capturer.Wait();
   }
   ASSERT_EQ(1U, new_root->child_at(0)->child_count());
@@ -919,7 +909,7 @@
 
   // Pop open a new window to about:blank.
   ShellAddedObserver new_shell_observer;
-  EXPECT_TRUE(ExecuteScript(root, "var w = window.open('about:blank')"));
+  EXPECT_TRUE(ExecJs(root, "var w = window.open('about:blank')"));
   Shell* new_shell = new_shell_observer.GetShell();
   ASSERT_NE(new_shell->web_contents(), shell()->web_contents());
   FrameTreeNode* new_root =
@@ -937,7 +927,7 @@
         "w.document.write($1);"
         "w.document.close();",
         html);
-    EXPECT_TRUE(ExecuteScript(root->current_frame_host(), script));
+    EXPECT_TRUE(ExecJs(root->current_frame_host(), script));
     capturer.Wait();
   }
   ASSERT_EQ(1U, new_root->child_count());
@@ -950,7 +940,7 @@
   {
     LoadCommittedCapturer capturer(new_root->child_at(0));
     std::string script = "location.href = '" + url2.spec() + "';";
-    EXPECT_TRUE(ExecuteScript(new_root->child_at(0), script));
+    EXPECT_TRUE(ExecJs(new_root->child_at(0), script));
     capturer.Wait();
   }
   EXPECT_EQ(blank_url, new_root->current_url());
@@ -962,7 +952,7 @@
   {
     LoadCommittedCapturer capturer(new_root);
     std::string script = "history.replaceState({}, 'foo', 'foo');";
-    EXPECT_TRUE(ExecuteScript(new_root, script));
+    EXPECT_TRUE(ExecJs(new_root, script));
     capturer.Wait();
   }
   EXPECT_EQ(embedded_test_server()->GetURL("/navigation_controller/foo"),
@@ -1097,7 +1087,7 @@
     // Load via a fragment link click.
     FrameNavigateParamsCapturer capturer(root);
     std::string script = "document.getElementById('fraglink').click()";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition(), ui::PAGE_TRANSITION_LINK));
@@ -1109,7 +1099,7 @@
     // Load via link click.
     FrameNavigateParamsCapturer capturer(root);
     std::string script = "document.getElementById('thelink').click()";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition(), ui::PAGE_TRANSITION_LINK));
@@ -1123,7 +1113,7 @@
     GURL frame_url(embedded_test_server()->GetURL(
         "/navigation_controller/simple_page_2.html"));
     std::string script = JsReplace("location.assign($1);", frame_url);
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition(),
@@ -1138,7 +1128,7 @@
     FrameNavigateParamsCapturer capturer(root);
     std::string script =
         "history.pushState({}, 'page 1', 'simple_page_1.html')";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition(),
@@ -1153,7 +1143,7 @@
   GURL frame_url(embedded_test_server()->GetURL(
       "foo.com", "/navigation_controller/simple_page_1.html"));
   std::string script = JsReplace("location.replace($1);", frame_url);
-  EXPECT_TRUE(ExecuteScript(root, script));
+  EXPECT_TRUE(ExecJs(root, script));
   capturer.Wait();
   EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
       capturer.transition(),
@@ -1210,7 +1200,7 @@
   {
     // Back from the renderer side.
     FrameNavigateParamsCapturer capturer(root);
-    EXPECT_TRUE(ExecuteScript(root, "history.back()"));
+    EXPECT_TRUE(ExecJs(root, "history.back()"));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition(),
@@ -1224,7 +1214,7 @@
   {
     // Forward from the renderer side.
     FrameNavigateParamsCapturer capturer(root);
-    EXPECT_TRUE(ExecuteScript(root, "history.forward()"));
+    EXPECT_TRUE(ExecJs(root, "history.forward()"));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition(),
@@ -1238,7 +1228,7 @@
   {
     // Back from the renderer side via history.go().
     FrameNavigateParamsCapturer capturer(root);
-    EXPECT_TRUE(ExecuteScript(root, "history.go(-1)"));
+    EXPECT_TRUE(ExecJs(root, "history.go(-1)"));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition(),
@@ -1252,7 +1242,7 @@
   {
     // Forward from the renderer side via history.go().
     FrameNavigateParamsCapturer capturer(root);
-    EXPECT_TRUE(ExecuteScript(root, "history.go(1)"));
+    EXPECT_TRUE(ExecJs(root, "history.go(1)"));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition(),
@@ -1277,7 +1267,7 @@
   {
     // Reload from the renderer side.
     FrameNavigateParamsCapturer capturer(root);
-    EXPECT_TRUE(ExecuteScript(root, "location.reload()"));
+    EXPECT_TRUE(ExecJs(root, "location.reload()"));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition(),
@@ -1293,7 +1283,7 @@
     GURL frame_url(embedded_test_server()->GetURL(
         "/navigation_controller/simple_page_1.html"));
     std::string script = JsReplace("location.replace($1);", frame_url);
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition(),
@@ -1326,7 +1316,7 @@
     FrameNavigateParamsCapturer capturer(root);
     std::string script =
         "history.replaceState({}, 'page 2', 'simple_page_2.html')";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition(),
@@ -1342,7 +1332,7 @@
       "/navigation_controller/page_with_links.html"));
   EXPECT_TRUE(NavigateToURL(shell(), url_links));
   std::string script = "document.getElementById('fraglink').click()";
-  EXPECT_TRUE(ExecuteScript(root, script));
+  EXPECT_TRUE(ExecJs(root, script));
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
 
   {
@@ -1376,7 +1366,7 @@
 
   EXPECT_TRUE(NavigateToURL(shell(), url1));
   script = "history.pushState({}, 'page 2', 'simple_page_2.html')";
-  EXPECT_TRUE(ExecuteScript(root, script));
+  EXPECT_TRUE(ExecJs(root, script));
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
 
   {
@@ -1438,11 +1428,7 @@
       "/navigation_controller/reload-with-url-anchor.html#center-element"));
   EXPECT_TRUE(NavigateToURL(shell(), url));
 
-  double window_scroll_y = 0;
-  std::string get_window_scroll_y =
-      "domAutomationController.send(window.scrollY);";
-  EXPECT_TRUE(ExecuteScriptAndExtractDouble(shell(), get_window_scroll_y,
-                                            &window_scroll_y));
+  double window_scroll_y = EvalJs(shell(), "window.scrollY").ExtractDouble();
 
   // The 'center-element' y-position is 2000px. 2000px is an arbitrary value.
   double expected_window_scroll_y = 2000;
@@ -1460,8 +1446,7 @@
   // Reload.
   ReloadBlockUntilNavigationsComplete(shell(), 1);
 
-  EXPECT_TRUE(ExecuteScriptAndExtractDouble(shell(), get_window_scroll_y,
-                                            &window_scroll_y));
+  window_scroll_y = EvalJs(shell(), "window.scrollY").ExtractDouble();
   EXPECT_FLOAT_EQ(expected_window_scroll_y, window_scroll_y);
 }
 
@@ -1478,11 +1463,7 @@
   std::string script_scroll_down = "window.scroll(0, 2100)";
   EXPECT_TRUE(ExecuteScript(shell(), script_scroll_down));
 
-  std::string get_window_scroll_y =
-      "domAutomationController.send(window.scrollY)";
-  double window_scroll_y = 0;
-  EXPECT_TRUE(ExecuteScriptAndExtractDouble(shell(), get_window_scroll_y,
-                                            &window_scroll_y));
+  double window_scroll_y = EvalJs(shell(), "window.scrollY").ExtractDouble();
 
   double expected_window_scroll_y = 2100;
   if (IsUseZoomForDSFEnabled()) {
@@ -1499,8 +1480,7 @@
   // Reload.
   ReloadBlockUntilNavigationsComplete(shell(), 1);
 
-  EXPECT_TRUE(ExecuteScriptAndExtractDouble(shell(), get_window_scroll_y,
-                                            &window_scroll_y));
+  window_scroll_y = EvalJs(shell(), "window.scrollY").ExtractDouble();
   EXPECT_FLOAT_EQ(expected_window_scroll_y, window_scroll_y);
 }
 
@@ -1603,7 +1583,7 @@
     // Load via a fragment link click.
     FrameNavigateParamsCapturer capturer(root->child_at(0));
     std::string script = "document.getElementById('fraglink').click()";
-    EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
+    EXPECT_TRUE(ExecJs(root->child_at(0), script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition(), ui::PAGE_TRANSITION_MANUAL_SUBFRAME));
@@ -1616,7 +1596,7 @@
     GURL frame_url(embedded_test_server()->GetURL(
         "/navigation_controller/simple_page_1.html"));
     std::string script = JsReplace("location.assign($1);", frame_url);
-    EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
+    EXPECT_TRUE(ExecJs(root->child_at(0), script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition(), ui::PAGE_TRANSITION_MANUAL_SUBFRAME));
@@ -1629,7 +1609,7 @@
     GURL frame_url(embedded_test_server()->GetURL(
         "/navigation_controller/simple_page_2.html"));
     std::string script = JsReplace("location.replace($1);", frame_url);
-    EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
+    EXPECT_TRUE(ExecJs(root->child_at(0), script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
@@ -1640,7 +1620,7 @@
     FrameNavigateParamsCapturer capturer(root->child_at(0));
     std::string script =
         "history.pushState({}, 'page 1', 'simple_page_1.html')";
-    EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
+    EXPECT_TRUE(ExecJs(root->child_at(0), script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition(), ui::PAGE_TRANSITION_MANUAL_SUBFRAME));
@@ -1652,7 +1632,7 @@
     LoadCommittedCapturer capturer(root->child_at(0));
     std::string script =
         "history.replaceState({}, 'page 2', 'simple_page_2.html')";
-    EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
+    EXPECT_TRUE(ExecJs(root->child_at(0), script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
@@ -1661,7 +1641,7 @@
   {
     // Reload.
     LoadCommittedCapturer capturer(root->child_at(0));
-    EXPECT_TRUE(ExecuteScript(root->child_at(0), "location.reload()"));
+    EXPECT_TRUE(ExecJs(root->child_at(0), "location.reload()"));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
@@ -1675,7 +1655,7 @@
     std::string script = "var iframe = document.createElement('iframe');"
                          "iframe.src = '" + frame_url.spec() + "';"
                          "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
@@ -1733,7 +1713,7 @@
     // Do a fragment link click.
     FrameNavigateParamsCapturer capturer(root);
     std::string script = "document.getElementById('fraglink').click()";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition(), ui::PAGE_TRANSITION_LINK));
@@ -1745,7 +1725,7 @@
     // Do a non-fragment link click.
     FrameNavigateParamsCapturer capturer(root);
     std::string script = "document.getElementById('thelink').click()";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition(), ui::PAGE_TRANSITION_LINK));
@@ -1772,7 +1752,7 @@
     // Do a fragment link click.
     FrameNavigateParamsCapturer capturer(root->child_at(0));
     std::string script = "document.getElementById('fraglink').click()";
-    EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
+    EXPECT_TRUE(ExecJs(root->child_at(0), script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition(), ui::PAGE_TRANSITION_MANUAL_SUBFRAME));
@@ -1784,7 +1764,7 @@
     // Do a non-fragment link click.
     FrameNavigateParamsCapturer capturer(root->child_at(0));
     std::string script = "document.getElementById('thelink').click()";
-    EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
+    EXPECT_TRUE(ExecJs(root->child_at(0), script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition(), ui::PAGE_TRANSITION_MANUAL_SUBFRAME));
@@ -1813,7 +1793,7 @@
     LoadCommittedCapturer capturer(shell()->web_contents());
     std::string script = "var iframe = document.createElement('iframe');"
                          "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
@@ -1840,7 +1820,7 @@
     LoadCommittedCapturer capturer(shell()->web_contents());
     std::string script = "var iframe = document.createElement('iframe');"
                          "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
+    EXPECT_TRUE(ExecJs(root->child_at(0), script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
@@ -1859,7 +1839,7 @@
     std::string script = "var iframe = document.createElement('iframe');"
                          "iframe.src = 'about:blank';"
                          "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
@@ -1879,14 +1859,13 @@
   // 3. A real same-site navigation in the nested iframe should be AUTO.
   GURL frame_url(embedded_test_server()->GetURL(
       "/navigation_controller/simple_page_1.html"));
-
   {
     LoadCommittedCapturer capturer(root->child_at(0)->child_at(0));
     std::string script = JsReplace(
         "var frames = document.getElementsByTagName('iframe');"
         "frames[0].src = $1;",
         frame_url);
-    EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
+    EXPECT_TRUE(ExecJs(root->child_at(0), script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
@@ -1914,7 +1893,7 @@
         "var frames = document.getElementsByTagName('iframe');"
         "frames[1].src = $1;",
         foo_url);
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
@@ -1939,7 +1918,7 @@
     LoadCommittedCapturer capturer(root->child_at(0)->child_at(0));
     std::string script = "var frames = document.getElementsByTagName('iframe');"
                          "frames[0].src = 'about:blank';";
-    EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
+    EXPECT_TRUE(ExecJs(root->child_at(0), script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_MANUAL_SUBFRAME));
@@ -1994,7 +1973,7 @@
       "iframe.src = $1;"
       "document.body.appendChild(iframe);";
 
-  EXPECT_TRUE(ExecuteScript(root, JsReplace(script_template, slow_url)));
+  EXPECT_TRUE(ExecJs(root, JsReplace(script_template, slow_url)));
   EXPECT_TRUE(subframe_delayer.WaitForRequestStart());
 
   // Stop the request so that we can wait for load stop below, without ending up
@@ -2004,8 +1983,7 @@
   // 2. A nested iframe with a cross-site URL should be able to commit.
   GURL foo_url(embedded_test_server()->GetURL(
       "foo.com", "/navigation_controller/simple_page_1.html"));
-  EXPECT_TRUE(
-      ExecuteScript(root->child_at(0), JsReplace(script_template, foo_url)));
+  EXPECT_TRUE(ExecJs(root->child_at(0), JsReplace(script_template, foo_url)));
   WaitForLoadStopWithoutSuccessCheck(shell()->web_contents());
 
   // TODO(creis): Check subframe entries once we create them in this case.
@@ -2033,7 +2011,7 @@
     std::string script = "var iframe = document.createElement('iframe');"
                          "iframe.src = '" + no_commit_url.spec() + "';"
                          "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
   }
   EXPECT_EQ(GURL(), root->child_at(0)->current_url());
 
@@ -2045,7 +2023,7 @@
     std::string script = "var iframe = document.createElement('iframe');"
                          "iframe.src = '" + foo_url.spec() + "';"
                          "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
+    EXPECT_TRUE(ExecJs(root->child_at(0), script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
@@ -2073,7 +2051,7 @@
   {
     FrameNavigateParamsCapturer capturer(root);
     std::string script = "history.pushState({}, 'foo', 'foo')";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
     EXPECT_EQ(NAVIGATION_TYPE_NEW_PAGE, capturer.navigation_type());
     EXPECT_TRUE(capturer.is_same_document());
@@ -2087,7 +2065,7 @@
     std::string script = "var iframe = document.createElement('iframe');"
                          "iframe.src = '" + child_url.spec() + "';"
                          "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
@@ -2108,7 +2086,7 @@
     std::string script = "var iframe = document.createElement('iframe');"
                          "iframe.src = '" + grandchild_url.spec() + "';"
                          "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
+    EXPECT_TRUE(ExecJs(root->child_at(0), script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
@@ -2139,14 +2117,14 @@
     std::string script = "var iframe = document.createElement('iframe');"
                          "iframe.src = '" + child_url.spec() + "';"
                          "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
   }
 
   // 2. Change the iframe's name.
-  EXPECT_TRUE(ExecuteScript(root->child_at(0), "window.name = 'foo';"));
+  EXPECT_TRUE(ExecJs(root->child_at(0), "window.name = 'foo';"));
 
   // 3. A nested iframe with a cross-site URL should be able to commit.
   GURL bar_url(embedded_test_server()->GetURL(
@@ -2156,7 +2134,7 @@
     std::string script = "var iframe = document.createElement('iframe');"
                          "iframe.src = '" + bar_url.spec() + "';"
                          "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
+    EXPECT_TRUE(ExecJs(root->child_at(0), script));
 
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
@@ -2191,7 +2169,7 @@
     std::string script = "var iframe = document.createElement('iframe');"
                          "iframe.src = '" + frame_url.spec() + "';"
                          "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
@@ -2220,7 +2198,7 @@
     std::string script = "var iframe = document.createElement('iframe');"
                          "iframe.src = '" + foo_url.spec() + "';"
                          "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
@@ -2246,7 +2224,7 @@
     std::string script = "var iframe = document.createElement('iframe');"
                          "iframe.src = '" + foo_url.spec() + "';"
                          "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(root->child_at(1), script));
+    EXPECT_TRUE(ExecJs(root->child_at(1), script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
@@ -2272,7 +2250,7 @@
     std::string script = "var iframe = document.createElement('iframe');"
                          "iframe.src = '" + foo_url.spec() + "';"
                          "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
@@ -2297,7 +2275,7 @@
                          "iframe.src = '" + frame_url.spec() + "';"
                          "document.body.appendChild(iframe);";
     FrameTreeNode* child = root->child_at(2);
-    EXPECT_TRUE(ExecuteScript(child, script));
+    EXPECT_TRUE(ExecJs(child, script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
@@ -2354,7 +2332,7 @@
     std::string script = "var iframe = document.createElement('iframe');"
                          "iframe.src = '" + frame_url.spec() + "';"
                          "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
   }
   NavigationEntryImpl* entry = controller.GetLastCommittedEntry();
@@ -2392,7 +2370,7 @@
     std::string script = "var iframe = document.createElement('iframe');"
                          "iframe.src = '" + foo_url.spec() + "';"
                          "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
   }
 
@@ -2403,7 +2381,7 @@
     std::string script = "var iframe = document.createElement('iframe');"
                          "iframe.src = '" + foo_url.spec() + "';"
                          "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(root->child_at(1), script));
+    EXPECT_TRUE(ExecJs(root->child_at(1), script));
     capturer.Wait();
   }
   GURL bar_url(embedded_test_server()->GetURL(
@@ -2448,7 +2426,7 @@
         root->child_at(1)->current_frame_host());
     std::string script = "var frames = document.getElementsByTagName('iframe');"
                          "frames[1].src = '" + baz_url.spec() + "';";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     // Wait for the RenderFrame to go away, if this will be cross-process.
     if (AreAllSitesIsolatedForTesting())
       deleted_observer.WaitUntilDeleted();
@@ -2518,7 +2496,7 @@
 
   // 2. Same document navigation in the main frame.
   std::string push_script = "history.pushState({}, 'page 2', 'page_2.html')";
-  EXPECT_TRUE(ExecuteScript(root, push_script));
+  EXPECT_TRUE(ExecJs(root, push_script));
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
 
   // The entry should have a FrameNavigationEntry for the subframe.
@@ -2532,7 +2510,7 @@
     std::string script = "var iframe = document.createElement('iframe');"
                          "iframe.src = '" + subframe_url.spec() + "';"
                          "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
+    EXPECT_TRUE(ExecJs(root->child_at(0), script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
@@ -2569,7 +2547,7 @@
     std::string script = "var iframe = document.createElement('iframe');"
                          "iframe.src = '" + frame_url.spec() + "';"
                          "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
   }
   NavigationEntryImpl* entry1 = controller.GetLastCommittedEntry();
@@ -2815,7 +2793,7 @@
                              ->frame_entry->document_sequence_number());
 
   // Inject a JS value so that we can check for it later.
-  EXPECT_TRUE(content::ExecuteScript(root, "foo=3;"));
+  EXPECT_TRUE(ExecJs(root, "foo=3;"));
 
   // 7. Go back again, to the data URL in the nested iframe.
   {
@@ -2841,12 +2819,7 @@
             entry3->root_node()->children[0]->children[0]->frame_entry->url());
 
   // Verify that we did not reload the main frame. See https://crbug.com/586234.
-  {
-    int value = 0;
-    EXPECT_TRUE(ExecuteScriptAndExtractInt(
-        root, "domAutomationController.send(foo)", &value));
-    EXPECT_EQ(3, value);
-  }
+  EXPECT_EQ(3, EvalJs(root, "foo"));
 
   // 8. Go back again, to the data URL in the first subframe.
   {
@@ -3109,14 +3082,8 @@
   EXPECT_EQ(blank_url, root->child_at(0)->current_url());
 
   // Verify that the parent was able to script the iframe.
-  std::string expected_text("Injected text");
-  {
-    std::string value;
-    EXPECT_TRUE(ExecuteScriptAndExtractString(
-        root->child_at(0),
-        "domAutomationController.send(document.body.innerHTML)", &value));
-    EXPECT_EQ(expected_text, value);
-  }
+  EXPECT_EQ("Injected text",
+            EvalJs(root->child_at(0), "document.body.innerHTML"));
 
   EXPECT_EQ(1, controller.GetEntryCount());
   EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
@@ -3147,13 +3114,8 @@
   EXPECT_EQ(blank_url, root->child_at(0)->current_url());
 
   // Verify that the parent was able to script the iframe.
-  {
-    std::string value;
-    EXPECT_TRUE(ExecuteScriptAndExtractString(
-        root->child_at(0),
-        "domAutomationController.send(document.body.innerHTML)", &value));
-    EXPECT_EQ(expected_text, value);
-  }
+  EXPECT_EQ("Injected text",
+            EvalJs(root->child_at(0), "document.body.innerHTML"));
 
   EXPECT_EQ(2, controller.GetEntryCount());
   EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
@@ -3202,9 +3164,8 @@
             entry->root_node()->children[0]->children[0]->frame_entry->url());
 
   // Set a value in the form which will be stored in the PageState.
-  EXPECT_TRUE(
-      ExecuteScript(root->child_at(0)->child_at(0),
-                    "document.getElementById('itext').value = 'modified';"));
+  EXPECT_TRUE(ExecJs(root->child_at(0)->child_at(0),
+                     "document.getElementById('itext').value = 'modified';"));
 
   // 2. Navigate the main frame same-site, destroying the subframes.
   GURL main_url_2(embedded_test_server()->GetURL(
@@ -3246,13 +3207,8 @@
 
   // With injected about:blank iframes, we never restore form values from
   // PageState.
-  std::string form_value = "fail";
-  EXPECT_TRUE(
-      ExecuteScriptAndExtractString(root->child_at(0)->child_at(0),
-                                    "window.domAutomationController.send("
-                                    "document.getElementById('itext').value);",
-                                    &form_value));
-  EXPECT_EQ("", form_value);
+  EXPECT_EQ("", EvalJs(root->child_at(0)->child_at(0),
+                       "document.getElementById('itext').value"));
 }
 
 // Verify that we correctly load a nested iframe created by an injected iframe
@@ -3300,9 +3256,8 @@
             entry->root_node()->children[0]->children[0]->frame_entry->url());
 
   // Set a value in the form which will be stored in the PageState.
-  EXPECT_TRUE(
-      ExecuteScript(root->child_at(0)->child_at(0),
-                    "document.getElementById('itext').value = 'modified';"));
+  EXPECT_TRUE(ExecJs(root->child_at(0)->child_at(0),
+                     "document.getElementById('itext').value = 'modified';"));
 
   // 2. Navigate the main frame same-site, destroying the subframes.
   GURL main_url_2(embedded_test_server()->GetURL(
@@ -3352,13 +3307,8 @@
   // Note that restoring form values in srcdoc frames created via static html is
   // expected to work and is tested by
   // RenderFrameHostManagerTest.RestoreSubframeFileAccessForHistoryNavigation.
-  std::string form_value = "fail";
-  EXPECT_TRUE(
-      ExecuteScriptAndExtractString(root->child_at(0)->child_at(0),
-                                    "window.domAutomationController.send("
-                                    "document.getElementById('itext').value);",
-                                    &form_value));
-  EXPECT_EQ("", form_value);
+  EXPECT_EQ("", EvalJs(root->child_at(0)->child_at(0),
+                       "document.getElementById('itext').value"));
 }
 
 // Verify that we can load about:blank in an iframe when going back to a page,
@@ -3466,7 +3416,7 @@
   EXPECT_EQ(main_url, root->current_url());
 
   // Check that the renderer is still alive.
-  EXPECT_TRUE(ExecuteScript(shell(), "console.log('Success');"));
+  EXPECT_TRUE(ExecJs(shell(), "console.log('Success');"));
 
   EXPECT_EQ(2, controller.GetEntryCount());
   EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
@@ -3536,7 +3486,7 @@
     TestNavigationObserver observer(shell()->web_contents());
     std::string script = "history.replaceState({}, '', '/server-redirect?" +
                          frame_redirect_dest_url.spec() + "')";
-    EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
+    EXPECT_TRUE(ExecJs(root->child_at(0), script));
     observer.Wait();
   }
 
@@ -3592,7 +3542,7 @@
     TestNavigationObserver observer(shell()->web_contents());
     std::string script = "history.replaceState({}, '', '/server-redirect?" +
                          frame_redirect_dest_url.spec() + "')";
-    EXPECT_TRUE(ExecuteScript(root->child_at(1), script));
+    EXPECT_TRUE(ExecJs(root->child_at(1), script));
     observer.Wait();
   }
 
@@ -3640,7 +3590,7 @@
     TestNavigationObserver observer(shell()->web_contents());
     std::string script = "history.replaceState({}, '', '/server-redirect?" +
                          redirect_dest_url.spec() + "')";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     observer.Wait();
   }
 
@@ -3708,7 +3658,7 @@
     TestNavigationObserver observer(shell()->web_contents());
     std::string script = "history.replaceState({}, '', '/server-redirect?" +
                          frame_redirect_dest_url.spec() + "')";
-    EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
+    EXPECT_TRUE(ExecJs(root->child_at(0), script));
     observer.Wait();
   }
 
@@ -3757,7 +3707,7 @@
     TestNavigationObserver observer(shell()->web_contents());
     std::string script = "history.replaceState({}, '', '/server-redirect?" +
                          redirect_dest_url.spec() + "')";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     observer.Wait();
   }
 
@@ -3948,14 +3898,8 @@
   EXPECT_EQ(blank_url, new_entry->root_node()->children[0]->frame_entry->url());
 
   // Verify that the parent was able to script the iframe.
-  std::string expected_text("Injected text");
-  {
-    std::string value;
-    EXPECT_TRUE(ExecuteScriptAndExtractString(
-        root->child_at(0),
-        "domAutomationController.send(document.body.innerHTML)", &value));
-    EXPECT_EQ(expected_text, value);
-  }
+  EXPECT_EQ("Injected text",
+            EvalJs(root->child_at(0), "document.body.innerHTML"));
 }
 
 // Verifies that the |frame_unique_name| is set to the correct frame, so that we
@@ -3988,7 +3932,7 @@
     std::string script = "var iframe = document.createElement('iframe');"
                          "iframe.src = '" + url.spec() + "';"
                          "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
@@ -4014,7 +3958,7 @@
                          "iframe.src = '" + url.spec() + "';"
                          "iframe.name = 'foo';"
                          "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
@@ -4494,7 +4438,7 @@
   EXPECT_TRUE(NavigateToURL(shell(), url_1));
 
   // Name the window.
-  EXPECT_TRUE(ExecuteScript(shell(), "window.name = 'foo';"));
+  EXPECT_TRUE(ExecJs(shell(), "window.name = 'foo';"));
 
   // Navigate it.
   GURL url_2(embedded_test_server()->GetURL(
@@ -4525,7 +4469,7 @@
   EXPECT_TRUE(NavigateToURL(shell(), url_1));
 
   // Name the window.
-  EXPECT_TRUE(ExecuteScript(shell(), "window.name = 'foo';"));
+  EXPECT_TRUE(ExecJs(shell(), "window.name = 'foo';"));
 
   // Navigate it.
   GURL url_2(embedded_test_server()->GetURL(
@@ -4533,7 +4477,7 @@
   EXPECT_TRUE(NavigateToURL(shell(), url_2));
 
   // Clear the name.
-  EXPECT_TRUE(ExecuteScript(shell(), "window.name = '';"));
+  EXPECT_TRUE(ExecJs(shell(), "window.name = '';"));
 
   // Navigate it again.
   EXPECT_TRUE(NavigateToURL(shell(), url_1));
@@ -4642,7 +4586,7 @@
                        "document.body.appendChild(iframe);";
   {
     LoadCommittedCapturer capturer(shell()->web_contents());
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
@@ -4671,7 +4615,7 @@
   // 4. Add the iframe again.
   {
     LoadCommittedCapturer capturer(shell()->web_contents());
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
@@ -4707,7 +4651,7 @@
 
   // 2. Do a same document fragment navigation.
   std::string script = "document.getElementById('fraglink').click()";
-  EXPECT_TRUE(ExecuteScript(root, script));
+  EXPECT_TRUE(ExecJs(root, script));
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
 
   frame_entry = controller.GetLastCommittedEntry()->GetFrameEntry(root);
@@ -4723,7 +4667,7 @@
     std::string add_script = "var iframe = document.createElement('iframe');"
                              "iframe.src = '" + url.spec() + "';"
                              "document.body.appendChild(iframe);";
-    EXPECT_TRUE(ExecuteScript(root, add_script));
+    EXPECT_TRUE(ExecJs(root, add_script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition_type(), ui::PAGE_TRANSITION_AUTO_SUBFRAME));
@@ -4744,7 +4688,7 @@
   EXPECT_NE(dsn_2, dsn_3);
 
   // 4. Do a same document fragment navigation in the subframe.
-  EXPECT_TRUE(ExecuteScript(subframe, script));
+  EXPECT_TRUE(ExecJs(subframe, script));
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
 
   subframe_entry = controller.GetLastCommittedEntry()->GetFrameEntry(subframe);
@@ -4982,7 +4926,7 @@
     capturer.set_wait_for_load(false);
     std::string script =
         "history.replaceState({}, '', '" + replace_state_filename + "')";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
 
     // The fact that there was a pending entry shouldn't interfere with the
@@ -5075,7 +5019,7 @@
     FrameNavigateParamsCapturer capturer(root);
     capturer.set_wait_for_load(false);
     std::string script = "history.pushState({}, '', 'pushed')";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
     EXPECT_EQ(NAVIGATION_TYPE_NEW_PAGE, capturer.navigation_type());
     EXPECT_TRUE(capturer.is_same_document());
@@ -5112,7 +5056,7 @@
 
   // Go to the second page.
   std::string script = "document.getElementById('thelink').click()";
-  EXPECT_TRUE(ExecuteScript(root, script));
+  EXPECT_TRUE(ExecJs(root, script));
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
   EXPECT_EQ(2, controller.GetEntryCount());
   EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
@@ -5181,7 +5125,7 @@
     // location.replace() to cause an inert commit.
     TestNavigationObserver replace_load_observer(shell()->web_contents());
     std::string script = "location.replace('" + url3.spec() + "')";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     replace_load_observer.Wait();
   }
 
@@ -5263,7 +5207,7 @@
     FrameNavigateParamsCapturer capturer(root);
     std::string script =
         "history.replaceState({}, 'replaced', 'replaced')";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     capturer.Wait();
   }
 
@@ -5411,7 +5355,7 @@
   {
     std::string script = "location.replace('" + redirect_url.spec() + "');";
     FrameNavigateParamsCapturer capturer(root);
-    EXPECT_TRUE(ExecuteScript(shell(), script));
+    EXPECT_TRUE(ExecJs(shell(), script));
     capturer.Wait();
     EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
         capturer.transition(),
@@ -5453,7 +5397,7 @@
   }
 
   // Make sure the renderer is still alive.
-  EXPECT_TRUE(ExecuteScript(shell(), "console.log('Success');"));
+  EXPECT_TRUE(ExecJs(shell(), "console.log('Success');"));
 }
 
 // This test shows that the initial "about:blank" URL is elided from the
@@ -5476,7 +5420,7 @@
                             ->root();
 
   EXPECT_EQ(1, controller.GetEntryCount());
-  EXPECT_EQ(1, RendererHistoryLength(shell()));
+  EXPECT_EQ(1, EvalJs(shell(), "history.length"));
 
   // Add an iframe with no 'src'.
 
@@ -5484,11 +5428,11 @@
       "var iframe = document.createElement('iframe');"
       "iframe.id = 'frame';"
       "document.body.appendChild(iframe);";
-  EXPECT_TRUE(ExecuteScript(root, script));
+  EXPECT_TRUE(ExecJs(root, script));
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
 
   EXPECT_EQ(1, controller.GetEntryCount());
-  EXPECT_EQ(1, RendererHistoryLength(shell()));
+  EXPECT_EQ(1, EvalJs(shell(), "history.length"));
   EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
 
   ASSERT_EQ(1U, root->child_count());
@@ -5502,11 +5446,11 @@
   // "about:blank" as the URL in the iframe.
 
   script = "history.pushState({}, '', 'notarealurl.html')";
-  EXPECT_TRUE(ExecuteScript(root, script));
+  EXPECT_TRUE(ExecJs(root, script));
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
 
   EXPECT_EQ(2, controller.GetEntryCount());
-  EXPECT_EQ(2, RendererHistoryLength(shell()));
+  EXPECT_EQ(2, EvalJs(shell(), "history.length"));
   EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
 
   // Load the iframe; the initial "about:blank" URL should be elided and thus we
@@ -5518,7 +5462,7 @@
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
 
   EXPECT_EQ(2, controller.GetEntryCount());
-  EXPECT_EQ(2, RendererHistoryLength(shell()));
+  EXPECT_EQ(2, EvalJs(shell(), "history.length"));
   EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
 
   EXPECT_EQ(frame_url, frame->current_url());
@@ -5532,7 +5476,7 @@
   }
 
   EXPECT_EQ(2, controller.GetEntryCount());
-  EXPECT_EQ(2, RendererHistoryLength(shell()));
+  EXPECT_EQ(2, EvalJs(shell(), "history.length"));
   EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
 
   // There is some open discussion over whether this should send the iframe
@@ -5550,11 +5494,11 @@
 
   // Do a same document navigation in the subframe.
   std::string fragment_script = "location.href = \"#foo\";";
-  EXPECT_TRUE(ExecuteScript(frame->current_frame_host(), fragment_script));
+  EXPECT_TRUE(ExecJs(frame->current_frame_host(), fragment_script));
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
 
   EXPECT_EQ(2, controller.GetEntryCount());
-  EXPECT_EQ(2, RendererHistoryLength(shell()));
+  EXPECT_EQ(2, EvalJs(shell(), "history.length"));
   EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
 
   GURL frame_url_2 = embedded_test_server()->GetURL(
@@ -5571,7 +5515,7 @@
   // Verify the process is still alive by running script.  We can't just call
   // IsRenderFrameLive after the navigation since it might not have disconnected
   // yet.
-  EXPECT_TRUE(ExecuteScript(root->current_frame_host(), "true;"));
+  EXPECT_TRUE(ExecJs(root->current_frame_host(), "true;"));
   EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
 
   // TODO(creis): We should probably go back to frame_url here instead of the
@@ -5598,7 +5542,7 @@
                             ->root();
 
   EXPECT_EQ(1, controller.GetEntryCount());
-  EXPECT_EQ(1, RendererHistoryLength(shell()));
+  EXPECT_EQ(1, EvalJs(shell(), "history.length"));
 
   // Add an iframe with a 'src'.
 
@@ -5609,11 +5553,11 @@
       "iframe.src = '" + frame_url_1.spec() + "';"
       "iframe.id = 'frame';"
       "document.body.appendChild(iframe);";
-  EXPECT_TRUE(ExecuteScript(root, script));
+  EXPECT_TRUE(ExecJs(root, script));
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
 
   EXPECT_EQ(1, controller.GetEntryCount());
-  EXPECT_EQ(1, RendererHistoryLength(shell()));
+  EXPECT_EQ(1, EvalJs(shell(), "history.length"));
   EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
 
   ASSERT_EQ(1U, root->child_count());
@@ -5626,11 +5570,11 @@
   // old navigation entry has frame_url_1 as the URL in the iframe.
 
   script = "document.getElementById('fraglink').click()";
-  EXPECT_TRUE(ExecuteScript(root, script));
+  EXPECT_TRUE(ExecJs(root, script));
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
 
   EXPECT_EQ(2, controller.GetEntryCount());
-  EXPECT_EQ(2, RendererHistoryLength(shell()));
+  EXPECT_EQ(2, EvalJs(shell(), "history.length"));
   EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
 
   EXPECT_EQ(frame_url_1, frame->current_url());
@@ -5644,7 +5588,7 @@
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
 
   EXPECT_EQ(3, controller.GetEntryCount());
-  EXPECT_EQ(3, RendererHistoryLength(shell()));
+  EXPECT_EQ(3, EvalJs(shell(), "history.length"));
   EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
 
   EXPECT_EQ(frame_url_2, frame->current_url());
@@ -5658,7 +5602,7 @@
   }
 
   EXPECT_EQ(3, controller.GetEntryCount());
-  EXPECT_EQ(3, RendererHistoryLength(shell()));
+  EXPECT_EQ(3, EvalJs(shell(), "history.length"));
   EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
 
   // There is some open discussion over whether this should send the iframe back
@@ -5676,11 +5620,11 @@
 
   // Do a same document navigation in the subframe.
   std::string fragment_script = "location.href = \"#foo\";";
-  EXPECT_TRUE(ExecuteScript(frame->current_frame_host(), fragment_script));
+  EXPECT_TRUE(ExecJs(frame->current_frame_host(), fragment_script));
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
 
   EXPECT_EQ(2, controller.GetEntryCount());
-  EXPECT_EQ(2, RendererHistoryLength(shell()));
+  EXPECT_EQ(2, EvalJs(shell(), "history.length"));
   EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
 
   // Go back.
@@ -5693,7 +5637,7 @@
   // Verify the process is still alive by running script.  We can't just call
   // IsRenderFrameLive after the navigation since it might not have disconnected
   // yet.
-  EXPECT_TRUE(ExecuteScript(root->current_frame_host(), "true;"));
+  EXPECT_TRUE(ExecJs(root->current_frame_host(), "true;"));
   EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
 
   // TODO(creis): It's a bit surprising to go to frame_url_1 here instead of
@@ -5723,7 +5667,7 @@
                             ->root();
 
   EXPECT_EQ(1, controller.GetEntryCount());
-  EXPECT_EQ(1, RendererHistoryLength(shell()));
+  EXPECT_EQ(1, EvalJs(shell(), "history.length"));
 
   // Add an iframe with a 'src'.
 
@@ -5734,11 +5678,11 @@
       "iframe.src = '" + frame_url_1.spec() + "';"
       "iframe.id = 'frame';"
       "document.body.appendChild(iframe);";
-  EXPECT_TRUE(ExecuteScript(root->current_frame_host(), script));
+  EXPECT_TRUE(ExecJs(root->current_frame_host(), script));
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
 
   EXPECT_EQ(1, controller.GetEntryCount());
-  EXPECT_EQ(1, RendererHistoryLength(shell()));
+  EXPECT_EQ(1, EvalJs(shell(), "history.length"));
   EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
 
   ASSERT_EQ(1U, root->child_count());
@@ -5751,19 +5695,19 @@
   GURL frame_url_2 = embedded_test_server()->GetURL(
       "/navigation_controller/simple_page_1.html#foo");
   std::string fragment_script = "location.href = \"#foo\";";
-  EXPECT_TRUE(ExecuteScript(frame->current_frame_host(), fragment_script));
+  EXPECT_TRUE(ExecJs(frame->current_frame_host(), fragment_script));
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
   EXPECT_EQ(2, controller.GetEntryCount());
-  EXPECT_EQ(2, RendererHistoryLength(shell()));
+  EXPECT_EQ(2, EvalJs(shell(), "history.length"));
   EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
   EXPECT_EQ(frame_url_2, frame->current_url());
 
   // Do a fragment navigation at the top level.
   std::string link_script = "document.getElementById('fraglink').click()";
-  EXPECT_TRUE(ExecuteScript(root->current_frame_host(), link_script));
+  EXPECT_TRUE(ExecJs(root->current_frame_host(), link_script));
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
   EXPECT_EQ(3, controller.GetEntryCount());
-  EXPECT_EQ(3, RendererHistoryLength(shell()));
+  EXPECT_EQ(3, EvalJs(shell(), "history.length"));
   EXPECT_EQ(2, controller.GetLastCommittedEntryIndex());
   EXPECT_EQ(frame_url_2, frame->current_url());
 
@@ -5773,7 +5717,7 @@
   NavigateFrameToURL(frame, frame_url_3);
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
   EXPECT_EQ(4, controller.GetEntryCount());
-  EXPECT_EQ(4, RendererHistoryLength(shell()));
+  EXPECT_EQ(4, EvalJs(shell(), "history.length"));
   EXPECT_EQ(3, controller.GetLastCommittedEntryIndex());
   EXPECT_EQ(frame_url_3, frame->current_url());
 
@@ -5785,7 +5729,7 @@
     observer.Wait();
   }
   EXPECT_EQ(4, controller.GetEntryCount());
-  EXPECT_EQ(4, RendererHistoryLength(shell()));
+  EXPECT_EQ(4, EvalJs(shell(), "history.length"));
   EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
   EXPECT_EQ(links_url, root->current_url());
 
@@ -5812,7 +5756,7 @@
   // Verify the process is still alive by running script.  We can't just call
   // IsRenderFrameLive after the navigation since it might not have disconnected
   // yet.
-  EXPECT_TRUE(ExecuteScript(root->current_frame_host(), "true;"));
+  EXPECT_TRUE(ExecJs(root->current_frame_host(), "true;"));
   EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
 
   // TODO(creis): It's a bit surprising to go to frame_url_1 here instead of
@@ -5836,7 +5780,7 @@
                             ->root();
 
   EXPECT_EQ(1, controller.GetEntryCount());
-  EXPECT_EQ(1, RendererHistoryLength(shell()));
+  EXPECT_EQ(1, EvalJs(shell(), "history.length"));
 
   // Add an iframe with no 'src'.
   GURL blank_url(url::kAboutBlankURL);
@@ -5844,10 +5788,10 @@
       "var iframe = document.createElement('iframe');"
       "iframe.id = 'frame';"
       "document.body.appendChild(iframe);";
-  EXPECT_TRUE(ExecuteScript(root->current_frame_host(), script));
+  EXPECT_TRUE(ExecJs(root->current_frame_host(), script));
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
   EXPECT_EQ(1, controller.GetEntryCount());
-  EXPECT_EQ(1, RendererHistoryLength(shell()));
+  EXPECT_EQ(1, EvalJs(shell(), "history.length"));
   EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
   ASSERT_EQ(1U, root->child_count());
   FrameTreeNode* frame = root->child_at(0);
@@ -5860,17 +5804,17 @@
       "iframe.contentWindow.document.write("
       "    \"<a id='fraglink' href='#frag'>fragment link</a>\");"
       "iframe.contentWindow.document.close();";
-  EXPECT_TRUE(ExecuteScript(root->current_frame_host(), document_write_script));
+  EXPECT_TRUE(ExecJs(root->current_frame_host(), document_write_script));
 
   // Click the link to do a same document navigation.  Due to the
   // document.write, the new URL matches the parent frame's URL.
   GURL frame_url_2(embedded_test_server()->GetURL(
       "/navigation_controller/page_with_links.html#frag"));
   std::string link_script = "document.getElementById('fraglink').click()";
-  EXPECT_TRUE(ExecuteScript(frame->current_frame_host(), link_script));
+  EXPECT_TRUE(ExecJs(frame->current_frame_host(), link_script));
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
   EXPECT_EQ(2, controller.GetEntryCount());
-  EXPECT_EQ(2, RendererHistoryLength(shell()));
+  EXPECT_EQ(2, EvalJs(shell(), "history.length"));
   EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
   EXPECT_EQ(frame_url_2, frame->current_url());
 
@@ -5884,7 +5828,7 @@
   // Verify the process is still alive by running script.  We can't just call
   // IsRenderFrameLive after the navigation since it might not have disconnected
   // yet.
-  EXPECT_TRUE(ExecuteScript(root->current_frame_host(), "true;"));
+  EXPECT_TRUE(ExecJs(root->current_frame_host(), "true;"));
   EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
 
   EXPECT_EQ(blank_url, frame->current_url());
@@ -5906,7 +5850,7 @@
                             ->root();
 
   EXPECT_EQ(1, controller.GetEntryCount());
-  EXPECT_EQ(1, RendererHistoryLength(shell()));
+  EXPECT_EQ(1, EvalJs(shell(), "history.length"));
 
   // Add an iframe with no 'src'.
   GURL blank_url(url::kAboutBlankURL);
@@ -5914,10 +5858,10 @@
       "var iframe = document.createElement('iframe');"
       "iframe.id = 'frame';"
       "document.body.appendChild(iframe);";
-  EXPECT_TRUE(ExecuteScript(root->current_frame_host(), script));
+  EXPECT_TRUE(ExecJs(root->current_frame_host(), script));
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
   EXPECT_EQ(1, controller.GetEntryCount());
-  EXPECT_EQ(1, RendererHistoryLength(shell()));
+  EXPECT_EQ(1, EvalJs(shell(), "history.length"));
   EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
   ASSERT_EQ(1U, root->child_count());
   FrameTreeNode* frame = root->child_at(0);
@@ -5931,16 +5875,16 @@
       "iframe.contentWindow.document.write($1);"
       "iframe.contentWindow.document.close();",
       html);
-  EXPECT_TRUE(ExecuteScript(root->current_frame_host(), document_write_script));
+  EXPECT_TRUE(ExecJs(root->current_frame_host(), document_write_script));
 
   // Click the link to do a same document navigation.  Due to the
   // document.write, the new URL matches the parent frame's URL.
   GURL frame_url_2("data:text/html,Top level page#frag");
   std::string link_script = "document.getElementById('fraglink').click()";
-  EXPECT_TRUE(ExecuteScript(frame->current_frame_host(), link_script));
+  EXPECT_TRUE(ExecJs(frame->current_frame_host(), link_script));
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
   EXPECT_EQ(2, controller.GetEntryCount());
-  EXPECT_EQ(2, RendererHistoryLength(shell()));
+  EXPECT_EQ(2, EvalJs(shell(), "history.length"));
   EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
   EXPECT_EQ(frame_url_2, frame->current_url());
 
@@ -5954,7 +5898,7 @@
   // Verify the process is still alive by running script.  We can't just call
   // IsRenderFrameLive after the navigation since it might not have disconnected
   // yet.
-  EXPECT_TRUE(ExecuteScript(root->current_frame_host(), "true;"));
+  EXPECT_TRUE(ExecJs(root->current_frame_host(), "true;"));
   EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
 
   EXPECT_EQ(blank_url, frame->current_url());
@@ -6119,7 +6063,7 @@
       "b.com", "/navigation_controller/simple_page_2.html"));
   std::string replace_script = "location.replace('" + url_b.spec() + "')";
   TestNavigationObserver replace_observer(shell()->web_contents());
-  EXPECT_TRUE(ExecuteScript(shell()->web_contents(), replace_script));
+  EXPECT_TRUE(ExecJs(shell()->web_contents(), replace_script));
   replace_observer.Wait();
   EXPECT_EQ(3, controller.GetEntryCount());
   EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
@@ -6131,8 +6075,8 @@
 
   // Navigate forward twice using script.  In https://crbug.com/623319, this
   // caused a mismatch between the NavigationEntry's URL and PageState.
-  EXPECT_TRUE(ExecuteScript(shell()->web_contents(),
-                            "history.forward(); history.forward();"));
+  EXPECT_TRUE(
+      ExecJs(shell()->web_contents(), "history.forward(); history.forward();"));
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
   EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
   EXPECT_EQ(url_b, root->current_url());
@@ -6204,7 +6148,7 @@
       "b.com", "/navigation_controller/page_with_data_iframe.html"));
   std::string replace_script = "location.replace('" + url_b.spec() + "')";
   TestNavigationObserver replace_observer(shell()->web_contents());
-  EXPECT_TRUE(ExecuteScript(shell()->web_contents(), replace_script));
+  EXPECT_TRUE(ExecJs(shell()->web_contents(), replace_script));
   replace_observer.Wait();
   EXPECT_EQ(3, controller.GetEntryCount());
   EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
@@ -6219,8 +6163,8 @@
   // we want to ensure that the subframes target entry index 1 and not 2.  In
   // https://crbug.com/623319, the subframes targeted the wrong entry, leading
   // to a URL spoof and renderer kill.
-  EXPECT_TRUE(ExecuteScript(shell()->web_contents(),
-                            "history.forward(); history.forward();"));
+  EXPECT_TRUE(
+      ExecJs(shell()->web_contents(), "history.forward(); history.forward();"));
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
   EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
   EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
@@ -6280,7 +6224,7 @@
     TestNavigationObserver observer(shell()->web_contents());
     std::string script =
         "history.replaceState({}, '', '/server-redirect?" + url_3.spec() + "')";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     observer.Wait();
   }
 
@@ -6338,7 +6282,7 @@
     TestNavigationObserver observer(shell()->web_contents());
     std::string script = "history.replaceState({}, '', '/server-redirect?" +
                          frame_url2.spec() + "')";
-    EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
+    EXPECT_TRUE(ExecJs(root->child_at(0), script));
     observer.Wait();
   }
 
@@ -6438,8 +6382,8 @@
 
   // Submit the form.
   TestNavigationObserver form_post_observer(shell()->web_contents(), 1);
-  EXPECT_TRUE(ExecuteScript(shell()->web_contents(),
-                            "document.getElementById('form').submit();"));
+  EXPECT_TRUE(ExecJs(shell()->web_contents(),
+                     "document.getElementById('form').submit();"));
   form_post_observer.Wait();
 
   // Verify that we arrived at the expected location.
@@ -6448,13 +6392,9 @@
 
   // Verify that POST body was correctly passed to the server and ended up in
   // the body of the page.
-  std::string body;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      shell()->web_contents(),
-      "window.domAutomationController.send("
-      "document.getElementsByTagName('pre')[0].innerText);",
-      &body));
-  EXPECT_EQ("text=value\n", body);
+  EXPECT_EQ(
+      "text=value\n",
+      EvalJs(shell(), "document.getElementsByTagName('pre')[0].innerText"));
 }
 
 // Tests that inserting a named subframe into the FrameTree clears any
@@ -6505,7 +6445,7 @@
 
   // Removing the first child of the main frame should remove the corresponding
   // FrameTreeNode.
-  EXPECT_TRUE(ExecuteScript(root, kRemoveFrameScript));
+  EXPECT_TRUE(ExecJs(root, kRemoveFrameScript));
   EXPECT_EQ(2U, root->child_count());
 
   // However, the FrameNavigationEntry objects for the frame that was removed
@@ -6546,7 +6486,7 @@
       "f.src = '1-1.html';"
       "document.body.appendChild(f);";
   TestNavigationObserver observer(web_contents, 1);
-  EXPECT_TRUE(ExecuteScript(subframe, add_matching_name_frame_script));
+  EXPECT_TRUE(ExecJs(subframe, add_matching_name_frame_script));
   EXPECT_EQ(1U, subframe->child_count());
   observer.Wait();
 
@@ -6586,7 +6526,7 @@
   // |tree_node| will becoma a dangling pointer when the frame is removed below.
   auto* tree_node = nav_entry->root_node()->children[0].get();
 
-  EXPECT_TRUE(ExecuteScript(root, kRemoveFrameScript));
+  EXPECT_TRUE(ExecJs(root, kRemoveFrameScript));
   EXPECT_EQ(0U, root->child_count());
   EXPECT_EQ(0U, nav_entry->root_node()->children.size());
 
@@ -6599,7 +6539,7 @@
   EXPECT_EQ(1U, nav_entry->root_node()->children.size());
   EXPECT_NE(tree_node, nav_entry->root_node()->children[0].get());
 
-  EXPECT_TRUE(ExecuteScript(root, kRemoveFrameScript));
+  EXPECT_TRUE(ExecJs(root, kRemoveFrameScript));
   EXPECT_EQ(0U, root->child_count());
 }
 
@@ -6630,7 +6570,7 @@
     TestNavigationObserver observer(web_contents);
     std::string script = "history.replaceState({}, '', '/server-redirect?" +
                          redirect_dest_url.spec() + "')";
-    EXPECT_TRUE(ExecuteScript(root, script));
+    EXPECT_TRUE(ExecJs(root, script));
     observer.Wait();
   }
 
@@ -6657,10 +6597,8 @@
 
   // Verify the expected origin through JavaScript. It also has the additional
   // verification of the process also being still alive.
-  std::string origin;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      web_contents, "domAutomationController.send(document.origin)", &origin));
-  EXPECT_EQ(start_url.GetOrigin().spec(), origin + "/");
+  EXPECT_EQ(url::Origin::Create(start_url).Serialize(),
+            EvalJs(web_contents, "document.origin"));
 }
 
 // Helper to trigger a history-back navigation in the WebContents after the
@@ -6748,10 +6686,8 @@
 
   // Verify the expected origin through JavaScript. It also has the additional
   // verification of the process also being still alive.
-  std::string origin;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      web_contents, "domAutomationController.send(document.origin)", &origin));
-  EXPECT_EQ(start_url.GetOrigin().spec(), origin + "/");
+  EXPECT_EQ(url::Origin::Create(start_url).Serialize(),
+            EvalJs(web_contents, "document.origin"));
 }
 
 // Test that verifies that Referer and Origin http headers are correctly sent
@@ -6775,7 +6711,7 @@
   // 3. http://x.com:.../echoall/
   TestNavigationObserver form_post_observer(shell()->web_contents(), 1);
   EXPECT_TRUE(
-      ExecuteScript(shell(), "document.getElementById('text-form').submit();"));
+      ExecJs(shell(), "document.getElementById('text-form').submit();"));
   form_post_observer.Wait();
 
   // Verify that we arrived at the expected, redirected location.
@@ -6783,12 +6719,9 @@
             shell()->web_contents()->GetLastCommittedURL());
 
   // Get the http request headers.
-  std::string headers;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      shell(),
-      "window.domAutomationController.send("
-      "document.getElementsByTagName('pre')[1].innerText);",
-      &headers));
+  std::string headers =
+      EvalJs(shell(), "document.getElementsByTagName('pre')[1].innerText")
+          .ExtractString();
 
   // Verify the Origin and Referer headers.
   EXPECT_THAT(headers, ::testing::HasSubstr("Origin: null"));
@@ -6930,7 +6863,7 @@
   // JavaScript asynchronously.
   TestNavigationObserver observer(web_contents);
 
-  // ExecuteScript() sets a user gesture flag internally for testing, but we
+  // ExecJs() sets a user gesture flag internally for testing, but we
   // want to run JavaScript without the flag.  Call ExecuteJavaScriptForTests
   // directory.
   static_cast<WebContentsImpl*>(web_contents)
@@ -7217,8 +7150,8 @@
   NavigationHandleCommitObserver push_state_observer(shell()->web_contents(),
                                                      kPushStateURL);
   std::string push_state =
-      "history.pushState({}, \"title 1\", \"" + kPushStateURL.spec() + "\");";
-  EXPECT_TRUE(ExecuteScript(shell()->web_contents(), push_state));
+      JsReplace("history.pushState({}, 'title 1', $1);", kPushStateURL);
+  EXPECT_TRUE(ExecJs(shell()->web_contents(), push_state));
   NavigationEntry* last_committed =
       shell()->web_contents()->GetController().GetLastCommittedEntry();
   ASSERT_TRUE(last_committed);
@@ -7482,17 +7415,13 @@
 
   // The opener should not be able to script the page, which should be another
   // error message and not a blank page.
-  std::string result;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      shell(),
-      "domAutomationController.send((function() {\n"
-      "  try {\n"
-      "    return w.document.body.innerHTML;\n"
-      "  } catch (e) {\n"
-      "    return e.toString();\n"
-      "  }\n"
-      "})())",
-      &result));
+  std::string result = EvalJs(shell(),
+                              "try {\n"
+                              "  w.document.body.innerHTML;\n"
+                              "} catch (e) {\n"
+                              "  e.toString();\n"
+                              "}")
+                           .ExtractString();
   DLOG(INFO) << "Result: " << result;
   EXPECT_THAT(result,
               ::testing::MatchesRegex("SecurityError: Blocked a frame with "
@@ -7742,25 +7671,19 @@
   EXPECT_TRUE(NavigateToURL(shell(), main_url));
 
   // Repeatedly create and remove a frame from a script.
-  std::string result;
   std::string script = R"(
-        var iterations_left = 5;
-        function runOneIteration() {
-          if (iterations_left == 0) {
-            domAutomationController.send("done-with-test");
-            return;
-          }
-
-          var iframe = document.createElement("iframe");
-          document.body.appendChild(iframe);
-          document.body.removeChild(iframe);
-
-          iterations_left = iterations_left - 1;
-          setTimeout(runOneIteration, 0);
-        }
-        runOneIteration(); )";
-  EXPECT_TRUE(ExecuteScriptAndExtractString(shell(), script, &result));
-  EXPECT_EQ("done-with-test", result);
+        (async () => {
+           for (let i = 0; i < 5; i++) {
+             // Create and remove an iframe.
+             let iframe = document.createElement('iframe');
+             document.body.appendChild(iframe);
+             document.body.removeChild(iframe);
+             // Let the message loop run (this works in an async function).
+             await new Promise(resolve => setTimeout(resolve, 0));
+           }
+           return 'done-with-test';
+        })(); )";
+  EXPECT_EQ("done-with-test", EvalJs(shell(), script));
 
   // Grab the last committed entry.
   const NavigationControllerImpl& controller =
@@ -8018,13 +7941,8 @@
   EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
 
   // Test what is in the loaded document.
-  std::string html_content;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      shell(), "domAutomationController.send(document.body.textContent)",
-      &html_content));
-
   EXPECT_EQ("First part of the response... ...and the second part!",
-            html_content);
+            EvalJs(shell(), "document.body.textContent"));
 }
 
 // Data URLs can have a reference fragment like any other URLs. In this test,
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 698e9bc..b9915e7 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -5273,25 +5273,48 @@
   // Error pages may sometimes commit a URL in the wrong process, which requires
   // an exception for the CanCommitURL checks.  This is ok as long as the origin
   // is unique.
-  // TODO(creis): Kill the renderer if it claims an error page has a non-unique
-  // origin.
   bool is_permitted_error_page = false;
-  if (validated_params->origin.unique()) {
-    if (SiteIsolationPolicy::IsErrorPageIsolationEnabled(
-            frame_tree_node_->IsMainFrame())) {
+  if (SiteIsolationPolicy::IsErrorPageIsolationEnabled(
+          frame_tree_node_->IsMainFrame())) {
+    if (site_instance_->GetSiteURL() == GURL(content::kUnreachableWebDataURL)) {
+      // Commits in the error page process must only be failures, otherwise
+      // successful navigations could commit documents from origins different
+      // than the chrome-error://chromewebdata/ one and violate expectations.
+      if (!validated_params->url_is_unreachable) {
+        DEBUG_ALIAS_FOR_ORIGIN(origin_debug_alias, validated_params->origin);
+        bad_message::ReceivedBadMessage(
+            process, bad_message::RFH_ERROR_PROCESS_NON_ERROR_COMMIT);
+        return false;
+      }
+
+      // Error pages must commit in a unique origin. Terminate the renderer
+      // process if this is violated.
+      if (!validated_params->origin.unique()) {
+        DEBUG_ALIAS_FOR_ORIGIN(origin_debug_alias, validated_params->origin);
+        bad_message::ReceivedBadMessage(
+            process, bad_message::RFH_ERROR_PROCESS_NON_UNIQUE_ORIGIN_COMMIT);
+        return false;
+      }
+
       // With error page isolation, any URL can commit in an error page process.
-      if (GetSiteInstance()->GetSiteURL() ==
-          GURL(content::kUnreachableWebDataURL)) {
-        is_permitted_error_page = true;
+      is_permitted_error_page = true;
+    }
+  } else {
+    // Without error page isolation, a blocked navigation is expected to
+    // commit in the old renderer process.  This may be true for subframe
+    // navigations even when error page isolation is enabled for main frames.
+    if (GetNavigationHandle() && GetNavigationHandle()->GetNetErrorCode() ==
+                                     net::ERR_BLOCKED_BY_CLIENT) {
+      // Since this is known to be an error page commit, verify it happened in
+      // a unique origin, terminating the renderer process otherwise.
+      if (!validated_params->origin.unique()) {
+        DEBUG_ALIAS_FOR_ORIGIN(origin_debug_alias, validated_params->origin);
+        bad_message::ReceivedBadMessage(
+            process, bad_message::RFH_ERROR_PROCESS_NON_UNIQUE_ORIGIN_COMMIT);
+        return false;
       }
-    } else {
-      // Without error page isolation, a blocked navigation is expected to
-      // commit in the old renderer process.  This may be true for subframe
-      // navigations even when error page isolation is enabled for main frames.
-      if (GetNavigationHandle() && GetNavigationHandle()->GetNetErrorCode() ==
-                                       net::ERR_BLOCKED_BY_CLIENT) {
-        is_permitted_error_page = true;
-      }
+
+      is_permitted_error_page = true;
     }
   }
 
diff --git a/content/browser/media/capture/aura_window_video_capture_device.cc b/content/browser/media/capture/aura_window_video_capture_device.cc
index 93160c0..72b973e 100644
--- a/content/browser/media/capture/aura_window_video_capture_device.cc
+++ b/content/browser/media/capture/aura_window_video_capture_device.cc
@@ -4,12 +4,15 @@
 
 #include "content/browser/media/capture/aura_window_video_capture_device.h"
 
+#include <utility>
+
 #include "base/bind.h"
 #include "base/location.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "content/browser/media/capture/mouse_cursor_overlay_controller.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/desktop_media_id.h"
 #include "media/base/bind_to_current_loop.h"
@@ -31,14 +34,14 @@
           AuraWindowVideoCaptureDevice::WindowTracker> {
  public:
   WindowTracker(base::WeakPtr<AuraWindowVideoCaptureDevice> device,
-                CursorRenderer* cursor_renderer,
+                MouseCursorOverlayController* cursor_controller,
                 const DesktopMediaID& source_id)
       : device_(std::move(device)),
         device_task_runner_(base::ThreadTaskRunnerHandle::Get()),
-        cursor_renderer_(cursor_renderer),
+        cursor_controller_(cursor_controller),
         target_type_(source_id.type) {
     DCHECK(device_task_runner_);
-    DCHECK(cursor_renderer_);
+    DCHECK(cursor_controller_);
 
     BrowserThread::PostTask(
         BrowserThread::UI, FROM_HERE,
@@ -86,10 +89,11 @@
           FROM_HERE,
           base::BindOnce(&FrameSinkVideoCaptureDevice::OnTargetChanged, device_,
                          target_window_->GetFrameSinkId()));
-      // Note: CursorRenderer runs on the UI thread. It's also important that
-      // SetTargetView() be called in the current stack while |target_window_|
-      // is known to be a valid pointer. http://crbug.com/818679
-      cursor_renderer_->SetTargetView(target_window_);
+      // Note: The MouseCursorOverlayController runs on the UI thread. It's also
+      // important that SetTargetView() be called in the current stack while
+      // |target_window_| is known to be a valid pointer.
+      // http://crbug.com/818679
+      cursor_controller_->SetTargetView(target_window_);
     } else {
       device_task_runner_->PostTask(
           FROM_HERE,
@@ -110,7 +114,7 @@
         FROM_HERE,
         base::BindOnce(&FrameSinkVideoCaptureDevice::OnTargetPermanentlyLost,
                        device_));
-    cursor_renderer_->SetTargetView(nullptr);
+    cursor_controller_->SetTargetView(gfx::NativeView());
   }
 
  private:
@@ -120,8 +124,8 @@
 
   // Owned by FrameSinkVideoCaptureDevice. This will be valid for the life of
   // WindowTracker because the WindowTracker deleter task will be posted to the
-  // UI thread before the CursorRenderer deleter task.
-  CursorRenderer* const cursor_renderer_;
+  // UI thread before the MouseCursorOverlayController deleter task.
+  MouseCursorOverlayController* const cursor_controller_;
 
   const DesktopMediaID::Type target_type_;
 
@@ -132,7 +136,8 @@
 
 AuraWindowVideoCaptureDevice::AuraWindowVideoCaptureDevice(
     const DesktopMediaID& source_id)
-    : tracker_(new WindowTracker(AsWeakPtr(), cursor_renderer(), source_id)) {}
+    : tracker_(new WindowTracker(AsWeakPtr(), cursor_controller(), source_id)) {
+}
 
 AuraWindowVideoCaptureDevice::~AuraWindowVideoCaptureDevice() = default;
 
diff --git a/content/browser/media/capture/frame_sink_video_capture_device.cc b/content/browser/media/capture/frame_sink_video_capture_device.cc
index e096bf8..08e7063 100644
--- a/content/browser/media/capture/frame_sink_video_capture_device.cc
+++ b/content/browser/media/capture/frame_sink_video_capture_device.cc
@@ -14,9 +14,11 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "base/numerics/safe_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "components/viz/host/host_frame_sink_manager.h"
 #include "content/browser/compositor/surface_utils.h"
+#include "content/browser/media/capture/mouse_cursor_overlay_controller.h"
 #include "media/base/bind_to_current_loop.h"
 #include "media/capture/mojom/video_capture_types.mojom.h"
 #include "mojo/public/cpp/system/buffer.h"
@@ -25,6 +27,8 @@
 
 namespace {
 
+constexpr int32_t kMouseCursorStackingIndex = 1;
+
 // Transfers ownership of an object to a std::unique_ptr with a custom deleter
 // that ensures the object is destroyed on the UI BrowserThread.
 template <typename T>
@@ -49,10 +53,10 @@
 }  // namespace
 
 FrameSinkVideoCaptureDevice::FrameSinkVideoCaptureDevice()
-    : cursor_renderer_(RescopeToUIThread(CursorRenderer::Create(
-          CursorRenderer::CURSOR_DISPLAYED_ON_MOUSE_MOVEMENT))),
+    : cursor_controller_(
+          RescopeToUIThread(std::make_unique<MouseCursorOverlayController>())),
       weak_factory_(this) {
-  DCHECK(cursor_renderer_);
+  DCHECK(cursor_controller_);
 }
 
 FrameSinkVideoCaptureDevice::~FrameSinkVideoCaptureDevice() {
@@ -79,17 +83,6 @@
   DCHECK(!receiver_);
   receiver_ = std::move(receiver);
 
-  // Set a callback that will be run whenever the mouse moves, to trampoline
-  // back to the device thread and request a refresh frame so that the new mouse
-  // cursor location can be drawn in a new video frame.
-  BrowserThread::PostTask(
-      BrowserThread::UI, FROM_HERE,
-      base::BindOnce(&CursorRenderer::SetNeedsRedrawCallback,
-                     cursor_renderer_->GetWeakPtr(),
-                     media::BindToCurrentLoop(base::BindRepeating(
-                         &FrameSinkVideoCaptureDevice::RequestRefreshFrame,
-                         weak_factory_.GetWeakPtr()))));
-
   // Shutdown the prior capturer, if any.
   MaybeStopConsuming();
 
@@ -112,6 +105,13 @@
     capturer_->ChangeTarget(target_);
   }
 
+  BrowserThread::PostTask(
+      BrowserThread::UI, FROM_HERE,
+      base::BindOnce(&MouseCursorOverlayController::Start,
+                     cursor_controller_->GetWeakPtr(),
+                     capturer_->CreateOverlay(kMouseCursorStackingIndex),
+                     base::ThreadTaskRunnerHandle::Get()));
+
   receiver_->OnStarted();
 
   if (!suspend_requested_) {
@@ -153,11 +153,9 @@
 void FrameSinkVideoCaptureDevice::StopAndDeAllocate() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  // Discontinue requesting extra video frames to render mouse cursor changes.
-  BrowserThread::PostTask(
-      BrowserThread::UI, FROM_HERE,
-      base::BindOnce(&CursorRenderer::SetNeedsRedrawCallback,
-                     cursor_renderer_->GetWeakPtr(), base::RepeatingClosure()));
+  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+                          base::BindOnce(&MouseCursorOverlayController::Stop,
+                                         cursor_controller_->GetWeakPtr()));
 
   MaybeStopConsuming();
   capturer_.reset();
@@ -171,12 +169,12 @@
                                                       double utilization) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  // Assumption: The "slot" should be valid at this point because this method
-  // will always be called before the VideoFrameReceiver signals it is done
-  // consuming the frame.
-  const auto slot_index = static_cast<size_t>(frame_feedback_id);
-  DCHECK_LT(slot_index, slots_.size());
-  slots_[slot_index].callbacks->ProvideFeedback(utilization);
+  // Assumption: The mojo InterfacePtr in |frame_callbacks_| should be valid at
+  // this point because this method will always be called before the
+  // VideoFrameReceiver signals it is done consuming the frame.
+  const auto index = static_cast<size_t>(frame_feedback_id);
+  DCHECK_LT(index, frame_callbacks_.size());
+  frame_callbacks_[index]->ProvideFeedback(utilization);
 }
 
 void FrameSinkVideoCaptureDevice::OnFrameCaptured(
@@ -194,51 +192,31 @@
     return;
   }
 
-  // Search for the next available ConsumptionState slot and bind |callbacks|
-  // there.
-  size_t slot_index = 0;
-  for (;; ++slot_index) {
-    if (slot_index == slots_.size()) {
-      // The growth of |slots_| should be bounded because the
+  // Search for the next available element in |frame_callbacks_| and bind
+  // |callbacks| there.
+  size_t index = 0;
+  for (;; ++index) {
+    if (index == frame_callbacks_.size()) {
+      // The growth of |frame_callbacks_| should be bounded because the
       // viz::mojom::FrameSinkVideoCapturer should enforce an upper-bound on the
       // number of frames in-flight.
       constexpr size_t kMaxInFlightFrames = 32;  // Arbitrarily-chosen limit.
-      DCHECK_LT(slots_.size(), kMaxInFlightFrames);
-      slots_.emplace_back();
+      DCHECK_LT(frame_callbacks_.size(), kMaxInFlightFrames);
+      frame_callbacks_.emplace_back(std::move(callbacks));
       break;
     }
-    if (!slots_[slot_index].callbacks.is_bound()) {
+    if (!frame_callbacks_[index].is_bound()) {
+      frame_callbacks_[index] = std::move(callbacks);
       break;
     }
   }
-  ConsumptionState& slot = slots_[slot_index];
-  slot.callbacks = std::move(callbacks);
-
-  // Render the mouse cursor on the video frame, but first map the shared memory
-  // into the current process in order to render the cursor.
-  mojo::ScopedSharedBufferMapping mapping = buffer->Map(buffer_size);
-  scoped_refptr<media::VideoFrame> frame;
-  if (mapping) {
-    frame = media::VideoFrame::WrapExternalData(
-        info->pixel_format, info->coded_size, info->visible_rect,
-        info->visible_rect.size(), static_cast<uint8_t*>(mapping.get()),
-        buffer_size, info->timestamp);
-    if (frame) {
-      frame->AddDestructionObserver(base::BindOnce(
-          [](mojo::ScopedSharedBufferMapping mapping) {}, std::move(mapping)));
-      if (!cursor_renderer_->RenderOnVideoFrame(frame.get(), content_rect,
-                                                &slot.undoer)) {
-        // Release |frame| now, since no "undo cursor rendering" will be needed.
-        frame = nullptr;
-      }
-    }
-  }
+  const BufferId buffer_id = static_cast<BufferId>(index);
 
   // Set the INTERACTIVE_CONTENT frame metadata.
   media::VideoFrameMetadata modified_metadata;
   modified_metadata.MergeInternalValuesFrom(info->metadata);
   modified_metadata.SetBoolean(media::VideoFrameMetadata::INTERACTIVE_CONTENT,
-                               cursor_renderer_->IsUserInteractingWithView());
+                               cursor_controller_->IsUserInteractingWithView());
   info->metadata = modified_metadata.GetInternalValues().Clone();
 
   // Pass the video frame to the VideoFrameReceiver. This is done by first
@@ -247,14 +225,13 @@
   media::mojom::VideoBufferHandlePtr buffer_handle =
       media::mojom::VideoBufferHandle::New();
   buffer_handle->set_shared_buffer_handle(std::move(buffer));
-  receiver_->OnNewBuffer(static_cast<BufferId>(slot_index),
-                         std::move(buffer_handle));
+  receiver_->OnNewBuffer(buffer_id, std::move(buffer_handle));
   receiver_->OnFrameReadyInBuffer(
-      static_cast<BufferId>(slot_index), slot_index,
+      buffer_id, buffer_id,
       std::make_unique<ScopedFrameDoneHelper>(
           media::BindToCurrentLoop(base::BindOnce(
               &FrameSinkVideoCaptureDevice::OnFramePropagationComplete,
-              weak_factory_.GetWeakPtr(), slot_index, std::move(frame)))),
+              weak_factory_.GetWeakPtr(), buffer_id))),
       std::move(info));
 }
 
@@ -333,26 +310,20 @@
 }
 
 void FrameSinkVideoCaptureDevice::OnFramePropagationComplete(
-    size_t slot_index,
-    scoped_refptr<media::VideoFrame> frame) {
+    BufferId buffer_id) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK_LT(slot_index, slots_.size());
 
   // Notify the VideoFrameReceiver that the buffer is no longer valid.
   if (receiver_) {
-    receiver_->OnBufferRetired(static_cast<BufferId>(slot_index));
-  }
-
-  // Undo the mouse cursor rendering, if any.
-  ConsumptionState& slot = slots_[slot_index];
-  if (frame) {
-    slot.undoer.Undo(frame.get());
-    frame = nullptr;
+    receiver_->OnBufferRetired(buffer_id);
   }
 
   // Notify the capturer that consumption of the frame is complete.
-  slot.callbacks->Done();
-  slot.callbacks.reset();
+  const size_t index = static_cast<size_t>(buffer_id);
+  DCHECK_LT(index, frame_callbacks_.size());
+  auto& callbacks_ptr = frame_callbacks_[index];
+  callbacks_ptr->Done();
+  callbacks_ptr.reset();
 }
 
 void FrameSinkVideoCaptureDevice::OnFatalError(std::string message) {
@@ -367,12 +338,4 @@
   StopAndDeAllocate();
 }
 
-FrameSinkVideoCaptureDevice::ConsumptionState::ConsumptionState() = default;
-FrameSinkVideoCaptureDevice::ConsumptionState::~ConsumptionState() = default;
-FrameSinkVideoCaptureDevice::ConsumptionState::ConsumptionState(
-    FrameSinkVideoCaptureDevice::ConsumptionState&& other) noexcept = default;
-FrameSinkVideoCaptureDevice::ConsumptionState&
-FrameSinkVideoCaptureDevice::ConsumptionState::operator=(
-    FrameSinkVideoCaptureDevice::ConsumptionState&& other) noexcept = default;
-
 }  // namespace content
diff --git a/content/browser/media/capture/frame_sink_video_capture_device.h b/content/browser/media/capture/frame_sink_video_capture_device.h
index 34124843..5e7f494a 100644
--- a/content/browser/media/capture/frame_sink_video_capture_device.h
+++ b/content/browser/media/capture/frame_sink_video_capture_device.h
@@ -16,7 +16,6 @@
 #include "base/sequence_checker.h"
 #include "components/viz/common/surfaces/frame_sink_id.h"
 #include "components/viz/host/client_frame_sink_video_capturer.h"
-#include "content/browser/media/capture/cursor_renderer.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/browser_thread.h"
 #include "media/base/video_frame.h"
@@ -27,6 +26,8 @@
 
 namespace content {
 
+class MouseCursorOverlayController;
+
 // A virtualized VideoCaptureDevice that captures the displayed contents of a
 // frame sink (see viz::CompositorFrameSink), such as the composited main view
 // of a WebContents instance, producing a stream of video frames.
@@ -86,7 +87,9 @@
   void OnTargetPermanentlyLost();
 
  protected:
-  CursorRenderer* cursor_renderer() const { return cursor_renderer_.get(); }
+  MouseCursorOverlayController* cursor_controller() const {
+    return cursor_controller_.get();
+  }
 
   // Subclasses override these to perform additional start/stop tasks.
   virtual void WillStart();
@@ -112,10 +115,8 @@
   // If consuming, shut it down.
   void MaybeStopConsuming();
 
-  // Undoes mouse cursor rendering and notifies the capturer that consumption of
-  // the frame is complete.
-  void OnFramePropagationComplete(size_t slot_index,
-                                  scoped_refptr<media::VideoFrame> frame);
+  // Notifies the capturer that consumption of the frame is complete.
+  void OnFramePropagationComplete(BufferId buffer_id);
 
   // Helper that logs the given error |message| to the |receiver_| and then
   // stops capture and this VideoCaptureDevice.
@@ -140,18 +141,13 @@
 
   std::unique_ptr<viz::ClientFrameSinkVideoCapturer> capturer_;
 
-  // A pool of structs that hold state relevant to frames currently being
-  // processed by VideoFrameReceiver. Each "slot" is re-used by later frames.
-  struct ConsumptionState {
-    viz::mojom::FrameSinkVideoConsumerFrameCallbacksPtr callbacks;
-    CursorRendererUndoer undoer;
-
-    ConsumptionState();
-    ~ConsumptionState();
-    ConsumptionState(ConsumptionState&& other) noexcept;
-    ConsumptionState& operator=(ConsumptionState&& other) noexcept;
-  };
-  std::vector<ConsumptionState> slots_;
+  // A vector that holds the "callbacks" mojo InterfacePtr for each frame while
+  // the frame is being processed by VideoFrameReceiver. The index corresponding
+  // to a particular frame is used as the BufferId passed to VideoFrameReceiver.
+  // Therefore, non-null pointers in this vector must never move to a different
+  // position.
+  std::vector<viz::mojom::FrameSinkVideoConsumerFrameCallbacksPtr>
+      frame_callbacks_;
 
   // Set when OnFatalError() is called. This prevents any future
   // AllocateAndStartWithReceiver() calls from succeeding.
@@ -159,9 +155,10 @@
 
   SEQUENCE_CHECKER(sequence_checker_);
 
-  // Renders the mouse cursor on each video frame.
-  const std::unique_ptr<CursorRenderer, BrowserThread::DeleteOnUIThread>
-      cursor_renderer_;
+  // Controls the overlay that renders the mouse cursor onto each video frame.
+  const std::unique_ptr<MouseCursorOverlayController,
+                        BrowserThread::DeleteOnUIThread>
+      cursor_controller_;
 
   // Creates WeakPtrs for use on the device thread.
   base::WeakPtrFactory<FrameSinkVideoCaptureDevice> weak_factory_;
diff --git a/content/browser/media/capture/mouse_cursor_overlay_controller.h b/content/browser/media/capture/mouse_cursor_overlay_controller.h
index 78d09d2..5902ac6d 100644
--- a/content/browser/media/capture/mouse_cursor_overlay_controller.h
+++ b/content/browser/media/capture/mouse_cursor_overlay_controller.h
@@ -14,6 +14,7 @@
 #include "base/sequenced_task_runner.h"
 #include "base/timer/timer.h"
 #include "content/common/content_export.h"
+#include "services/viz/privileged/interfaces/compositing/frame_sink_video_capture.mojom.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/base/cursor/cursor.h"
 #include "ui/gfx/geometry/point_f.h"
@@ -33,15 +34,7 @@
 // multiple threads.
 class CONTENT_EXPORT MouseCursorOverlayController {
  public:
-  // TODO(crbug.com/810133): Replace this stub interface with
-  // viz::mojom::FrameSinkVideoCaptureOverlay in a soon-upcoming change.
-  class Overlay {
-   public:
-    virtual ~Overlay() = default;
-    virtual void SetImageAndBounds(const SkBitmap& image,
-                                   const gfx::RectF& bounds) = 0;
-    virtual void SetBounds(const gfx::RectF& bounds) = 0;
-  };
+  using Overlay = viz::mojom::FrameSinkVideoCaptureOverlay;
 
   MouseCursorOverlayController();
   ~MouseCursorOverlayController();
diff --git a/content/browser/media/capture/web_contents_video_capture_device.cc b/content/browser/media/capture/web_contents_video_capture_device.cc
index e4122a24..ff8651a 100644
--- a/content/browser/media/capture/web_contents_video_capture_device.cc
+++ b/content/browser/media/capture/web_contents_video_capture_device.cc
@@ -4,12 +4,15 @@
 
 #include "content/browser/media/capture/web_contents_video_capture_device.h"
 
+#include <utility>
+
 #include "base/bind.h"
 #include "base/location.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "content/browser/media/capture/mouse_cursor_overlay_controller.h"
 #include "content/browser/renderer_host/render_widget_host_view_base.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
@@ -31,14 +34,14 @@
           WebContentsVideoCaptureDevice::FrameTracker> {
  public:
   FrameTracker(base::WeakPtr<WebContentsVideoCaptureDevice> device,
-               CursorRenderer* cursor_renderer,
+               MouseCursorOverlayController* cursor_controller,
                int render_process_id,
                int main_render_frame_id)
       : device_(std::move(device)),
         device_task_runner_(base::ThreadTaskRunnerHandle::Get()),
-        cursor_renderer_(cursor_renderer) {
+        cursor_controller_(cursor_controller) {
     DCHECK(device_task_runner_);
-    DCHECK(cursor_renderer_);
+    DCHECK(cursor_controller_);
 
     BrowserThread::PostTask(
         BrowserThread::UI, FROM_HERE,
@@ -175,10 +178,10 @@
 
       if (native_view != target_native_view_) {
         target_native_view_ = native_view;
-        // Note: CursorRenderer runs on the UI thread. It's also important that
-        // SetTargetView() be called in the current stack while |native_view| is
-        // known to be a valid pointer. http://crbug.com/818679
-        cursor_renderer_->SetTargetView(native_view);
+        // Note: MouseCursorOverlayController runs on the UI thread. It's also
+        // important that SetTargetView() be called in the current stack while
+        // |native_view| is known to be a valid pointer. http://crbug.com/818679
+        cursor_controller_->SetTargetView(native_view);
       }
     } else {
       device_task_runner_->PostTask(
@@ -186,7 +189,7 @@
           base::BindOnce(
               &WebContentsVideoCaptureDevice::OnTargetPermanentlyLost,
               device_));
-      cursor_renderer_->SetTargetView(gfx::NativeView());
+      cursor_controller_->SetTargetView(gfx::NativeView());
     }
   }
 
@@ -196,8 +199,8 @@
 
   // Owned by FrameSinkVideoCaptureDevice. This will be valid for the life of
   // FrameTracker because the FrameTracker deleter task will be posted to the UI
-  // thread before the CursorRenderer deleter task.
-  CursorRenderer* const cursor_renderer_;
+  // thread before the MouseCursorOverlayController deleter task.
+  MouseCursorOverlayController* const cursor_controller_;
 
   viz::FrameSinkId target_frame_sink_id_;
   gfx::NativeView target_native_view_ = gfx::NativeView();
@@ -212,7 +215,7 @@
     int render_process_id,
     int main_render_frame_id)
     : tracker_(new FrameTracker(AsWeakPtr(),
-                                cursor_renderer(),
+                                cursor_controller(),
                                 render_process_id,
                                 main_render_frame_id)) {}
 
diff --git a/content/browser/media/media_color_browsertest.cc b/content/browser/media/media_color_browsertest.cc
index 1a541b2..6cc930bb 100644
--- a/content/browser/media/media_color_browsertest.cc
+++ b/content/browser/media/media_color_browsertest.cc
@@ -49,13 +49,7 @@
 
 #if BUILDFLAG(USE_PROPRIETARY_CODECS)
 
-// This fails on some Android devices: http://crbug.com/649199
-#if defined(OS_ANDROID)
-#define MAYBE_Yuv420pH264 DISABLED_Yuv420pH264
-#else
-#define MAYBE_Yuv420pH264 Yuv420pH264
-#endif
-IN_PROC_BROWSER_TEST_F(MediaColorTest, MAYBE_Yuv420pH264) {
+IN_PROC_BROWSER_TEST_F(MediaColorTest, Yuv420pH264) {
   RunColorTest("yuv420p.mp4");
 }
 
@@ -71,8 +65,7 @@
 }
 
 // This fails on ChromeOS: http://crbug.com/647400,
-// and Android: http://crbug.com/649199, http://crbug.com/649185.
-#if defined(OS_CHROMEOS) || defined(OS_ANDROID)
+#if defined(OS_CHROMEOS)
 #define MAYBE_Yuv420pRec709H264 DISABLED_Yuv420pRec709H264
 #else
 #define MAYBE_Yuv420pRec709H264 Yuv420pRec709H264
diff --git a/content/browser/pointer_lock_browsertest.cc b/content/browser/pointer_lock_browsertest.cc
index b4f1023..d6eaf14 100644
--- a/content/browser/pointer_lock_browsertest.cc
+++ b/content/browser/pointer_lock_browsertest.cc
@@ -125,41 +125,27 @@
   FrameTreeNode* child = root->child_at(0);
 
   // Request a pointer lock on the root frame's body.
-  EXPECT_TRUE(ExecuteScript(root, "document.body.requestPointerLock()"));
+  EXPECT_TRUE(ExecJs(root, "document.body.requestPointerLock()"));
 
   // Root frame should have been granted pointer lock.
-  bool locked = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(root,
-                                          "window.domAutomationController.send("
-                                          "document.pointerLockElement == "
-                                          "document.body);",
-                                          &locked));
-  EXPECT_TRUE(locked);
+  EXPECT_EQ(true, EvalJs(root, "document.pointerLockElement == document.body"));
 
   // Request a pointer lock on the child frame's body.
-  EXPECT_TRUE(ExecuteScript(child, "document.body.requestPointerLock()"));
+  EXPECT_TRUE(ExecJs(child, "document.body.requestPointerLock()"));
 
   // Child frame should not be granted pointer lock since the root frame has it.
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(child,
-                                          "window.domAutomationController.send("
-                                          "document.pointerLockElement == "
-                                          "document.body);",
-                                          &locked));
-  EXPECT_FALSE(locked);
+  EXPECT_EQ(false,
+            EvalJs(child, "document.pointerLockElement == document.body"));
 
   // Release pointer lock on root frame.
-  EXPECT_TRUE(ExecuteScript(root, "document.exitPointerLock()"));
+  EXPECT_TRUE(ExecJs(root, "document.exitPointerLock()"));
 
   // Request a pointer lock on the child frame's body.
-  EXPECT_TRUE(ExecuteScript(child, "document.body.requestPointerLock()"));
+  EXPECT_TRUE(ExecJs(child, "document.body.requestPointerLock()"));
 
   // Child frame should have been granted pointer lock.
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(child,
-                                          "window.domAutomationController.send("
-                                          "document.pointerLockElement == "
-                                          "document.body);",
-                                          &locked));
-  EXPECT_TRUE(locked);
+  EXPECT_EQ(true,
+            EvalJs(child, "document.pointerLockElement == document.body"));
 }
 
 IN_PROC_BROWSER_TEST_F(PointerLockBrowserTest, PointerLockEventRouting) {
@@ -179,19 +165,13 @@
   WaitForHitTestDataOrChildSurfaceReady(child->current_frame_host());
 
   // Request a pointer lock on the root frame's body.
-  EXPECT_TRUE(ExecuteScript(root, "document.body.requestPointerLock()"));
+  EXPECT_TRUE(ExecJs(root, "document.body.requestPointerLock()"));
 
   // Root frame should have been granted pointer lock.
-  bool locked = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(root,
-                                          "window.domAutomationController.send("
-                                          "document.pointerLockElement == "
-                                          "document.body);",
-                                          &locked));
-  EXPECT_TRUE(locked);
+  EXPECT_EQ(true, EvalJs(root, "document.pointerLockElement == document.body"));
 
   // Add a mouse move event listener to the root frame.
-  EXPECT_TRUE(ExecuteScript(
+  EXPECT_TRUE(ExecJs(
       root,
       "var x; var y; var mX; var mY; document.addEventListener('mousemove', "
       "function(e) {x = e.x; y = e.y; mX = e.movementX; mY = e.movementY;});"));
@@ -208,36 +188,20 @@
   MainThreadFrameObserver root_observer(root_view->GetRenderWidgetHost());
   root_observer.Wait();
 
-  int x, y, movementX, movementY;
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      root, "window.domAutomationController.send(x);", &x));
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      root, "window.domAutomationController.send(y);", &y));
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      root, "window.domAutomationController.send(mX);", &movementX));
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      root, "window.domAutomationController.send(mY);", &movementY));
-  EXPECT_EQ(10, x);
-  EXPECT_EQ(11, y);
-  EXPECT_EQ(12, movementX);
-  EXPECT_EQ(13, movementY);
+  EXPECT_EQ("[10,11,12,13]", EvalJs(root, "JSON.stringify([x,y,mX,mY])"));
 
   // Release pointer lock on root frame.
-  EXPECT_TRUE(ExecuteScript(root, "document.exitPointerLock()"));
+  EXPECT_TRUE(ExecJs(root, "document.exitPointerLock()"));
 
   // Request a pointer lock on the child frame's body.
-  EXPECT_TRUE(ExecuteScript(child, "document.body.requestPointerLock()"));
+  EXPECT_TRUE(ExecJs(child, "document.body.requestPointerLock()"));
 
   // Child frame should have been granted pointer lock.
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(child,
-                                          "window.domAutomationController.send("
-                                          "document.pointerLockElement == "
-                                          "document.body);",
-                                          &locked));
-  EXPECT_TRUE(locked);
+  EXPECT_EQ(true,
+            EvalJs(child, "document.pointerLockElement == document.body"));
 
   // Add a mouse move event listener to the child frame.
-  EXPECT_TRUE(ExecuteScript(
+  EXPECT_TRUE(ExecJs(
       child,
       "var x; var y; var mX; var mY; document.addEventListener('mousemove', "
       "function(e) {x = e.x; y = e.y; mX = e.movementX; mY = e.movementY;});"));
@@ -259,18 +223,7 @@
   MainThreadFrameObserver child_observer(child_view->GetRenderWidgetHost());
   child_observer.Wait();
 
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      child, "window.domAutomationController.send(x);", &x));
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      child, "window.domAutomationController.send(y);", &y));
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      child, "window.domAutomationController.send(mX);", &movementX));
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      child, "window.domAutomationController.send(mY);", &movementY));
-  EXPECT_EQ(14, x);
-  EXPECT_EQ(15, y);
-  EXPECT_EQ(16, movementX);
-  EXPECT_EQ(17, movementY);
+  EXPECT_EQ("[14,15,16,17]", EvalJs(child, "JSON.stringify([x,y,mX,mY])"));
 }
 
 // Tests that the browser will not unlock the pointer if a RenderWidgetHostView
@@ -283,16 +236,10 @@
   FrameTreeNode* root = web_contents()->GetFrameTree()->root();
 
   // Request a pointer lock on the root frame's body.
-  EXPECT_TRUE(ExecuteScript(root, "document.body.requestPointerLock()"));
+  EXPECT_TRUE(ExecJs(root, "document.body.requestPointerLock()"));
 
   // Root frame should have been granted pointer lock.
-  bool locked = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(root,
-                                          "window.domAutomationController.send("
-                                          "document.pointerLockElement == "
-                                          "document.body);",
-                                          &locked));
-  EXPECT_TRUE(locked);
+  EXPECT_EQ(true, EvalJs(root, "document.pointerLockElement == document.body"));
 
   // Root (platform) RenderWidgetHostView should have the pointer locked.
   EXPECT_TRUE(root->current_frame_host()->GetView()->IsMouseLocked());
@@ -300,7 +247,7 @@
             web_contents()->GetMouseLockWidget());
 
   // Detach the child frame.
-  EXPECT_TRUE(ExecuteScript(root, "document.querySelector('iframe').remove()"));
+  EXPECT_TRUE(ExecJs(root, "document.querySelector('iframe').remove()"));
 
   // Root (platform) RenderWidgetHostView should still have the pointer locked.
   EXPECT_TRUE(root->current_frame_host()->GetView()->IsMouseLocked());
@@ -333,21 +280,13 @@
                           "c.com", "/cross_site_iframe_factory.html?c(d)")));
 
   // Request a pointer lock to the inner WebContents's document.body.
-  std::string result;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(inner_contents->GetMainFrame(), R"(
-        (new Promise((resolve, reject) => {
+  EXPECT_EQ("success", EvalJs(inner_contents->GetMainFrame(), R"(
+        new Promise((resolve, reject) => {
             document.addEventListener('pointerlockchange', resolve);
             document.addEventListener('pointerlockerror', reject);
-        }).then(() => {
-            window.domAutomationController.send(
-                (document.pointerLockElement == document.body) ?
-                     "success" : "error");
-        }).catch(error => {
-            window.domAutomationController.send("" + error);
-        }));
-        document.body.requestPointerLock();)",
-                                            &result));
-  EXPECT_EQ("success", result);
+            document.body.requestPointerLock();
+        }).then(() => 'success');
+        )"));
 
   // Root (platform) RenderWidgetHostView should have the pointer locked.
   EXPECT_TRUE(root->current_frame_host()->GetView()->IsMouseLocked());
@@ -397,21 +336,15 @@
   WaitForHitTestDataOrChildSurfaceReady(child->current_frame_host());
 
   // Request a pointer lock on the root frame's body.
-  EXPECT_TRUE(ExecuteScript(root, "document.body.requestPointerLock()"));
+  EXPECT_TRUE(ExecJs(root, "document.body.requestPointerLock()"));
 
   // Root frame should have been granted pointer lock.
-  bool locked = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(root,
-                                          "window.domAutomationController.send("
-                                          "document.pointerLockElement == "
-                                          "document.body);",
-                                          &locked));
-  EXPECT_TRUE(locked);
+  EXPECT_EQ(true, EvalJs(root, "document.pointerLockElement == document.body"));
 
   // Add a mouse move wheel event listener to the root frame.
-  EXPECT_TRUE(ExecuteScript(
+  EXPECT_TRUE(ExecJs(
       root,
-      "var x; var y; var mX; var mY; document.addEventListener('mousewheel', "
+      "var x; var y; var dX; var dY; document.addEventListener('mousewheel', "
       "function(e) {x = e.x; y = e.y; dX = e.deltaX; dY = e.deltaY;});"));
   MainThreadFrameObserver root_observer(root_view->GetRenderWidgetHost());
   root_observer.Wait();
@@ -439,38 +372,22 @@
   // Make sure that the renderer handled the input event.
   root_observer.Wait();
 
-  int x, y, deltaX, deltaY;
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      root, "window.domAutomationController.send(x);", &x));
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      root, "window.domAutomationController.send(y);", &y));
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      root, "window.domAutomationController.send(dX);", &deltaX));
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      root, "window.domAutomationController.send(dY);", &deltaY));
-  EXPECT_EQ(10, x);
-  EXPECT_EQ(11, y);
-  EXPECT_EQ(12, deltaX);
-  EXPECT_EQ(13, deltaY);
+  EXPECT_EQ("[10,11,12,13]", EvalJs(root, "JSON.stringify([x, y, dX, dY])"));
 
   // Release pointer lock on root frame.
-  EXPECT_TRUE(ExecuteScript(root, "document.exitPointerLock()"));
+  EXPECT_TRUE(ExecJs(root, "document.exitPointerLock()"));
 
   // Request a pointer lock on the child frame's body.
-  EXPECT_TRUE(ExecuteScript(child, "document.body.requestPointerLock()"));
+  EXPECT_TRUE(ExecJs(child, "document.body.requestPointerLock()"));
 
   // Child frame should have been granted pointer lock.
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(child,
-                                          "window.domAutomationController.send("
-                                          "document.pointerLockElement == "
-                                          "document.body);",
-                                          &locked));
-  EXPECT_TRUE(locked);
+  EXPECT_EQ(true,
+            EvalJs(child, "document.pointerLockElement == document.body"));
 
   // Add a mouse move event listener to the child frame.
-  EXPECT_TRUE(ExecuteScript(
+  EXPECT_TRUE(ExecJs(
       child,
-      "var x; var y; var mX; var mY; document.addEventListener('mousewheel', "
+      "var x; var y; var dX; var dY; document.addEventListener('mousewheel', "
       "function(e) {x = e.x; y = e.y; dX = e.deltaX; dY = e.deltaY;});"));
   MainThreadFrameObserver child_observer(child_view->GetRenderWidgetHost());
   child_observer.Wait();
@@ -492,18 +409,7 @@
   // Make sure that the renderer handled the input event.
   child_observer.Wait();
 
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      child, "window.domAutomationController.send(x);", &x));
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      child, "window.domAutomationController.send(y);", &y));
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      child, "window.domAutomationController.send(dX);", &deltaX));
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      child, "window.domAutomationController.send(dY);", &deltaY));
-  EXPECT_EQ(14, x);
-  EXPECT_EQ(15, y);
-  EXPECT_EQ(16, deltaX);
-  EXPECT_EQ(17, deltaY);
+  EXPECT_EQ("[14,15,16,17]", EvalJs(child, "JSON.stringify([x, y, dX, dY])"));
 }
 
 IN_PROC_BROWSER_TEST_F(PointerLockBrowserTest, PointerLockWidgetHidden) {
@@ -519,16 +425,11 @@
   WaitForHitTestDataOrChildSurfaceReady(child->current_frame_host());
 
   // Request a pointer lock on the child frame's body.
-  EXPECT_TRUE(ExecuteScript(child, "document.body.requestPointerLock()"));
+  EXPECT_TRUE(ExecJs(child, "document.body.requestPointerLock()"));
 
   // Child frame should have been granted pointer lock.
-  bool locked = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(child,
-                                          "window.domAutomationController.send("
-                                          "document.pointerLockElement == "
-                                          "document.body);",
-                                          &locked));
-  EXPECT_TRUE(locked);
+  EXPECT_EQ(true,
+            EvalJs(child, "document.pointerLockElement == document.body"));
   EXPECT_TRUE(child_view->IsMouseLocked());
   EXPECT_EQ(child_view->host(), web_contents()->GetMouseLockWidget());
 
diff --git a/content/browser/renderer_host/media/audio_input_delegate_impl.cc b/content/browser/renderer_host/media/audio_input_delegate_impl.cc
index d01a24bd..718bb1a6 100644
--- a/content/browser/renderer_host/media/audio_input_delegate_impl.cc
+++ b/content/browser/renderer_host/media/audio_input_delegate_impl.cc
@@ -192,9 +192,9 @@
   const std::string& device_id = device->id;
 
   if (WebContentsMediaCaptureId::Parse(device_id, nullptr)) {
-    // For MEDIA_DESKTOP_AUDIO_CAPTURE, the source is selected from picker
-    // window, we do not mute the source audio.
-    // For MEDIA_TAB_AUDIO_CAPTURE, the probable use case is Cast, we mute
+    // For MEDIA_GUM_DESKTOP_AUDIO_CAPTURE, the source is selected from
+    // picker window, we do not mute the source audio. For
+    // MEDIA_GUM_TAB_AUDIO_CAPTURE, the probable use case is Cast, we mute
     // the source audio.
     // TODO(qiangchen): Analyze audio constraints to make a duplicating or
     // diverting decision. It would give web developer more flexibility.
@@ -206,7 +206,7 @@
         writer_.get(), user_input_monitor);
     DCHECK(controller_);
     // Only count for captures from desktop media picker dialog.
-    if (device->type == MEDIA_DESKTOP_AUDIO_CAPTURE)
+    if (device->type == MEDIA_GUM_DESKTOP_AUDIO_CAPTURE)
       IncrementDesktopCaptureCounter(TAB_AUDIO_CAPTURER_CREATED);
   } else {
     controller_ = media::AudioInputController::Create(
@@ -216,7 +216,7 @@
 
     // Only count for captures from desktop media picker dialog and system loop
     // back audio.
-    if (device->type == MEDIA_DESKTOP_AUDIO_CAPTURE &&
+    if (device->type == MEDIA_GUM_DESKTOP_AUDIO_CAPTURE &&
         (media::AudioDeviceDescription::IsLoopbackDevice(device_id))) {
       IncrementDesktopCaptureCounter(SYSTEM_LOOPBACK_AUDIO_CAPTURER_CREATED);
     }
diff --git a/content/browser/renderer_host/media/audio_input_device_manager.cc b/content/browser/renderer_host/media/audio_input_device_manager.cc
index db018e66..2e72505 100644
--- a/content/browser/renderer_host/media/audio_input_device_manager.cc
+++ b/content/browser/renderer_host/media/audio_input_device_manager.cc
@@ -94,7 +94,7 @@
                                   base::Optional<media::AudioParameters>()));
   } else {
     // TODO(tommi): As is, we hit this code path when device.type is
-    // MEDIA_TAB_AUDIO_CAPTURE and the device id is not a device that
+    // MEDIA_GUM_TAB_AUDIO_CAPTURE and the device id is not a device that
     // the AudioManager can know about. This currently does not fail because
     // the implementation of GetInputStreamParameters returns valid parameters
     // by default for invalid devices. That behavior is problematic because it
diff --git a/content/browser/renderer_host/media/audio_input_device_manager_unittest.cc b/content/browser/renderer_host/media/audio_input_device_manager_unittest.cc
index 2f9723a..499484a 100644
--- a/content/browser/renderer_host/media/audio_input_device_manager_unittest.cc
+++ b/content/browser/renderer_host/media/audio_input_device_manager_unittest.cc
@@ -312,9 +312,9 @@
         std::make_unique<media::AudioThreadImpl>());
 
     // Devices to request from AudioInputDeviceManager.
-    devices_.emplace_back(MEDIA_TAB_AUDIO_CAPTURE, "tab_capture",
+    devices_.emplace_back(MEDIA_GUM_TAB_AUDIO_CAPTURE, "tab_capture",
                           "Tab capture");
-    devices_.emplace_back(MEDIA_DESKTOP_AUDIO_CAPTURE, "desktop_capture",
+    devices_.emplace_back(MEDIA_GUM_DESKTOP_AUDIO_CAPTURE, "desktop_capture",
                           "Desktop capture");
     devices_.emplace_back(MEDIA_DEVICE_AUDIO_CAPTURE, "fake_device",
                           "Fake Device");
diff --git a/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc b/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc
index 77742e8..c5165c6 100644
--- a/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc
+++ b/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc
@@ -123,7 +123,7 @@
 
 #if defined(ENABLE_SCREEN_CAPTURE)
 #if !defined(OS_ANDROID)
-    case MEDIA_TAB_VIDEO_CAPTURE:
+    case MEDIA_GUM_TAB_VIDEO_CAPTURE:
       start_capture_closure = base::BindOnce(
           &InProcessVideoCaptureDeviceLauncher::DoStartTabCaptureOnDeviceThread,
           base::Unretained(this), device_id, params, std::move(receiver),
@@ -131,7 +131,9 @@
       break;
 #endif  // !defined(OS_ANDROID)
 
-    case MEDIA_DESKTOP_VIDEO_CAPTURE: {
+    case MEDIA_GUM_DESKTOP_VIDEO_CAPTURE:
+      FALLTHROUGH;
+    case MEDIA_DISPLAY_VIDEO_CAPTURE: {
       const DesktopMediaID desktop_id = DesktopMediaID::Parse(device_id);
       if (desktop_id.is_null()) {
         DLOG(ERROR) << "Desktop media ID is null";
diff --git a/content/browser/renderer_host/media/media_stream_manager.cc b/content/browser/renderer_host/media/media_stream_manager.cc
index b97bd90..2deae14 100644
--- a/content/browser/renderer_host/media/media_stream_manager.cc
+++ b/content/browser/renderer_host/media/media_stream_manager.cc
@@ -177,12 +177,12 @@
 
 MediaStreamType AdjustAudioStreamTypeBasedOnCommandLineSwitches(
     MediaStreamType stream_type) {
-  if (stream_type != MEDIA_DESKTOP_AUDIO_CAPTURE)
+  if (stream_type != MEDIA_GUM_DESKTOP_AUDIO_CAPTURE)
     return stream_type;
   const bool audio_support_flag_for_desktop_share =
       !base::CommandLine::ForCurrentProcess()->HasSwitch(
           switches::kDisableAudioSupportForDesktopShare);
-  return audio_support_flag_for_desktop_share ? MEDIA_DESKTOP_AUDIO_CAPTURE
+  return audio_support_flag_for_desktop_share ? MEDIA_GUM_DESKTOP_AUDIO_CAPTURE
                                               : MEDIA_NO_SERVICE;
 }
 
@@ -989,10 +989,11 @@
     const MediaDeviceEnumeration& enumeration) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
-  // Actual audio parameters are required only for MEDIA_TAB_AUDIO_CAPTURE.
-  // TODO(guidou): MEDIA_TAB_AUDIO_CAPTURE should not be a special case. See
-  // crbug.com/584287.
-  if (request->audio_type() == MEDIA_TAB_AUDIO_CAPTURE) {
+  // Actual audio parameters are required only for
+  // MEDIA_GUM_TAB_AUDIO_CAPTURE.
+  // TODO(guidou): MEDIA_GUM_TAB_AUDIO_CAPTURE should not be a special
+  // case. See https://crbug.com/584287.
+  if (request->audio_type() == MEDIA_GUM_TAB_AUDIO_CAPTURE) {
     // Using base::Unretained is safe: |audio_system_| will post
     // PostRequestToUI() to IO thread, and MediaStreamManager is deleted on the
     // UI thread, after the IO thread has been stopped.
@@ -1032,7 +1033,7 @@
   // The fake UI doesn't work for desktop sharing requests since we can't see
   // its devices from here; always use the real UI for such requests.
   if (fake_ui_factory_ &&
-      request->video_type() != MEDIA_DESKTOP_VIDEO_CAPTURE) {
+      request->video_type() != MEDIA_GUM_DESKTOP_VIDEO_CAPTURE) {
     MediaStreamDevices devices = ConvertToMediaStreamDevices(
         request->audio_type(), enumeration[MEDIA_DEVICE_TYPE_AUDIO_INPUT]);
     MediaStreamDevices video_devices = ConvertToMediaStreamDevices(
@@ -1069,8 +1070,8 @@
   request->SetVideoType(request->controls.video.stream_type);
 
   const bool is_tab_capture =
-      request->audio_type() == MEDIA_TAB_AUDIO_CAPTURE ||
-      request->video_type() == MEDIA_TAB_VIDEO_CAPTURE;
+      request->audio_type() == MEDIA_GUM_TAB_AUDIO_CAPTURE ||
+      request->video_type() == MEDIA_GUM_TAB_VIDEO_CAPTURE;
   if (is_tab_capture) {
     if (!SetUpTabCaptureRequest(request, label)) {
       FinalizeRequestFailed(label, request, MEDIA_DEVICE_TAB_CAPTURE_FAILURE);
@@ -1079,7 +1080,7 @@
   }
 
   const bool is_screen_capture =
-      request->video_type() == MEDIA_DESKTOP_VIDEO_CAPTURE;
+      request->video_type() == MEDIA_GUM_DESKTOP_VIDEO_CAPTURE;
   if (is_screen_capture && !SetUpScreenCaptureRequest(request)) {
     FinalizeRequestFailed(label, request, MEDIA_DEVICE_SCREEN_CAPTURE_FAILURE);
     return;
@@ -1133,8 +1134,8 @@
 
 bool MediaStreamManager::SetUpTabCaptureRequest(DeviceRequest* request,
                                                 const std::string& label) {
-  DCHECK(request->audio_type() == MEDIA_TAB_AUDIO_CAPTURE ||
-         request->video_type() == MEDIA_TAB_VIDEO_CAPTURE);
+  DCHECK(request->audio_type() == MEDIA_GUM_TAB_AUDIO_CAPTURE ||
+         request->video_type() == MEDIA_GUM_TAB_VIDEO_CAPTURE);
 
   std::string capture_device_id;
   if (!request->controls.audio.device_id.empty()) {
@@ -1145,9 +1146,9 @@
     return false;
   }
 
-  if ((request->audio_type() != MEDIA_TAB_AUDIO_CAPTURE &&
+  if ((request->audio_type() != MEDIA_GUM_TAB_AUDIO_CAPTURE &&
        request->audio_type() != MEDIA_NO_SERVICE) ||
-      (request->video_type() != MEDIA_TAB_VIDEO_CAPTURE &&
+      (request->video_type() != MEDIA_GUM_TAB_VIDEO_CAPTURE &&
        request->video_type() != MEDIA_NO_SERVICE)) {
     return false;
   }
@@ -1213,29 +1214,28 @@
 }
 
 bool MediaStreamManager::SetUpScreenCaptureRequest(DeviceRequest* request) {
-  DCHECK(request->audio_type() == MEDIA_DESKTOP_AUDIO_CAPTURE ||
-         request->video_type() == MEDIA_DESKTOP_VIDEO_CAPTURE);
+  DCHECK(request->audio_type() == MEDIA_GUM_DESKTOP_AUDIO_CAPTURE ||
+         request->video_type() == MEDIA_GUM_DESKTOP_VIDEO_CAPTURE);
 
   // For screen capture we only support two valid combinations:
   // (1) screen video capture only, or
   // (2) screen video capture with loopback audio capture.
-  if (request->video_type() != MEDIA_DESKTOP_VIDEO_CAPTURE ||
+  if (request->video_type() != MEDIA_GUM_DESKTOP_VIDEO_CAPTURE ||
       (request->audio_type() != MEDIA_NO_SERVICE &&
-       request->audio_type() != MEDIA_DESKTOP_AUDIO_CAPTURE)) {
+       request->audio_type() != MEDIA_GUM_DESKTOP_AUDIO_CAPTURE)) {
     LOG(ERROR) << "Invalid screen capture request.";
     return false;
   }
 
   std::string video_device_id;
-  if (request->video_type() == MEDIA_DESKTOP_VIDEO_CAPTURE) {
-    if (!request->controls.video.device_id.empty()) {
-      video_device_id = request->controls.video.device_id;
-    }
+  if (request->video_type() == MEDIA_GUM_DESKTOP_VIDEO_CAPTURE &&
+      !request->controls.video.device_id.empty()) {
+    video_device_id = request->controls.video.device_id;
   }
 
   const std::string audio_device_id =
-      request->audio_type() == MEDIA_DESKTOP_AUDIO_CAPTURE ? video_device_id
-                                                           : "";
+      request->audio_type() == MEDIA_GUM_DESKTOP_AUDIO_CAPTURE ? video_device_id
+                                                               : "";
 
   request->CreateUIRequest(audio_device_id, video_device_id);
   return true;
@@ -1430,7 +1430,7 @@
           // Store the native audio parameters in the device struct.
           // TODO(xians): Handle the tab capture sample rate/channel layout
           // in AudioInputDeviceManager::Open().
-          if (device.type != MEDIA_TAB_AUDIO_CAPTURE) {
+          if (device.type != MEDIA_GUM_TAB_AUDIO_CAPTURE) {
             const MediaStreamDevice* opened_device =
                 audio_input_device_manager_->GetOpenedDeviceById(
                     device.session_id);
@@ -1599,16 +1599,16 @@
   for (const MediaStreamDevice& media_stream_device : devices) {
     MediaStreamDevice device = media_stream_device;
 
-    if (device.type == MEDIA_TAB_VIDEO_CAPTURE ||
-        device.type == MEDIA_TAB_AUDIO_CAPTURE) {
+    if (device.type == MEDIA_GUM_TAB_VIDEO_CAPTURE ||
+        device.type == MEDIA_GUM_TAB_AUDIO_CAPTURE) {
       device.id = request->tab_capture_device_id;
     }
 
     // Initialize the sample_rate and channel_layout here since for audio
     // mirroring, we don't go through EnumerateDevices where these are usually
     // initialized.
-    if (device.type == MEDIA_TAB_AUDIO_CAPTURE ||
-        device.type == MEDIA_DESKTOP_AUDIO_CAPTURE) {
+    if (device.type == MEDIA_GUM_TAB_AUDIO_CAPTURE ||
+        device.type == MEDIA_GUM_DESKTOP_AUDIO_CAPTURE) {
       int sample_rate = output_parameters.sample_rate();
       // If we weren't able to get the native sampling rate or the sample_rate
       // is outside the valid range for input devices set reasonable defaults.
@@ -1770,13 +1770,13 @@
   if (!window_id)
     return;
 
-  if (video_type != MEDIA_DESKTOP_VIDEO_CAPTURE)
+  if (video_type != MEDIA_GUM_DESKTOP_VIDEO_CAPTURE)
     return;
 
   // Pass along for desktop screen and window capturing when
   // DesktopCaptureDevice is used.
   for (const MediaStreamDevice& device : devices) {
-    if (device.type != MEDIA_DESKTOP_VIDEO_CAPTURE)
+    if (device.type != MEDIA_GUM_DESKTOP_VIDEO_CAPTURE)
       continue;
 
     DesktopMediaID media_id = DesktopMediaID::Parse(device.id);
diff --git a/content/browser/renderer_host/media/media_stream_manager.h b/content/browser/renderer_host/media/media_stream_manager.h
index 569745f..620965a4 100644
--- a/content/browser/renderer_host/media/media_stream_manager.h
+++ b/content/browser/renderer_host/media/media_stream_manager.h
@@ -358,8 +358,8 @@
   // StreamControls for requested device IDs.
   bool SetUpDeviceCaptureRequest(DeviceRequest* request,
                                  const MediaDeviceEnumeration& enumeration);
-  // Prepare |request| of type MEDIA_DESKTOP_AUDIO_CAPTURE and/or
-  // MEDIA_DESKTOP_VIDEO_CAPTURE for being posted to the UI by parsing
+  // Prepare |request| of type MEDIA_GUM_DESKTOP_AUDIO_CAPTURE and/or
+  // MEDIA_GUM_DESKTOP_VIDEO_CAPTURE for being posted to the UI by parsing
   // StreamControls for the requested desktop ID.
   bool SetUpScreenCaptureRequest(DeviceRequest* request);
   // Resolve the random device ID of tab capture on UI thread before proceeding
@@ -370,9 +370,9 @@
       int requesting_process_id,
       int requesting_frame_id,
       const GURL& origin);
-  // Prepare |request| of type MEDIA_TAB_AUDIO_CAPTURE and/or
-  // MEDIA_TAB_VIDEO_CAPTURE for being posted to the UI after the requested
-  // tab capture IDs are resolved from registry.
+  // Prepare |request| of type MEDIA_GUM_TAB_AUDIO_CAPTURE and/or
+  // MEDIA_GUM_TAB_VIDEO_CAPTURE for being posted to the UI after the
+  // requested tab capture IDs are resolved from registry.
   void FinishTabCaptureRequestSetupWithDeviceId(
       const std::string& label,
       const DesktopMediaID& device_id);
diff --git a/content/browser/renderer_host/media/media_stream_manager_unittest.cc b/content/browser/renderer_host/media/media_stream_manager_unittest.cc
index 14d12b8..f91261ce 100644
--- a/content/browser/renderer_host/media/media_stream_manager_unittest.cc
+++ b/content/browser/renderer_host/media/media_stream_manager_unittest.cc
@@ -250,17 +250,20 @@
   EXPECT_CALL(*media_observer_,
               OnMediaRequestStateChanged(_, _, _, _, MEDIA_DEVICE_VIDEO_CAPTURE,
                                          MEDIA_REQUEST_STATE_CLOSING));
-  EXPECT_CALL(*media_observer_,
-              OnMediaRequestStateChanged(_, _, _, _, MEDIA_TAB_AUDIO_CAPTURE,
-                                         MEDIA_REQUEST_STATE_CLOSING));
-  EXPECT_CALL(*media_observer_,
-              OnMediaRequestStateChanged(_, _, _, _, MEDIA_TAB_VIDEO_CAPTURE,
-                                         MEDIA_REQUEST_STATE_CLOSING));
   EXPECT_CALL(*media_observer_, OnMediaRequestStateChanged(
-                                    _, _, _, _, MEDIA_DESKTOP_VIDEO_CAPTURE,
+                                    _, _, _, _, MEDIA_GUM_TAB_AUDIO_CAPTURE,
                                     MEDIA_REQUEST_STATE_CLOSING));
   EXPECT_CALL(*media_observer_, OnMediaRequestStateChanged(
-                                    _, _, _, _, MEDIA_DESKTOP_AUDIO_CAPTURE,
+                                    _, _, _, _, MEDIA_GUM_TAB_VIDEO_CAPTURE,
+                                    MEDIA_REQUEST_STATE_CLOSING));
+  EXPECT_CALL(*media_observer_, OnMediaRequestStateChanged(
+                                    _, _, _, _, MEDIA_GUM_DESKTOP_VIDEO_CAPTURE,
+                                    MEDIA_REQUEST_STATE_CLOSING));
+  EXPECT_CALL(*media_observer_, OnMediaRequestStateChanged(
+                                    _, _, _, _, MEDIA_GUM_DESKTOP_AUDIO_CAPTURE,
+                                    MEDIA_REQUEST_STATE_CLOSING));
+  EXPECT_CALL(*media_observer_, OnMediaRequestStateChanged(
+                                    _, _, _, _, MEDIA_DISPLAY_VIDEO_CAPTURE,
                                     MEDIA_REQUEST_STATE_CLOSING));
   media_stream_manager_->CancelRequest(label);
   run_loop_.RunUntilIdle();
@@ -301,17 +304,20 @@
   EXPECT_CALL(*media_observer_,
               OnMediaRequestStateChanged(_, _, _, _, MEDIA_DEVICE_VIDEO_CAPTURE,
                                          MEDIA_REQUEST_STATE_CLOSING));
-  EXPECT_CALL(*media_observer_,
-              OnMediaRequestStateChanged(_, _, _, _, MEDIA_TAB_AUDIO_CAPTURE,
-                                         MEDIA_REQUEST_STATE_CLOSING));
-  EXPECT_CALL(*media_observer_,
-              OnMediaRequestStateChanged(_, _, _, _, MEDIA_TAB_VIDEO_CAPTURE,
-                                         MEDIA_REQUEST_STATE_CLOSING));
   EXPECT_CALL(*media_observer_, OnMediaRequestStateChanged(
-                                    _, _, _, _, MEDIA_DESKTOP_VIDEO_CAPTURE,
+                                    _, _, _, _, MEDIA_GUM_TAB_AUDIO_CAPTURE,
                                     MEDIA_REQUEST_STATE_CLOSING));
   EXPECT_CALL(*media_observer_, OnMediaRequestStateChanged(
-                                    _, _, _, _, MEDIA_DESKTOP_AUDIO_CAPTURE,
+                                    _, _, _, _, MEDIA_GUM_TAB_VIDEO_CAPTURE,
+                                    MEDIA_REQUEST_STATE_CLOSING));
+  EXPECT_CALL(*media_observer_, OnMediaRequestStateChanged(
+                                    _, _, _, _, MEDIA_GUM_DESKTOP_VIDEO_CAPTURE,
+                                    MEDIA_REQUEST_STATE_CLOSING));
+  EXPECT_CALL(*media_observer_, OnMediaRequestStateChanged(
+                                    _, _, _, _, MEDIA_GUM_DESKTOP_AUDIO_CAPTURE,
+                                    MEDIA_REQUEST_STATE_CLOSING));
+  EXPECT_CALL(*media_observer_, OnMediaRequestStateChanged(
+                                    _, _, _, _, MEDIA_DISPLAY_VIDEO_CAPTURE,
                                     MEDIA_REQUEST_STATE_CLOSING));
 
   media_stream_manager_->CancelRequest(label1);
diff --git a/content/browser/renderer_host/media/media_stream_ui_proxy_unittest.cc b/content/browser/renderer_host/media/media_stream_ui_proxy_unittest.cc
index 0acf2f42..bd7a391c 100644
--- a/content/browser/renderer_host/media/media_stream_ui_proxy_unittest.cc
+++ b/content/browser/renderer_host/media/media_stream_ui_proxy_unittest.cc
@@ -262,7 +262,7 @@
   std::unique_ptr<MediaStreamRequest> request(new MediaStreamRequest(
       0, 0, 0, GURL("http://origin/"), false, MEDIA_GENERATE_STREAM,
       std::string(), std::string(), MEDIA_NO_SERVICE,
-      MEDIA_DESKTOP_VIDEO_CAPTURE, false));
+      MEDIA_GUM_DESKTOP_VIDEO_CAPTURE, false));
   MediaStreamRequest* request_ptr = request.get();
 
   proxy_->RequestAccess(
diff --git a/content/browser/renderer_host/media/render_frame_audio_input_stream_factory.cc b/content/browser/renderer_host/media/render_frame_audio_input_stream_factory.cc
index a073941..590a9d5b 100644
--- a/content/browser/renderer_host/media/render_frame_audio_input_stream_factory.cc
+++ b/content/browser/renderer_host/media/render_frame_audio_input_stream_factory.cc
@@ -126,9 +126,9 @@
 
   WebContentsMediaCaptureId capture_id;
   if (WebContentsMediaCaptureId::Parse(device.id, &capture_id)) {
-    // For MEDIA_DESKTOP_AUDIO_CAPTURE, the source is selected from picker
-    // window, we do not mute the source audio.
-    // For MEDIA_TAB_AUDIO_CAPTURE, the probable use case is Cast, we mute
+    // For MEDIA_GUM_DESKTOP_AUDIO_CAPTURE, the source is selected from
+    // picker window, we do not mute the source audio. For
+    // MEDIA_GUM_TAB_AUDIO_CAPTURE, the probable use case is Cast, we mute
     // the source audio.
     // TODO(qiangchen): Analyze audio constraints to make a duplicating or
     // diverting decision. It would give web developer more flexibility.
@@ -144,7 +144,7 @@
         render_frame_host_, source_host, audio_params, shared_memory_count,
         capture_id.disable_local_echo, std::move(client));
 
-    if (device.type == MEDIA_DESKTOP_AUDIO_CAPTURE)
+    if (device.type == MEDIA_GUM_DESKTOP_AUDIO_CAPTURE)
       IncrementDesktopCaptureCounter(SYSTEM_LOOPBACK_AUDIO_CAPTURER_CREATED);
   } else {
     factory->CreateInputStream(render_frame_host_, device.id, audio_params,
@@ -153,7 +153,7 @@
 
     // Only count for captures from desktop media picker dialog and system loop
     // back audio.
-    if (device.type == MEDIA_DESKTOP_AUDIO_CAPTURE &&
+    if (device.type == MEDIA_GUM_DESKTOP_AUDIO_CAPTURE &&
         (media::AudioDeviceDescription::IsLoopbackDevice(device.id))) {
       IncrementDesktopCaptureCounter(SYSTEM_LOOPBACK_AUDIO_CAPTURER_CREATED);
     }
diff --git a/content/browser/renderer_host/media/render_frame_audio_input_stream_factory_unittest.cc b/content/browser/renderer_host/media/render_frame_audio_input_stream_factory_unittest.cc
index 0a78c67..94d2156 100644
--- a/content/browser/renderer_host/media/render_frame_audio_input_stream_factory_unittest.cc
+++ b/content/browser/renderer_host/media/render_frame_audio_input_stream_factory_unittest.cc
@@ -217,7 +217,7 @@
   WebContentsMediaCaptureId capture_id(main_frame->GetProcess()->GetID(),
                                        main_frame->GetRoutingID());
   int session_id = audio_input_device_manager()->Open(MediaStreamDevice(
-      MEDIA_TAB_AUDIO_CAPTURE, capture_id.ToString(), kDeviceName));
+      MEDIA_GUM_TAB_AUDIO_CAPTURE, capture_id.ToString(), kDeviceName));
   base::RunLoop().RunUntilIdle();
 
   mojom::RendererAudioInputStreamFactoryClientPtr client;
@@ -242,7 +242,7 @@
   WebContentsMediaCaptureId capture_id(main_frame->GetProcess()->GetID(),
                                        main_frame->GetRoutingID());
   int session_id = audio_input_device_manager()->Open(MediaStreamDevice(
-      MEDIA_TAB_AUDIO_CAPTURE, capture_id.ToString(), kDeviceName));
+      MEDIA_GUM_TAB_AUDIO_CAPTURE, capture_id.ToString(), kDeviceName));
   base::RunLoop().RunUntilIdle();
 
   source_contents.reset();
diff --git a/content/browser/renderer_host/media/video_capture_manager.cc b/content/browser/renderer_host/media/video_capture_manager.cc
index 3cdff16e..e0654b6e 100644
--- a/content/browser/renderer_host/media/video_capture_manager.cc
+++ b/content/browser/renderer_host/media/video_capture_manager.cc
@@ -295,7 +295,7 @@
   DCHECK_EQ(controller, device_start_request_queue_.begin()->controller());
   DCHECK(controller);
 
-  if (controller->stream_type() == MEDIA_DESKTOP_VIDEO_CAPTURE) {
+  if (controller->stream_type() == MEDIA_GUM_DESKTOP_VIDEO_CAPTURE) {
     const media::VideoCaptureSessionId session_id =
         device_start_request_queue_.front().session_id();
     DCHECK(session_id != kFakeSessionId);
@@ -573,7 +573,7 @@
     return;
   }
 
-  DCHECK_EQ(MEDIA_DESKTOP_VIDEO_CAPTURE, existing_device->stream_type());
+  DCHECK_EQ(MEDIA_GUM_DESKTOP_VIDEO_CAPTURE, existing_device->stream_type());
   DesktopMediaID id = DesktopMediaID::Parse(existing_device->device_id());
   if (id.is_null())
     return;
diff --git a/content/browser/session_history_browsertest.cc b/content/browser/session_history_browsertest.cc
index d07ea19..561ac71 100644
--- a/content/browser/session_history_browsertest.cc
+++ b/content/browser/session_history_browsertest.cc
@@ -487,23 +487,15 @@
 
 // http://code.google.com/p/chromium/issues/detail?id=56267
 IN_PROC_BROWSER_TEST_F(SessionHistoryTest, HistoryLength) {
-  int length;
-  ASSERT_TRUE(ExecuteScriptAndExtractInt(
-      shell(), "domAutomationController.send(history.length)", &length));
-  EXPECT_EQ(1, length);
-
+  EXPECT_EQ(1, EvalJs(shell(), "history.length"));
   NavigateToURL(shell(), GetURL("title1.html"));
 
-  ASSERT_TRUE(ExecuteScriptAndExtractInt(
-      shell(), "domAutomationController.send(history.length)", &length));
-  EXPECT_EQ(2, length);
+  EXPECT_EQ(2, EvalJs(shell(), "history.length"));
 
   // Now test that history.length is updated when the navigation is committed.
   NavigateToURL(shell(), GetURL("record_length.html"));
 
-  ASSERT_TRUE(ExecuteScriptAndExtractInt(
-      shell(), "domAutomationController.send(history.length)", &length));
-  EXPECT_EQ(3, length);
+  EXPECT_EQ(3, EvalJs(shell(), "history.length"));
 
   GoBack();
   GoBack();
@@ -511,9 +503,7 @@
   // Ensure history.length is properly truncated.
   NavigateToURL(shell(), GetURL("title2.html"));
 
-  ASSERT_TRUE(ExecuteScriptAndExtractInt(
-      shell(), "domAutomationController.send(history.length)", &length));
-  EXPECT_EQ(2, length);
+  EXPECT_EQ(2, EvalJs(shell(), "history.length"));
 }
 
 // Test that verifies that a cross-process transfer doesn't lose session
@@ -537,8 +527,7 @@
 
   // Submit the form.
   TestNavigationObserver form_post_observer(shell()->web_contents(), 1);
-  EXPECT_TRUE(
-      ExecuteScript(shell(), "document.getElementById('text-form').submit();"));
+  EXPECT_TRUE(ExecJs(shell(), "document.getElementById('text-form').submit()"));
   form_post_observer.Wait();
 
   // Verify that we arrived at the expected, redirected location.
@@ -547,13 +536,9 @@
 
   // Verify that POST body got preserved by 307 redirect.  This expectation
   // comes from: https://tools.ietf.org/html/rfc7231#section-6.4.7
-  std::string body;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      shell(),
-      "window.domAutomationController.send("
-      "document.getElementsByTagName('pre')[0].innerText);",
-      &body));
-  EXPECT_EQ("text=value\n", body);
+  EXPECT_EQ(
+      "text=value\n",
+      EvalJs(shell(), "document.getElementsByTagName('pre')[0].innerText"));
 
   // Navigate to a page from yet another site.
   EXPECT_TRUE(NavigateToURL(shell(), page_to_go_back_from));
@@ -568,13 +553,9 @@
             shell()->web_contents()->GetLastCommittedURL());
 
   // Again verify that POST body got preserved by 307 redirect.
-  std::string body_after_back_navigation;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      shell(),
-      "window.domAutomationController.send("
-      "document.getElementsByTagName('pre')[0].innerText);",
-      &body_after_back_navigation));
-  EXPECT_EQ("text=value\n", body_after_back_navigation);
+  EXPECT_EQ(
+      "text=value\n",
+      EvalJs(shell(), "document.getElementsByTagName('pre')[0].innerText"));
 }
 
 }  // namespace content
diff --git a/content/browser/site_instance_impl.cc b/content/browser/site_instance_impl.cc
index a07abd46..805b407 100644
--- a/content/browser/site_instance_impl.cc
+++ b/content/browser/site_instance_impl.cc
@@ -249,13 +249,15 @@
   if (IsRendererDebugURL(url))
     return false;
 
-  // Any process can host an about:blank URL.  This check avoids a process
-  // transfer for browser-initiated navigations to about:blank in a dedicated
-  // process; without it, IsSuitableHost would consider this process unsuitable
-  // for about:blank when it compares origin locks.  Renderer-initiated
-  // navigations will handle about:blank navigations elsewhere and leave them
-  // in the source SiteInstance, along with about:srcdoc and data:.
-  if (url == url::kAboutBlankURL)
+  // Any process can host an about:blank URL, except the one used for error
+  // pages, which should not commit successful navigations.  This check avoids a
+  // process transfer for browser-initiated navigations to about:blank in a
+  // dedicated process; without it, IsSuitableHost would consider this process
+  // unsuitable for about:blank when it compares origin locks.
+  // Renderer-initiated navigations will handle about:blank navigations
+  // elsewhere and leave them in the source SiteInstance, along with
+  // about:srcdoc and data:.
+  if (url.IsAboutBlank() && site_ != GURL(kUnreachableWebDataURL))
     return false;
 
   // If the site URL is an extension (e.g., for hosted apps or WebUI) but the
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index 81410cb..d7d030b 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -164,12 +164,7 @@
   // else it might miss the message of interest.  See https://crbug.com/518729.
   DOMMessageQueue msg_queue;
 
-  bool success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      sender_ftn,
-      "window.domAutomationController.send(" + post_message_script + ");",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(true, EvalJs(sender_ftn, "(" + post_message_script + ");"));
 
   std::string status;
   while (msg_queue.WaitForMessage(&status)) {
@@ -182,11 +177,7 @@
 // |sender_ftn| frame.  This variable is used in post_message.html to count the
 // number of messages received via postMessage by the current window.
 int GetReceivedMessages(FrameTreeNode* ftn) {
-  int received_messages = 0;
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      ftn, "window.domAutomationController.send(window.receivedMessages);",
-      &received_messages));
-  return received_messages;
+  return EvalJs(ftn, "window.receivedMessages;").ExtractInt();
 }
 
 // Helper function to perform a window.open from the |caller_frame| targeting a
@@ -194,13 +185,8 @@
 void NavigateNamedFrame(const ToRenderFrameHost& caller_frame,
                         const GURL& url,
                         const std::string& name) {
-  bool success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      caller_frame,
-      "window.domAutomationController.send("
-      "    !!window.open('" + url.spec() + "', '" + name + "'));",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(true, EvalJs(caller_frame,
+                         JsReplace("!!window.open($1, $2)", url, name)));
 }
 
 // Helper function to generate a click on the given RenderWidgetHost.  The
@@ -216,20 +202,12 @@
 }
 
 // Retrieve document.origin for the frame |ftn|.
-std::string GetDocumentOrigin(FrameTreeNode* ftn) {
-  std::string origin;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      ftn, "domAutomationController.send(document.origin)", &origin));
-  return origin;
+EvalJsResult GetDocumentOrigin(FrameTreeNode* ftn) {
+  return EvalJs(ftn, "document.origin;");
 }
 
 double GetFrameDeviceScaleFactor(const ToRenderFrameHost& adapter) {
-  double device_scale_factor;
-  const char kGetFrameDeviceScaleFactor[] =
-      "window.domAutomationController.send(window.devicePixelRatio);";
-  EXPECT_TRUE(ExecuteScriptAndExtractDouble(adapter, kGetFrameDeviceScaleFactor,
-                                            &device_scale_factor));
-  return device_scale_factor;
+  return EvalJs(adapter, "window.devicePixelRatio;").ExtractDouble();
 }
 
 class RedirectNotificationObserver : public NotificationObserver {
@@ -2279,6 +2257,42 @@
 #endif
 }
 
+// This test verifies that smooth scrolling works correctly inside nested OOPIFs
+// which are same origin with the parent. Note that since the frame tree has
+// a A(B(A1())) structure, if and A1 and A2 shared the same
+// SmoothScrollSequencer, then this test would time out or at best be flaky with
+// random time outs. See https://crbug.com/865446 for more context.
+IN_PROC_BROWSER_TEST_F(SitePerProcessProgrammaticScrollTest,
+                       SmoothScrollInNestedSameProcessOOPIF) {
+  GURL main_frame(
+      embedded_test_server()->GetURL("a.com", kIframeOutOfViewHTML));
+  GURL child_url_b(
+      embedded_test_server()->GetURL("b.com", kIframeOutOfViewHTML));
+  GURL same_origin(
+      embedded_test_server()->GetURL("a.com", kIframeOutOfViewHTML));
+
+  // This will set up the page frame tree as A(B(A1(A2()))) where A1 is later
+  // asked to scroll the <iframe> element of A2 into view. The important bit
+  // here is that the inner frame A1 is recursively scrolling (smoothly) an
+  // element inside its document into view (A2's origin is irrelevant here).
+  ASSERT_TRUE(NavigateToURL(shell(), main_frame));
+  FrameTreeNode* root = web_contents()->GetFrameTree()->root();
+  WaitForOnLoad(root);
+  NavigateFrameToURL(root->child_at(0), child_url_b);
+  WaitForOnLoad(root->child_at(0));
+  auto* nested_ftn = root->child_at(0)->child_at(0);
+  NavigateFrameToURL(nested_ftn, same_origin);
+  WaitForOnLoad(nested_ftn);
+
+  // *Smoothly* scroll the inner most frame into view.
+  ASSERT_TRUE(ExecuteScript(
+      nested_ftn,
+      "document.querySelector('iframe').scrollIntoView({behavior: 'smooth'})"));
+  WaitForElementVisible(root, kIframeSelector);
+  WaitForElementVisible(root->child_at(0), kIframeSelector);
+  WaitForElementVisible(nested_ftn, kIframeSelector);
+}
+
 // Tests OOPIF rendering by checking that the RWH of the iframe generates
 // OnSwapCompositorFrame message.
 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, CompositorFrameSwapped) {
@@ -3723,17 +3737,14 @@
   // Also note that just comparing clientHeight and scrollHeight of the frame's
   // document will not work.
   auto has_scrollbar = [](RenderFrameHostImpl* rfh) {
-    int client_width;
-    EXPECT_TRUE(ExecuteScriptAndExtractInt(
-        rfh, "window.domAutomationController.send(document.body.clientWidth);",
-        &client_width));
+    int client_width = EvalJs(rfh, "document.body.clientWidth").ExtractInt();
     const int kFrameElementWidth = 200;
     return client_width < kFrameElementWidth;
   };
 
   auto set_scrolling_property = [](RenderFrameHostImpl* parent_rfh,
                                    const std::string& value) {
-    EXPECT_TRUE(ExecuteScript(
+    EXPECT_TRUE(ExecJs(
         parent_rfh,
         base::StringPrintf("document.getElementById('child-1').setAttribute("
                            "    'scrolling', '%s');",
@@ -3780,21 +3791,8 @@
 
   FrameTreeNode* child = root->child_at(0);
 
-  std::string margin_width;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      child,
-      "window.domAutomationController.send("
-      "document.body.getAttribute('marginwidth'));",
-      &margin_width));
-  EXPECT_EQ("10", margin_width);
-
-  std::string margin_height;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      child,
-      "window.domAutomationController.send("
-      "document.body.getAttribute('marginheight'));",
-      &margin_height));
-  EXPECT_EQ("50", margin_height);
+  EXPECT_EQ("10", EvalJs(child, "document.body.getAttribute('marginwidth');"));
+  EXPECT_EQ("50", EvalJs(child, "document.body.getAttribute('marginheight');"));
 
   // Run the test over variety of parent/child cases.
   GURL urls[] = {// Remote to remote.
@@ -3812,34 +3810,19 @@
   // correctly after the navigation has completed.
   for (size_t i = 0; i < arraysize(urls); ++i) {
     // Change marginwidth and marginheight before navigating.
-    EXPECT_TRUE(ExecuteScript(
+    EXPECT_TRUE(ExecJs(
         root,
-        base::StringPrintf("document.getElementById('child-1').setAttribute("
-                           "    'marginwidth', '%d');",
-                           current_margin_width)));
-    EXPECT_TRUE(ExecuteScript(
-        root,
-        base::StringPrintf("document.getElementById('child-1').setAttribute("
-                           "    'marginheight', '%d');",
-                           current_margin_height)));
+        base::StringPrintf("var child = document.getElementById('child-1');"
+                           "child.setAttribute('marginwidth', '%d');"
+                           "child.setAttribute('marginheight', '%d');",
+                           current_margin_width, current_margin_height)));
 
     NavigateFrameToURL(child, urls[i]);
 
-    std::string actual_margin_width;
-    EXPECT_TRUE(ExecuteScriptAndExtractString(
-        child,
-        "window.domAutomationController.send("
-        "document.body.getAttribute('marginwidth'));",
-        &actual_margin_width));
-    EXPECT_EQ(base::IntToString(current_margin_width), actual_margin_width);
-
-    std::string actual_margin_height;
-    EXPECT_TRUE(ExecuteScriptAndExtractString(
-        child,
-        "window.domAutomationController.send("
-        "document.body.getAttribute('marginheight'));",
-        &actual_margin_height));
-    EXPECT_EQ(base::IntToString(current_margin_height), actual_margin_height);
+    EXPECT_EQ(base::IntToString(current_margin_width),
+              EvalJs(child, "document.body.getAttribute('marginwidth');"));
+    EXPECT_EQ(base::IntToString(current_margin_height),
+              EvalJs(child, "document.body.getAttribute('marginheight');"));
 
     current_margin_width += 5;
     current_margin_height += 10;
@@ -3867,13 +3850,9 @@
 
   FrameTreeNode* child = root->child_at(0);
 
-  std::string csp;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      root,
-      "window.domAutomationController.send("
-      "document.getElementById('child-1').getAttribute('csp'));",
-      &csp));
-  EXPECT_EQ("object-src \'none\'", csp);
+  EXPECT_EQ(
+      "object-src \'none\'",
+      EvalJs(root, "document.getElementById('child-1').getAttribute('csp');"));
 
   // Run the test over variety of parent/child cases.
   GURL urls[] = {// Remote to remote.
@@ -3924,9 +3903,12 @@
       "      C = http://c.com/",
       DepictFrameTree(root));
 
-  std::string a_origin = embedded_test_server()->GetURL("a.com", "/").spec();
-  std::string b_origin = embedded_test_server()->GetURL("b.com", "/").spec();
-  std::string c_origin = embedded_test_server()->GetURL("c.com", "/").spec();
+  url::Origin a_origin =
+      url::Origin::Create(embedded_test_server()->GetURL("a.com", "/"));
+  url::Origin b_origin =
+      url::Origin::Create(embedded_test_server()->GetURL("b.com", "/"));
+  url::Origin c_origin =
+      url::Origin::Create(embedded_test_server()->GetURL("c.com", "/"));
   FrameTreeNode* tiptop_child = root->child_at(0);
   FrameTreeNode* middle_child = root->child_at(0)->child_at(0);
   FrameTreeNode* lowest_child = root->child_at(0)->child_at(0)->child_at(0);
@@ -3935,18 +3917,8 @@
   // origin for the parent.  The origin should have been replicated as part of
   // the mojom::Renderer::CreateView message that created the parent's
   // RenderFrameProxy in b.com's process.
-  int ancestor_origins_length = 0;
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      tiptop_child,
-      "window.domAutomationController.send(location.ancestorOrigins.length);",
-      &ancestor_origins_length));
-  EXPECT_EQ(1, ancestor_origins_length);
-  std::string result;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      tiptop_child,
-      "window.domAutomationController.send(location.ancestorOrigins[0]);",
-      &result));
-  EXPECT_EQ(a_origin, result + "/");
+  EXPECT_EQ(ListValueOf(a_origin),
+            EvalJs(tiptop_child, "Array.from(location.ancestorOrigins);"));
 
   // Check that c.com frame's location.ancestorOrigins contains the correct
   // origin for its two ancestors. The topmost parent origin should be
@@ -3954,44 +3926,13 @@
   // (b.com's) origin should be replicated as part of
   // mojom::Renderer::CreateFrameProxy sent for b.com's frame in c.com's
   // process.
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      middle_child,
-      "window.domAutomationController.send(location.ancestorOrigins.length);",
-      &ancestor_origins_length));
-  EXPECT_EQ(2, ancestor_origins_length);
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      middle_child,
-      "window.domAutomationController.send(location.ancestorOrigins[0]);",
-      &result));
-  EXPECT_EQ(b_origin, result + "/");
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      middle_child,
-      "window.domAutomationController.send(location.ancestorOrigins[1]);",
-      &result));
-  EXPECT_EQ(a_origin, result + "/");
+  EXPECT_EQ(ListValueOf(b_origin, a_origin),
+            EvalJs(middle_child, "Array.from(location.ancestorOrigins);"));
 
   // Check that the nested a.com frame's location.ancestorOrigins contains the
   // correct origin for its three ancestors.
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      lowest_child,
-      "window.domAutomationController.send(location.ancestorOrigins.length);",
-      &ancestor_origins_length));
-  EXPECT_EQ(3, ancestor_origins_length);
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      lowest_child,
-      "window.domAutomationController.send(location.ancestorOrigins[0]);",
-      &result));
-  EXPECT_EQ(c_origin, result + "/");
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      lowest_child,
-      "window.domAutomationController.send(location.ancestorOrigins[1]);",
-      &result));
-  EXPECT_EQ(b_origin, result + "/");
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      lowest_child,
-      "window.domAutomationController.send(location.ancestorOrigins[2]);",
-      &result));
-  EXPECT_EQ(a_origin, result + "/");
+  EXPECT_EQ(ListValueOf(c_origin, b_origin, a_origin),
+            EvalJs(lowest_child, "Array.from(location.ancestorOrigins);"));
 }
 
 // Test that HasReceivedUserGesture and HasReceivedUserGestureBeforeNavigation
@@ -4078,6 +4019,7 @@
 // Check that iframe sandbox flags are replicated correctly.
 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, SandboxFlagsReplication) {
   GURL main_url(embedded_test_server()->GetURL("/sandboxed_frames.html"));
+  const url::Origin main_origin = url::Origin::Create(main_url);
   EXPECT_TRUE(NavigateToURL(shell(), main_url));
 
   // It is safe to obtain the root frame tree node here, as it doesn't change.
@@ -4104,72 +4046,34 @@
   EXPECT_EQ(bar_url, observer.last_navigation_url());
 
   // Opening a popup in the sandboxed foo.com iframe should fail.
-  bool success = false;
-  EXPECT_TRUE(
-      ExecuteScriptAndExtractBool(root->child_at(1),
-                                  "window.domAutomationController.send("
-                                  "!window.open('data:text/html,dataurl'));",
-                                  &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(false, EvalJs(root->child_at(1),
+                          "!!window.open('data:text/html,dataurl');"));
   EXPECT_EQ(1u, Shell::windows().size());
 
   // Opening a popup in a frame whose parent is sandboxed should also fail.
   // Here, bar.com frame's sandboxed parent frame is a remote frame in
   // bar.com's process.
-  success = false;
-  EXPECT_TRUE(
-      ExecuteScriptAndExtractBool(root->child_at(1)->child_at(0),
-                                  "window.domAutomationController.send("
-                                  "!window.open('data:text/html,dataurl'));",
-                                  &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(false, EvalJs(root->child_at(1)->child_at(0),
+                          "!!window.open('data:text/html,dataurl');"));
   EXPECT_EQ(1u, Shell::windows().size());
 
   // Same, but now try the case where bar.com frame's sandboxed parent is a
   // local frame in bar.com's process.
-  success = false;
-  EXPECT_TRUE(
-      ExecuteScriptAndExtractBool(root->child_at(2)->child_at(0),
-                                  "window.domAutomationController.send("
-                                  "!window.open('data:text/html,dataurl'));",
-                                  &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(false, EvalJs(root->child_at(2)->child_at(0),
+                          "!!window.open('data:text/html,dataurl');"));
   EXPECT_EQ(1u, Shell::windows().size());
 
   // Check that foo.com frame's location.ancestorOrigins contains the correct
   // origin for the parent, which should be unaffected by sandboxing.
-  int ancestor_origins_length = 0;
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      root->child_at(1),
-      "window.domAutomationController.send(location.ancestorOrigins.length);",
-      &ancestor_origins_length));
-  EXPECT_EQ(1, ancestor_origins_length);
-  std::string result;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      root->child_at(1),
-      "window.domAutomationController.send(location.ancestorOrigins[0]);",
-      &result));
-  EXPECT_EQ(result + "/", main_url.GetOrigin().spec());
+  EXPECT_EQ(ListValueOf(main_origin),
+            EvalJs(root->child_at(1), "Array.from(location.ancestorOrigins);"));
 
   // Now check location.ancestorOrigins for the bar.com frame. The middle frame
   // (foo.com's) origin should be unique, since that frame is sandboxed, and
   // the top frame should match |main_url|.
-  FrameTreeNode* bottom_child = root->child_at(1)->child_at(0);
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      bottom_child,
-      "window.domAutomationController.send(location.ancestorOrigins.length);",
-      &ancestor_origins_length));
-  EXPECT_EQ(2, ancestor_origins_length);
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      bottom_child,
-      "window.domAutomationController.send(location.ancestorOrigins[0]);",
-      &result));
-  EXPECT_EQ("null", result);
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      bottom_child,
-      "window.domAutomationController.send(location.ancestorOrigins[1]);",
-      &result));
-  EXPECT_EQ(main_url.GetOrigin().spec(), result + "/");
+  EXPECT_EQ(ListValueOf("null", main_origin),
+            EvalJs(root->child_at(1)->child_at(0),
+                   "Array.from(location.ancestorOrigins);"));
 }
 
 // Check that dynamic updates to iframe sandbox flags are propagated correctly.
@@ -4250,13 +4154,8 @@
             root->child_at(0)->effective_frame_policy().sandbox_flags);
 
   // Opening a popup in the now-sandboxed frame should fail.
-  bool success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      root->child_at(0),
-      "window.domAutomationController.send("
-      "    !window.open('data:text/html,dataurl'));",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(false, EvalJs(root->child_at(0),
+                          "!!window.open('data:text/html,dataurl');"));
   EXPECT_EQ(1u, Shell::windows().size());
 
   // Navigate the child of the now-sandboxed frame to a page on baz.com.  The
@@ -4280,13 +4179,8 @@
       DepictFrameTree(root));
 
   // Opening a popup in the child of a sandboxed frame should fail.
-  success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      root->child_at(0)->child_at(0),
-      "window.domAutomationController.send("
-      "    !window.open('data:text/html,dataurl'));",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(false, EvalJs(root->child_at(0)->child_at(0),
+                          "!!window.open('data:text/html,dataurl');"));
   EXPECT_EQ(1u, Shell::windows().size());
 
   // Child of a sandboxed frame should also be sandboxed on the browser side.
@@ -4344,24 +4238,14 @@
             root->child_at(1)->effective_frame_policy().sandbox_flags);
 
   // Opening a popup in the sandboxed second frame should fail.
-  bool success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      root->child_at(1),
-      "window.domAutomationController.send("
-      "    !window.open('data:text/html,dataurl'));",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(false, EvalJs(root->child_at(1),
+                          "!!window.open('data:text/html,dataurl');"));
   EXPECT_EQ(1u, Shell::windows().size());
 
   // Make sure that the child frame inherits the sandbox flags of its
   // now-sandboxed parent frame.
-  success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      root->child_at(1)->child_at(0),
-      "window.domAutomationController.send("
-      "    !window.open('data:text/html,dataurl'));",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(false, EvalJs(root->child_at(1)->child_at(0),
+                          "!!window.open('data:text/html,dataurl');"));
   EXPECT_EQ(1u, Shell::windows().size());
 }
 
@@ -4423,13 +4307,8 @@
             root->child_at(0)->effective_frame_policy().sandbox_flags);
 
   // Opening a popup in the now-sandboxed frame should fail.
-  bool success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      root->child_at(0),
-      "window.domAutomationController.send("
-      "    !window.open('data:text/html,dataurl'));",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(false, EvalJs(root->child_at(0),
+                          "!!window.open('data:text/html,dataurl');"));
   EXPECT_EQ(1u, Shell::windows().size());
 }
 
@@ -4491,18 +4370,8 @@
 
   // Use location.ancestorOrigins to check that the grandchild on baz.com sees
   // correct origin for its parent.
-  int ancestor_origins_length = 0;
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      bottom_child,
-      "window.domAutomationController.send(location.ancestorOrigins.length);",
-      &ancestor_origins_length));
-  EXPECT_EQ(2, ancestor_origins_length);
-  std::string parent_origin;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      bottom_child,
-      "window.domAutomationController.send(location.ancestorOrigins[0]);",
-      &parent_origin));
-  EXPECT_EQ(main_url.GetOrigin().spec(), parent_origin + "/");
+  EXPECT_EQ(ListValueOf(url::Origin::Create(main_url)),
+            EvalJs(bottom_child, "Array.from(location.ancestorOrigins);"));
 
   // Check that the sandbox flags in the browser process are correct.
   // "allow-scripts" resets both WebSandboxFlags::Scripts and
@@ -4518,13 +4387,8 @@
   // should not be able to create popups.
   EXPECT_EQ(expected_flags,
             bottom_child->effective_frame_policy().sandbox_flags);
-  bool success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      bottom_child,
-      "window.domAutomationController.send("
-      "    !window.open('data:text/html,dataurl'));",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(false,
+            EvalJs(bottom_child, "!!window.open('data:text/html,dataurl')"));
   EXPECT_EQ(1u, Shell::windows().size());
 }
 
@@ -4551,11 +4415,7 @@
 
   // Check that the window.name seen by the frame matches the name attribute
   // specified by its parent in the iframe tag.
-  std::string result;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      root->child_at(0), "window.domAutomationController.send(window.name);",
-      &result));
-  EXPECT_EQ("3-1-name", result);
+  EXPECT_EQ("3-1-name", EvalJs(root->child_at(0), "window.name;"));
 }
 
 // Verify that dynamic updates to a frame's window.name propagate to the
@@ -4589,26 +4449,15 @@
   // The proxy in the parent process should also receive the updated name.
   // Now iframe's name and the content window's name differ, so it shouldn't
   // be possible to access to the content window with the updated name.
-  bool success = false;
-  EXPECT_TRUE(
-      ExecuteScriptAndExtractBool(shell(),
-                                  "window.domAutomationController.send("
-                                  "    frames['updated-name'] === undefined);",
-                                  &success));
-  // TODO(yukishiino): The following expectation should be TRUE, but we're
+  //
+  // TODO(yukishiino): The following expectation should be |true|, but we're
   // intentionally disabling the name and origin check of the named access on
   // window.  See also crbug.com/538562 and crbug.com/701489.
-  EXPECT_FALSE(success);
+  EXPECT_EQ(false, EvalJs(shell(), "frames['updated-name'] === undefined;"));
   // Change iframe's name to match the content window's name so that it can
   // reference the child frame by its new name in case of cross origin.
   EXPECT_TRUE(ExecuteScript(root, "window['3-1-id'].name = 'updated-name';"));
-  success = false;
-  EXPECT_TRUE(
-      ExecuteScriptAndExtractBool(shell(),
-                                  "window.domAutomationController.send("
-                                  "    frames['updated-name'] == frames[0]);",
-                                  &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(true, EvalJs(shell(), "frames['updated-name'] == frames[0];"));
 
   // Issue a renderer-initiated navigation from the root frame to the child
   // frame using the frame's name. Make sure correct frame is navigated.
@@ -5034,26 +4883,9 @@
       DepictFrameTree(root));
 
   // Check that each subframe sees itself at correct index in parent.frames.
-  bool success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      child0,
-      "window.domAutomationController.send(window === parent.frames[0]);",
-      &success));
-  EXPECT_TRUE(success);
-
-  success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      child1,
-      "window.domAutomationController.send(window === parent.frames[1]);",
-      &success));
-  EXPECT_TRUE(success);
-
-  success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      child2,
-      "window.domAutomationController.send(window === parent.frames[2]);",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(true, EvalJs(child0, "window === parent.frames[0];"));
+  EXPECT_EQ(true, EvalJs(child1, "window === parent.frames[1];"));
+  EXPECT_EQ(true, EvalJs(child2, "window === parent.frames[2];"));
 
   // Send a postMessage from B to parent.frames[1], which should go to C, and
   // wait for reply.
@@ -5168,12 +5000,8 @@
           ->root();
   EXPECT_EQ(root->child_at(0), popup_root->opener());
 
-  std::string opener_url;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      popup_root,
-      "window.domAutomationController.send(window.opener.location.href);",
-      &opener_url));
-  EXPECT_EQ(frame_url.spec(), opener_url);
+  EXPECT_EQ(frame_url.spec(),
+            EvalJs(popup_root, "window.opener.location.href;"));
 
   // Now try the same with a cross-site popup and make sure it ends up in a new
   // process and with a correct opener.
@@ -5196,13 +5024,8 @@
 
   // Ensure the popup's window.opener points to the right subframe.  Note that
   // we can't check the opener's location as above since it's cross-origin.
-  bool success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      cross_site_popup_root,
-      "window.domAutomationController.send("
-      "    window.opener === window.opener.top.frames[0]);",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(true, EvalJs(cross_site_popup_root,
+                         "window.opener === window.opener.top.frames[0];"));
 }
 
 // Test that cross-process popups can't be navigated to disallowed URLs by
@@ -5380,45 +5203,27 @@
 
   // Update the popup's opener to the second subframe on the main page (which
   // is same-origin with the top frame, i.e., foo.com).
-  bool success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      root->child_at(1),
-      "window.domAutomationController.send(!!window.open('','popup'));",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(true, EvalJs(root->child_at(1), "!!window.open('','popup');"));
 
   // Check that updated opener propagated to the browser process and the
   // popup's bar.com process.
   EXPECT_EQ(root->child_at(1), popup_root->opener());
 
-  success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      popup_shell,
-      "window.domAutomationController.send("
-      "    window.opener === window.opener.parent.frames['frame2']);",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(true,
+            EvalJs(popup_shell,
+                   "window.opener === window.opener.parent.frames['frame2'];"));
 
   // Now update opener on the popup's second subframe (foo.com) to the main
   // page's first subframe (bar.com).
-  success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      root->child_at(0),
-      "window.domAutomationController.send(!!window.open('','subframe2'));",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(true, EvalJs(root->child_at(0), "!!window.open('','subframe2');"));
 
   // Check that updated opener propagated to the browser process and the
   // foo.com process.
   EXPECT_EQ(root->child_at(0), popup_root->child_at(1)->opener());
 
-  success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      popup_root->child_at(1),
-      "window.domAutomationController.send("
-      "    window.opener === window.opener.parent.frames['frame1']);",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(true,
+            EvalJs(popup_root->child_at(1),
+                   "window.opener === window.opener.parent.frames['frame1'];"));
 }
 
 // Check that when a subframe navigates to a new SiteInstance, the new
@@ -5452,19 +5257,12 @@
 
   // Check that the new subframe process still sees correct opener for its
   // parent by sending a postMessage to subframe's parent.opener.
-  bool success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      popup_root->child_at(0),
-      "window.domAutomationController.send(!!parent.opener);", &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(true, EvalJs(popup_root->child_at(0), "!!parent.opener;"));
 
   base::string16 expected_title = base::ASCIIToUTF16("msg");
   TitleWatcher title_watcher(shell()->web_contents(), expected_title);
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      popup_root->child_at(0),
-      "window.domAutomationController.send(postToOpenerOfParent('msg','*'));",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(true, EvalJs(popup_root->child_at(0),
+                         "postToOpenerOfParent('msg','*');"));
   EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
 }
 
@@ -5485,47 +5283,30 @@
       DepictFrameTree(root));
 
   // Update the first (cross-site) subframe's opener to root frame.
-  bool success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      root, "window.domAutomationController.send(!!window.open('','frame1'));",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(true, EvalJs(root, "!!window.open('','frame1');"));
 
   // Check that updated opener propagated to the browser process and subframe's
   // process.
   EXPECT_EQ(root, root->child_at(0)->opener());
 
-  success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      root->child_at(0),
-      "window.domAutomationController.send(window.opener === window.parent);",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(true,
+            EvalJs(root->child_at(0), "window.opener === window.parent;"));
 
   // Navigate the subframe with opener to another site.
   GURL frame_url(embedded_test_server()->GetURL("baz.com", "/title1.html"));
   NavigateFrameToURL(root->child_at(0), frame_url);
 
   // Check that the subframe still sees correct opener in its new process.
-  success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      root->child_at(0),
-      "window.domAutomationController.send(window.opener === window.parent);",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(true,
+            EvalJs(root->child_at(0), "window.opener === window.parent;"));
 
   // Navigate second subframe to a new site.  Check that the proxy that's
   // created for the first subframe in the new SiteInstance has correct opener.
   GURL frame2_url(embedded_test_server()->GetURL("qux.com", "/title1.html"));
   NavigateFrameToURL(root->child_at(1), frame2_url);
 
-  success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      root->child_at(1),
-      "window.domAutomationController.send("
-      "    parent.frames['frame1'].opener === parent);",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(true, EvalJs(root->child_at(1),
+                         "parent.frames['frame1'].opener === parent;"));
 }
 
 // Check that if a subframe has an opener, that opener is preserved when a new
@@ -5561,24 +5342,15 @@
   // Update the popup's second subframe's opener to root frame.  This is
   // allowed because that subframe is in the same foo.com SiteInstance as the
   // root frame.
-  bool success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      root,
-      "window.domAutomationController.send(!!window.open('','subframe2'));",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(true, EvalJs(root, "!!window.open('','subframe2');"));
 
   // Check that the opener update propagated to the browser process and bar.com
   // process.
   EXPECT_EQ(root, popup_root->child_at(1)->opener());
-  success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      popup_root->child_at(0),
-      "window.domAutomationController.send("
-      "    parent.frames['subframe2'].opener && "
-      "        parent.frames['subframe2'].opener === parent.opener);",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(true,
+            EvalJs(popup_root->child_at(0),
+                   "parent.frames['subframe2'].opener && "
+                   "    parent.frames['subframe2'].opener === parent.opener;"));
 
   // Navigate the popup's first subframe to another site.
   GURL frame_url(
@@ -5587,23 +5359,15 @@
 
   // Check that the second subframe's opener is still correct in the first
   // subframe's new process.  Verify it both in JS and with a postMessage.
-  success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      popup_root->child_at(0),
-      "window.domAutomationController.send("
-      "    parent.frames['subframe2'].opener && "
-      "        parent.frames['subframe2'].opener === parent.opener);",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(true,
+            EvalJs(popup_root->child_at(0),
+                   "parent.frames['subframe2'].opener && "
+                   "    parent.frames['subframe2'].opener === parent.opener;"));
 
   base::string16 expected_title = base::ASCIIToUTF16("msg");
   TitleWatcher title_watcher(shell()->web_contents(), expected_title);
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      popup_root->child_at(0),
-      "window.domAutomationController.send("
-      "    postToOpenerOfSibling('subframe2', 'msg', '*'));",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(true, EvalJs(popup_root->child_at(0),
+                         "postToOpenerOfSibling('subframe2', 'msg', '*');"));
   EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
 }
 
@@ -5811,11 +5575,8 @@
                                            const std::string& property,
                                            const std::string& expected_value) {
     std::string script = base::StringPrintf(
-        "window.domAutomationController.send(document.activeElement.%s);",
-        property.c_str());
-    std::string result;
-    EXPECT_TRUE(ExecuteScriptAndExtractString(rfh, script, &result));
-    EXPECT_EQ(expected_value, base::ToLowerASCII(result));
+        "document.activeElement.%s.toLowerCase();", property.c_str());
+    EXPECT_EQ(expected_value, EvalJs(rfh, script));
   };
 
   // Verify that document.activeElement on main frame points to the <iframe>
@@ -5873,17 +5634,33 @@
   // The main frame should be focused to start with.
   EXPECT_EQ(root, root->frame_tree()->GetFocusedFrame());
 
-  DOMMessageQueue msg_queue;
-
   // Register focus and blur events that will send messages when each frame's
-  // window gets or loses focus.
-  const char kSetupFocusEvents[] =
-      "window.addEventListener('focus', function() {"
-      "  domAutomationController.send('%s-got-focus');"
-      "});"
-      "window.addEventListener('blur', function() {"
-      "  domAutomationController.send('%s-lost-focus');"
-      "});";
+  // window gets or loses focus, and configure some utility functions useful for
+  // waiting for these messages.
+  const char kSetupFocusEvents[] = R"(
+      window.addEventListener('focus', function() {
+        window.top.postMessage('%s-got-focus', '*');
+      });
+      window.addEventListener('blur', function() {
+        window.top.postMessage('%s-lost-focus', '*');
+      });
+      function onEvent(target, eventName, property, value) {
+        return new Promise((resolve, reject) => {
+          function listener(event) {
+            if (event[property] == value) {
+              resolve();
+              target.removeEventListener(eventName, listener);
+            }
+          };
+          target.addEventListener(eventName, listener);
+        });
+      }
+      function expectMessages(messageList) {
+        var promiseList = messageList.map(
+            (dataValue) => onEvent(window, 'message', 'data', dataValue));
+        return Promise.all(promiseList);
+      }
+  )";
   std::string script = base::StringPrintf(kSetupFocusEvents, "main", "main");
   ExecuteScriptAsync(shell(), script);
   script = base::StringPrintf(kSetupFocusEvents, "child1", "child1");
@@ -5892,51 +5669,52 @@
   ExecuteScriptAsync(child2, script);
 
   // Execute window.focus on the B subframe from the A main frame.
-  ExecuteScriptAsync(root, "frames[0].focus()");
-
-  // Helper to wait for two specified messages to arrive on the specified
-  // DOMMessageQueue, assuming that the two messages can arrive in any order.
-  auto wait_for_two_messages = [](DOMMessageQueue* msg_queue,
-                                  const std::string& msg1,
-                                  const std::string& msg2) {
-    bool msg1_received = false;
-    bool msg2_received = false;
-    std::string status;
-    while (msg_queue->WaitForMessage(&status)) {
-      if (status == msg1)
-        msg1_received = true;
-      if (status == msg2)
-        msg2_received = true;
-      if (msg1_received && msg2_received)
-        break;
-    }
-  };
-
   // Process A should fire a blur event, and process B should fire a focus
   // event.  Wait for both events.
-  wait_for_two_messages(&msg_queue, "\"main-lost-focus\"",
-                        "\"child1-got-focus\"");
+  EXPECT_EQ(true, EvalJs(root, R"((async function() {
+      allMessages = [];
+      window.addEventListener('message', (event) => {
+        allMessages.push(event.data);
+      });
 
-  // The B subframe should now be focused in the browser process.
+      var messages = expectMessages(['main-lost-focus', 'child1-got-focus']);
+      frames[0].focus();
+      await messages;
+
+      return allMessages.length == 2 || allMessages;
+  })())"));
+
   EXPECT_EQ(child1, root->frame_tree()->GetFocusedFrame());
 
   // Now, execute window.focus on the C subframe from A main frame.  This
   // checks that we can shift focus from one remote frame to another.
-  ExecuteScriptAsync(root, "frames[1].focus()");
-
+  //
   // Wait for the two subframes (B and C) to fire blur and focus events.
-  wait_for_two_messages(&msg_queue, "\"child1-lost-focus\"",
-                        "\"child2-got-focus\"");
+  EXPECT_EQ(true, EvalJs(root, R"((async function() {
+      var messages = expectMessages(['child1-lost-focus', 'child2-got-focus']);
+      frames[1].focus();
+      await messages;
+      return allMessages.length == 4 || allMessages;
+  })())"));
 
   // The C subframe should now be focused.
   EXPECT_EQ(child2, root->frame_tree()->GetFocusedFrame());
 
+  // Install event listeners in the A main frame, expecting the main frame to
+  // obtain focus.
+  EXPECT_TRUE(
+      ExecJs(root,
+             "var messages = "
+             "    expectMessages(['child2-lost-focus', 'main-got-focus']);"));
+
   // window.focus the main frame from the C subframe.
   ExecuteScriptAsync(child2, "parent.focus()");
 
-  // Wait for the C subframe to blur and main frame to focus.
-  wait_for_two_messages(&msg_queue, "\"child2-lost-focus\"",
-                        "\"main-got-focus\"");
+  // Wait for the messages to arrive in the A main frame.
+  EXPECT_EQ(true, EvalJs(root, R"((async function() {
+      await messages;
+      return allMessages.length == 6 || allMessages;
+  })())"));
 
   // The main frame should now be focused.
   EXPECT_EQ(root, root->frame_tree()->GetFocusedFrame());
@@ -6037,10 +5815,7 @@
 
   // Make sure the main frame renderer does not crash and ignores the
   // navigation to the frame that's already been deleted.
-  int child_count = 0;
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      root, "domAutomationController.send(frames.length)", &child_count));
-  EXPECT_EQ(1, child_count);
+  EXPECT_EQ(1, EvalJs(root, "frames.length"));
 }
 
 // Test for a variation of https://crbug.com/526304, where a child frame does a
@@ -6076,10 +5851,7 @@
   EXPECT_EQ(1U, root->child_count());
 
   // Make sure the a.com renderer does not crash.
-  int child_count = 0;
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      root, "domAutomationController.send(frames.length)", &child_count));
-  EXPECT_EQ(1, child_count);
+  EXPECT_EQ(1, EvalJs(root, "frames.length;"));
 }
 
 // Similar to NavigateProxyAndDetachBeforeCommit, but uses a synchronous
@@ -6106,10 +5878,7 @@
   observer.Wait();
 
   // Make sure the a.com renderer does not crash and the frame is removed.
-  int child_count = 0;
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      root, "domAutomationController.send(frames.length)", &child_count));
-  EXPECT_EQ(0, child_count);
+  EXPECT_EQ(0, EvalJs(root, "frames.length;"));
 }
 
 // Test for https://crbug.com/568670.  In A-embed-B, simultaneously have B
@@ -6160,10 +5929,7 @@
   EXPECT_EQ(0U, root->child_count());
 
   // Make sure process A did not crash.
-  int child_count = 0;
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      root, "domAutomationController.send(frames.length)", &child_count));
-  EXPECT_EQ(0, child_count);
+  EXPECT_EQ(0, EvalJs(root, "frames.length;"));
 }
 
 // This test ensures that the RenderFrame isn't leaked in the renderer process
@@ -6718,7 +6484,8 @@
 
   // Check that the grandchild frame isn't sandboxed on the renderer side.  If
   // sandboxed, its origin would be unique ("null").
-  EXPECT_EQ(frame_url.GetOrigin().spec(), GetDocumentOrigin(grandchild) + "/");
+  std::string expected_origin = url::Origin::Create(frame_url).Serialize();
+  EXPECT_EQ(expected_origin, GetDocumentOrigin(grandchild));
 }
 
 // Verify that popups opened from sandboxed frames inherit sandbox flags from
@@ -6864,11 +6631,8 @@
             foo_root->effective_frame_policy().sandbox_flags);
 
   // The popup's origin should match |b_url|, since it's not sandboxed.
-  std::string popup_origin;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      foo_root, "domAutomationController.send(document.origin)",
-      &popup_origin));
-  EXPECT_EQ(b_url.GetOrigin().spec(), popup_origin + "/");
+  EXPECT_EQ(url::Origin::Create(b_url).Serialize(),
+            EvalJs(foo_root, "document.origin;"));
 }
 
 // Tests that the WebContents is notified when passive mixed content is
@@ -7136,11 +6900,7 @@
     // sure that the frame's document.title is empty: this double-checks both
     // that the blocked URL's contents wasn't loaded, and that the old page
     // isn't active anymore (both of these pages have non-empty titles).
-    std::string frame_title;
-    EXPECT_TRUE(ExecuteScriptAndExtractString(
-        root->child_at(0), "domAutomationController.send(document.title)",
-        &frame_title));
-    EXPECT_EQ("", frame_title);
+    EXPECT_EQ("", EvalJs(root->child_at(0), "document.title"));
 
     // Navigate the subframe to another cross-origin page and ensure that this
     // navigation succeeds.  Use a renderer-initiated navigation to test the
@@ -7212,19 +6972,11 @@
 
   // The page should get the title of an error page (i.e "Error") and not the
   // title of the blocked page.
-  std::string frame_title;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      root->child_at(0), "domAutomationController.send(document.title)",
-      &frame_title));
-  EXPECT_EQ("Error", frame_title);
+  EXPECT_EQ("Error", EvalJs(root->child_at(0), "document.title"));
 
   // Navigate to a URL without CSP.
   EXPECT_TRUE(NavigateToURL(
       shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
-
-  // Verify that the frame's CSP got correctly reset to an empty set.
-  EXPECT_EQ(0u,
-            root->current_replication_state().accumulated_csp_headers.size());
 }
 
 // Test that a cross-origin frame's navigation can be blocked by CSP frame-src.
@@ -7244,12 +6996,12 @@
   NavigateFrameToURL(root->child_at(0), old_subframe_url);
 
   // Add frame-src CSP via a new <meta> element.
-  EXPECT_TRUE(ExecuteScript(
-      shell(),
-      "var meta = document.createElement('meta');"
-      "meta.httpEquiv = 'Content-Security-Policy';"
-      "meta.content = 'frame-src https://a.com:*';"
-      "document.getElementsByTagName('head')[0].appendChild(meta);"));
+  EXPECT_TRUE(
+      ExecJs(shell(),
+             "var meta = document.createElement('meta');"
+             "meta.httpEquiv = 'Content-Security-Policy';"
+             "meta.content = 'frame-src https://a.com:*';"
+             "document.getElementsByTagName('head')[0].appendChild(meta);"));
 
   // Sanity-check that the test page has the expected shape for testing.
   // (the CSP should not have an effect on the already loaded frames).
@@ -7261,18 +7013,18 @@
   EXPECT_EQ("frame-src https://a.com:*", root_csp[0].header_value);
 
   // Monitor subframe's load events via main frame's title.
-  EXPECT_TRUE(ExecuteScript(shell(),
-                            "document.querySelector('iframe').onload = "
-                            "    function() { document.title = 'loaded'; };"));
-  EXPECT_TRUE(ExecuteScript(shell(), "document.title = 'not loaded';"));
+  EXPECT_TRUE(ExecJs(shell(),
+                     "document.querySelector('iframe').onload = "
+                     "    function() { document.title = 'loaded'; };"));
+  EXPECT_TRUE(ExecJs(shell(), "document.title = 'not loaded';"));
   base::string16 expected_title(base::UTF8ToUTF16("loaded"));
   TitleWatcher title_watcher(shell()->web_contents(), expected_title);
 
   // Try to navigate the subframe to a blocked URL.
   TestNavigationObserver load_observer2(shell()->web_contents());
   GURL blocked_url = embedded_test_server()->GetURL("c.com", "/title3.html");
-  EXPECT_TRUE(ExecuteScript(root->child_at(0), "window.location.href = '" +
-                                                   blocked_url.spec() + "';"));
+  EXPECT_TRUE(ExecJs(root->child_at(0),
+                     JsReplace("window.location.href = $1;", blocked_url)));
 
   // The blocked frame should still fire a load event in its parent's process.
   EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
@@ -7291,11 +7043,7 @@
 
   // The page should get the title of an error page (i.e "Error") and not the
   // title of the blocked page.
-  std::string frame_title;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      root->child_at(0), "domAutomationController.send(document.title)",
-      &frame_title));
-  EXPECT_EQ("Error", frame_title);
+  EXPECT_EQ("Error", EvalJs(root->child_at(0), "document.title"));
 }
 
 // Test that a cross-origin frame's navigation can be blocked by CSP frame-src.
@@ -7328,24 +7076,23 @@
 
   // Monitor navigating_frame's load events via srcdoc_frame posting
   // a message to the parent frame.
+  EXPECT_TRUE(ExecJs(root,
+                     "window.addEventListener('message', function(event) {"
+                     "  document.title = event.data;"
+                     "});"));
   EXPECT_TRUE(
-      ExecuteScript(root,
-                    "window.addEventListener('message', function(event) {"
-                    "  document.title = event.data;"
-                    "});"));
-  EXPECT_TRUE(ExecuteScript(
-      srcdoc_frame,
-      "document.querySelector('iframe').onload = "
-      "    function() { window.top.postMessage('loaded', '*'); };"));
-  EXPECT_TRUE(ExecuteScript(shell(), "document.title = 'not loaded';"));
+      ExecJs(srcdoc_frame,
+             "document.querySelector('iframe').onload = "
+             "    function() { window.top.postMessage('loaded', '*'); };"));
+  EXPECT_TRUE(ExecJs(shell(), "document.title = 'not loaded';"));
   base::string16 expected_title(base::UTF8ToUTF16("loaded"));
   TitleWatcher title_watcher(shell()->web_contents(), expected_title);
 
   // Try to navigate the subframe to a blocked URL.
   TestNavigationObserver load_observer2(shell()->web_contents());
   GURL blocked_url = embedded_test_server()->GetURL("c.com", "/title3.html");
-  EXPECT_TRUE(ExecuteScript(navigating_frame, "window.location.href = '" +
-                                                  blocked_url.spec() + "';"));
+  EXPECT_TRUE(ExecJs(navigating_frame,
+                     JsReplace("window.location.href = $1;", blocked_url)));
 
   // The blocked frame should still fire a load event in its parent's process.
   EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
@@ -7364,11 +7111,7 @@
 
   // The page should get the title of an error page (i.e "Error") and not the
   // title of the blocked page.
-  std::string frame_title;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      navigating_frame, "domAutomationController.send(document.title)",
-      &frame_title));
-  EXPECT_EQ("Error", frame_title);
+  EXPECT_EQ("Error", EvalJs(navigating_frame, "document.title"));
 
   // Navigate the subframe to a URL without CSP.
   NavigateFrameToURL(srcdoc_frame,
@@ -7392,16 +7135,9 @@
                               "outerHeight"};
 
   for (const char* property : properties) {
-    std::string script = "window.domAutomationController.send(window.";
-    script += property;
-    script += ");";
-    int root_value = 1;
-    int child_value = 2;
-    EXPECT_TRUE(ExecuteScriptAndExtractInt(root, script.c_str(), &root_value));
-
-    EXPECT_TRUE(
-        ExecuteScriptAndExtractInt(child, script.c_str(), &child_value));
-
+    std::string script = base::StringPrintf("window.%s;", property);
+    int root_value = EvalJs(root, script).ExtractInt();
+    int child_value = EvalJs(child, script).ExtractInt();
     EXPECT_EQ(root_value, child_value);
   }
 }
@@ -7668,11 +7404,7 @@
       "      B = http://b.com/",
       DepictFrameTree(root));
 
-  int child_count = 0;
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      root->child_at(0), "window.domAutomationController.send(frames.length);",
-      &child_count));
-  EXPECT_EQ(1, child_count);
+  EXPECT_EQ(1, EvalJs(root->child_at(0), "frames.length;"));
 
   RenderFrameDeletedObserver deleted_observer(
       root->child_at(0)->child_at(0)->current_frame_host());
@@ -7694,10 +7426,7 @@
 
   deleted_observer.WaitUntilDeleted();
 
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      root->child_at(0), "window.domAutomationController.send(frames.length);",
-      &child_count));
-  EXPECT_EQ(0, child_count);
+  EXPECT_EQ(0, EvalJs(root->child_at(0), "frames.length;"));
 
   EXPECT_EQ(
       " Site A ------------ proxies for B\n"
@@ -7929,13 +7658,9 @@
 
   // Also, extract the file from the renderer process to ensure that the
   // response made it over successfully and the proper filename is set.
-  std::string file_name;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      root->child_at(0),
-      "window.domAutomationController.send("
-      "document.getElementById('fileinput').files[0].name);",
-      &file_name));
-  EXPECT_EQ("bar", file_name);
+  EXPECT_EQ("bar",
+            EvalJs(root->child_at(0),
+                   "document.getElementById('fileinput').files[0].name;"));
 }
 
 // Tests that an out-of-process iframe receives the visibilitychange event.
@@ -7955,26 +7680,19 @@
       "      B = http://b.com/",
       DepictFrameTree(root));
 
-  EXPECT_TRUE(ExecuteScript(
-      root->child_at(0)->current_frame_host(),
-      "var event_fired = 0;\n"
-      "document.addEventListener('visibilitychange',\n"
-      "                          function() { event_fired++; });\n"));
+  EXPECT_TRUE(
+      ExecJs(root->child_at(0),
+             "var event_fired = 0;\n"
+             "document.addEventListener('visibilitychange',\n"
+             "                          function() { event_fired++; });\n"));
 
   shell()->web_contents()->WasHidden();
 
-  int event_fired = 0;
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      root->child_at(0)->current_frame_host(),
-      "window.domAutomationController.send(event_fired);", &event_fired));
-  EXPECT_EQ(1, event_fired);
+  EXPECT_EQ(1, EvalJs(root->child_at(0), "event_fired"));
 
   shell()->web_contents()->WasShown();
 
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      root->child_at(0)->current_frame_host(),
-      "window.domAutomationController.send(event_fired);", &event_fired));
-  EXPECT_EQ(2, event_fired);
+  EXPECT_EQ(2, EvalJs(root->child_at(0), "event_fired"));
 }
 
 // Test that the pending RenderFrameHost is canceled and destroyed when its
@@ -8165,12 +7883,7 @@
 
   // Helper to retrieve the history length from a given frame.
   auto history_length = [](FrameTreeNode* ftn) {
-    int history_length = -1;
-    EXPECT_TRUE(ExecuteScriptAndExtractInt(
-        ftn->current_frame_host(),
-        "window.domAutomationController.send(history.length);",
-        &history_length));
-    return history_length;
+    return EvalJs(ftn->current_frame_host(), "history.length;");
   };
 
   // All frames should see a history length of 1 to start with.
@@ -8313,11 +8026,8 @@
       "      B = http://b.com/",
       DepictFrameTree(root));
 
-  int child_count = 0;
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      root->child_at(0)->current_frame_host(),
-      "window.domAutomationController.send(frames.length);", &child_count));
-  EXPECT_EQ(1, child_count);
+  EXPECT_EQ(1,
+            EvalJs(root->child_at(0)->current_frame_host(), "frames.length;"));
 
   // Add an unload handler to B's subframe.
   EXPECT_TRUE(
@@ -8343,10 +8053,8 @@
 
   // Check that C's subframe is alive and the navigation in the unload handler
   // was ignored.
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      root->child_at(0)->child_at(0)->current_frame_host(),
-      "window.domAutomationController.send(frames.length);", &child_count));
-  EXPECT_EQ(0, child_count);
+  EXPECT_EQ(0, EvalJs(root->child_at(0)->child_at(0)->current_frame_host(),
+                      "frames.length;"));
 
   EXPECT_EQ(
       " Site A ------------ proxies for B C\n"
@@ -8703,13 +8411,8 @@
   // feature. If its parent frame's policy was replicated correctly to the
   // proxy, then this will be enabled. Otherwise, it will be disabled, as
   // geolocation is disabled by default in cross-origin frames.
-  bool success = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      root->child_at(1)->child_at(0),
-      "window.domAutomationController.send("
-      "document.policy.allowsFeature('geolocation'));",
-      &success));
-  EXPECT_TRUE(success);
+  EXPECT_EQ(true, EvalJs(root->child_at(1)->child_at(0),
+                         "document.policy.allowsFeature('geolocation')"));
 
   // Now navigate the iframe to a page with no policy, and the same nested
   // cross-site iframe. The policy should be cleared in the proxy.
@@ -8722,13 +8425,8 @@
   // Ask the deepest iframe to report the enabled state of the geolocation
   // feature. If its parent frame's policy was replicated correctly to the
   // proxy, then this will now be disabled.
-  success = true;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      root->child_at(1)->child_at(0),
-      "window.domAutomationController.send("
-      "document.policy.allowsFeature('geolocation'));",
-      &success));
-  EXPECT_FALSE(success);
+  EXPECT_EQ(false, EvalJs(root->child_at(1)->child_at(0),
+                          "document.policy.allowsFeature('geolocation')"));
 }
 
 // Test that the constructed feature policy is correct in sandboxed
@@ -8925,18 +8623,10 @@
 
   // Ensure the subframe is correctly attached in the frame tree, and that it
   // has correct content.
-  int child_count = 0;
-  EXPECT_TRUE(ExecuteScriptAndExtractInt(
-      root, "window.domAutomationController.send(frames.length);",
-      &child_count));
-  EXPECT_EQ(1, child_count);
+  EXPECT_EQ(1, EvalJs(root, "frames.length;"));
 
-  std::string result;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(
-      root,
-      "window.domAutomationController.send(frames[0].document.body.innerText);",
-      &result));
-  EXPECT_EQ("This page has no title.", result);
+  EXPECT_EQ("This page has no title.",
+            EvalJs(root, "frames[0].document.body.innerText;"));
 }
 
 // Tests that trying to open a context menu in the old RFH after commiting a
@@ -9033,12 +8723,7 @@
   // Helper to check if a frame is allowed to go fullscreen on the renderer
   // side.
   auto is_fullscreen_allowed = [](FrameTreeNode* ftn) {
-    bool fullscreen_allowed = false;
-    EXPECT_TRUE(ExecuteScriptAndExtractBool(
-        ftn,
-        "window.domAutomationController.send(document.webkitFullscreenEnabled)",
-        &fullscreen_allowed));
-    return fullscreen_allowed;
+    return EvalJs(ftn, "document.webkitFullscreenEnabled;");
   };
 
   // Load a page with an <iframe> without allowFullscreen.
@@ -9053,13 +8738,13 @@
 
   // No change is expected to the container policy for dynamic modification of
   // a loaded frame.
-  EXPECT_FALSE(is_fullscreen_allowed(root->child_at(0)));
+  EXPECT_EQ(false, is_fullscreen_allowed(root->child_at(0)));
 
   // Cross-site navigation should update the container policy in the new render
   // frame.
   NavigateFrameToURL(root->child_at(0),
                      embedded_test_server()->GetURL("c.com", "/title1.html"));
-  EXPECT_TRUE(is_fullscreen_allowed(root->child_at(0)));
+  EXPECT_EQ(true, is_fullscreen_allowed(root->child_at(0)));
 }
 
 // Test that dynamic updates to iframe sandbox attribute correctly set the
@@ -9357,13 +9042,6 @@
         ->ime_adapter_for_testing();
   }
 
-  std::string GetInputValue(RenderFrameHostImpl* frame) {
-    std::string result;
-    EXPECT_TRUE(ExecuteScriptAndExtractString(
-        frame, "window.domAutomationController.send(input.value);", &result));
-    return result;
-  }
-
   void FocusInputInFrame(RenderFrameHostImpl* frame) {
     ASSERT_TRUE(ExecuteScript(frame, "window.focus(); input.focus();"));
   }
@@ -9566,12 +9244,9 @@
 
   // Verify that POST body was correctly passed to the server and ended up in
   // the body of the page.
-  std::string body;
-  EXPECT_TRUE(ExecuteScriptAndExtractString(root->child_at(0), R"(
-    var body = document.getElementsByTagName('pre')[0].innerText;
-    window.domAutomationController.send(body);)",
-                                            &body));
-  EXPECT_EQ("my_token=my_value\n", body);
+  EXPECT_EQ("my_token=my_value\n",
+            EvalJs(root->child_at(0),
+                   "document.getElementsByTagName('pre')[0].innerText;"));
 }
 
 // Tests that POST method and body is not lost when an OOPIF submits a form
@@ -13071,4 +12746,48 @@
   EXPECT_TRUE(b_process_observer.did_exit_normally());
 }
 
+// Tests that an inner WebContents will reattach to its outer WebContents after
+// a navigation that causes a process swap.
+IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, ProcessSwapOnInnerContents) {
+  GURL main_url(embedded_test_server()->GetURL(
+      "a.com", "/cross_site_iframe_factory.html?a(a)"));
+  EXPECT_TRUE(NavigateToURL(shell(), main_url));
+
+  FrameTreeNode* child_frame =
+      web_contents()->GetFrameTree()->root()->child_at(0);
+  WebContentsImpl* inner_contents =
+      static_cast<WebContentsImpl*>(CreateAndAttachInnerContents(
+          ToRenderFrameHost(child_frame).render_frame_host()));
+  FrameTreeNode* inner_contents_root = inner_contents->GetFrameTree()->root();
+  RenderFrameProxyHost* outer_proxy =
+      inner_contents_root->render_manager()->GetProxyToOuterDelegate();
+  CrossProcessFrameConnector* outer_connector =
+      outer_proxy->cross_process_frame_connector();
+  EXPECT_NE(nullptr, outer_connector->get_view_for_testing());
+
+  GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  NavigateFrameToURL(inner_contents_root, a_url);
+  SiteInstance* a_site_instance =
+      inner_contents->GetMainFrame()->GetSiteInstance();
+  RenderProcessHost* a_process = a_site_instance->GetProcess();
+  RenderWidgetHostViewChildFrame* a_view =
+      outer_connector->get_view_for_testing();
+
+  GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
+  NavigateFrameToURL(inner_contents_root, b_url);
+  SiteInstance* b_site_instance =
+      inner_contents->GetMainFrame()->GetSiteInstance();
+  RenderProcessHost* b_process = b_site_instance->GetProcess();
+  RenderWidgetHostViewChildFrame* b_view =
+      outer_connector->get_view_for_testing();
+
+  // Ensure that the SiteInstances have changed, we've completed a process swap
+  // and reattached the inner WebContents creating a new RenderWidgetHostView.
+  EXPECT_NE(a_site_instance, b_site_instance);
+  EXPECT_NE(a_process, b_process);
+  EXPECT_NE(nullptr, a_view);
+  EXPECT_NE(nullptr, b_view);
+  EXPECT_NE(a_view, b_view);
+}
+
 }  // namespace content
diff --git a/content/browser/tracing/DEPS b/content/browser/tracing/DEPS
index fddb5c8..1c6a061 100644
--- a/content/browser/tracing/DEPS
+++ b/content/browser/tracing/DEPS
@@ -1,7 +1,6 @@
 include_rules = [
   # Generated by the local tracing_resources.gyp:tracing_resources
   "+grit/tracing_resources.h",
-  "+tools/battor_agent",
 ]
 
 specific_include_rules = {
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index d493c2a..df8c751f 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -4908,6 +4908,11 @@
 
   view_->RenderViewHostChanged(old_host, new_host);
 
+  // If this is an inner WebContents that has swapped views, we need to reattach
+  // it to its outer WebContents.
+  if (node_.outer_web_contents())
+    ReattachToOuterWebContentsFrame();
+
   // Ensure that the associated embedder gets cleared after a RenderViewHost
   // gets swapped, so we don't reuse the same embedder next time a
   // RenderViewHost is attached to this WebContents.
diff --git a/content/browser/web_contents/web_contents_impl_browsertest.cc b/content/browser/web_contents/web_contents_impl_browsertest.cc
index 79bb417..6d3dfae2 100644
--- a/content/browser/web_contents/web_contents_impl_browsertest.cc
+++ b/content/browser/web_contents/web_contents_impl_browsertest.cc
@@ -551,8 +551,7 @@
 
   NavigateToURL(shell(), kWebUIUrl);
 
-  bool js_executed = content::ExecuteScript(shell(), kJSCodeForAppendingFrame);
-  EXPECT_TRUE(js_executed);
+  EXPECT_TRUE(content::ExecuteScript(shell(), kJSCodeForAppendingFrame));
 }
 
 // Observer class to track the creation of RenderFrameHost objects. It is used
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index 55cbadc..4731f21a0 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -46,7 +46,6 @@
   WebRuntimeFeatures::EnableNotificationConstructor(false);
   // Android does not yet support switching of audio output devices
   WebRuntimeFeatures::EnableAudioOutputDevices(false);
-  WebRuntimeFeatures::EnableAutoplayMutedVideos(true);
   // Android does not yet support SystemMonitor.
   WebRuntimeFeatures::EnableOnDeviceChange(false);
   WebRuntimeFeatures::EnableMediaSession(true);
@@ -364,11 +363,6 @@
     WebRuntimeFeatures::EnableWebNfc(true);
 #endif
 
-  if (media::GetEffectiveAutoplayPolicy(command_line) !=
-      switches::autoplay::kNoUserGestureRequiredPolicy) {
-    WebRuntimeFeatures::EnableAutoplayMutedVideos(true);
-  }
-
   WebRuntimeFeatures::EnableWebAuth(
       base::FeatureList::IsEnabled(features::kWebAuth));
 
diff --git a/content/common/media/OWNERS b/content/common/media/OWNERS
index be853ae..85d7411 100644
--- a/content/common/media/OWNERS
+++ b/content/common/media/OWNERS
@@ -7,6 +7,9 @@
 per-file *.mojom=set noparent
 per-file *.mojom=file://ipc/SECURITY_OWNERS
 
+per-file *_mojom_traits*.*=set noparent
+per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
+
 per-file *_param_traits*.*=set noparent
 per-file *_param_traits*.*=file://ipc/SECURITY_OWNERS
 
@@ -16,7 +19,4 @@
 per-file *.typemap=set noparent
 per-file *.typemap=file://ipc/SECURITY_OWNERS
 
-per-file *_typemap_traits*.*=set noparent
-per-file *_typemap_traits*.*=file://ipc/SECURITY_OWNERS
-
 # COMPONENT: Internals>Media
diff --git a/content/common/media/media_devices.typemap b/content/common/media/media_devices.typemap
index 2451e202..eaf7eb51 100644
--- a/content/common/media/media_devices.typemap
+++ b/content/common/media/media_devices.typemap
@@ -4,9 +4,9 @@
 
 mojom = "//third_party/blink/public/platform/modules/mediastream/media_devices.mojom"
 public_headers = [ "//content/common/media/media_devices.h" ]
-traits_headers = [ "//content/common/media/media_devices_typemap_traits.h" ]
+traits_headers = [ "//content/common/media/media_devices_mojom_traits.h" ]
 sources = [
-  "//content/common/media/media_devices_typemap_traits.cc",
+  "//content/common/media/media_devices_mojom_traits.cc",
 ]
 type_mappings = [
   "blink.mojom.MediaDeviceType=content::MediaDeviceType",
diff --git a/content/common/media/media_devices_typemap_traits.cc b/content/common/media/media_devices_mojom_traits.cc
similarity index 97%
rename from content/common/media/media_devices_typemap_traits.cc
rename to content/common/media/media_devices_mojom_traits.cc
index 3a46450..ff6b20531 100644
--- a/content/common/media/media_devices_typemap_traits.cc
+++ b/content/common/media/media_devices_mojom_traits.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/common/media/media_devices_typemap_traits.h"
+#include "content/common/media/media_devices_mojom_traits.h"
 
 #include "base/logging.h"
 
diff --git a/content/common/media/media_devices_typemap_traits.h b/content/common/media/media_devices_mojom_traits.h
similarity index 88%
rename from content/common/media/media_devices_typemap_traits.h
rename to content/common/media/media_devices_mojom_traits.h
index b15e800..8274b11 100644
--- a/content/common/media/media_devices_typemap_traits.h
+++ b/content/common/media/media_devices_mojom_traits.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CONTENT_COMMON_MEDIA_MEDIA_DEVICES_TYPEMAP_TRAITS_H_
-#define CONTENT_COMMON_MEDIA_MEDIA_DEVICES_TYPEMAP_TRAITS_H_
+#ifndef CONTENT_COMMON_MEDIA_MEDIA_DEVICES_MOJOM_TRAITS_H_
+#define CONTENT_COMMON_MEDIA_MEDIA_DEVICES_MOJOM_TRAITS_H_
 
 #include "content/common/media/media_devices.h"
 #include "third_party/blink/public/platform/modules/mediastream/media_devices.mojom.h"
@@ -47,4 +47,4 @@
 
 }  // namespace mojo
 
-#endif  // CONTENT_COMMON_MEDIA_MEDIA_DEVICES_TYPEMAP_TRAITS_H_
\ No newline at end of file
+#endif  // CONTENT_COMMON_MEDIA_MEDIA_DEVICES_MOJOM_TRAITS_H_
diff --git a/content/common/media/media_stream.mojom b/content/common/media/media_stream.mojom
index 04b5a935..99faa65 100644
--- a/content/common/media/media_stream.mojom
+++ b/content/common/media/media_stream.mojom
@@ -16,10 +16,11 @@
   MEDIA_NO_SERVICE,
   MEDIA_DEVICE_AUDIO_CAPTURE,
   MEDIA_DEVICE_VIDEO_CAPTURE,
-  MEDIA_TAB_AUDIO_CAPTURE,
-  MEDIA_TAB_VIDEO_CAPTURE,
-  MEDIA_DESKTOP_VIDEO_CAPTURE,
-  MEDIA_DESKTOP_AUDIO_CAPTURE,
+  MEDIA_GUM_TAB_AUDIO_CAPTURE,
+  MEDIA_GUM_TAB_VIDEO_CAPTURE,
+  MEDIA_GUM_DESKTOP_VIDEO_CAPTURE,
+  MEDIA_GUM_DESKTOP_AUDIO_CAPTURE,
+  MEDIA_DISPLAY_VIDEO_CAPTURE,
   NUM_MEDIA_TYPES
 };
 
diff --git a/content/common/media/media_stream.typemap b/content/common/media/media_stream.typemap
index dca61520..eacf4d6 100644
--- a/content/common/media/media_stream.typemap
+++ b/content/common/media/media_stream.typemap
@@ -11,11 +11,11 @@
 
 traits_headers = [
   "//content/common/media/media_stream_param_traits.h",
-  "//content/common/media/media_stream_typemap_traits.h",
+  "//content/common/media/media_stream_mojom_traits.h",
 ]
 
 sources = [
-  "//content/common/media/media_stream_typemap_traits.cc",
+  "//content/common/media/media_stream_mojom_traits.cc",
 ]
 
 deps = [
diff --git a/content/common/media/media_stream_typemap_traits.cc b/content/common/media/media_stream_mojom_traits.cc
similarity index 85%
rename from content/common/media/media_stream_typemap_traits.cc
rename to content/common/media/media_stream_mojom_traits.cc
index 32104659..d88a188 100644
--- a/content/common/media/media_stream_typemap_traits.cc
+++ b/content/common/media/media_stream_mojom_traits.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/common/media/media_stream_typemap_traits.h"
+#include "content/common/media/media_stream_mojom_traits.h"
 
 #include "base/logging.h"
 
@@ -19,14 +19,16 @@
       return content::mojom::MediaStreamType::MEDIA_DEVICE_AUDIO_CAPTURE;
     case content::MediaStreamType::MEDIA_DEVICE_VIDEO_CAPTURE:
       return content::mojom::MediaStreamType::MEDIA_DEVICE_VIDEO_CAPTURE;
-    case content::MediaStreamType::MEDIA_TAB_AUDIO_CAPTURE:
-      return content::mojom::MediaStreamType::MEDIA_TAB_AUDIO_CAPTURE;
-    case content::MediaStreamType::MEDIA_TAB_VIDEO_CAPTURE:
-      return content::mojom::MediaStreamType::MEDIA_TAB_VIDEO_CAPTURE;
-    case content::MediaStreamType::MEDIA_DESKTOP_VIDEO_CAPTURE:
-      return content::mojom::MediaStreamType::MEDIA_DESKTOP_VIDEO_CAPTURE;
-    case content::MediaStreamType::MEDIA_DESKTOP_AUDIO_CAPTURE:
-      return content::mojom::MediaStreamType::MEDIA_DESKTOP_AUDIO_CAPTURE;
+    case content::MediaStreamType::MEDIA_GUM_TAB_AUDIO_CAPTURE:
+      return content::mojom::MediaStreamType::MEDIA_GUM_TAB_AUDIO_CAPTURE;
+    case content::MediaStreamType::MEDIA_GUM_TAB_VIDEO_CAPTURE:
+      return content::mojom::MediaStreamType::MEDIA_GUM_TAB_VIDEO_CAPTURE;
+    case content::MediaStreamType::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE:
+      return content::mojom::MediaStreamType::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE;
+    case content::MediaStreamType::MEDIA_GUM_DESKTOP_AUDIO_CAPTURE:
+      return content::mojom::MediaStreamType::MEDIA_GUM_DESKTOP_AUDIO_CAPTURE;
+    case content::MediaStreamType::MEDIA_DISPLAY_VIDEO_CAPTURE:
+      return content::mojom::MediaStreamType::MEDIA_DISPLAY_VIDEO_CAPTURE;
     case content::MediaStreamType::NUM_MEDIA_TYPES:
       return content::mojom::MediaStreamType::NUM_MEDIA_TYPES;
   }
@@ -48,17 +50,20 @@
     case content::mojom::MediaStreamType::MEDIA_DEVICE_VIDEO_CAPTURE:
       *out = content::MediaStreamType::MEDIA_DEVICE_VIDEO_CAPTURE;
       return true;
-    case content::mojom::MediaStreamType::MEDIA_TAB_AUDIO_CAPTURE:
-      *out = content::MediaStreamType::MEDIA_TAB_AUDIO_CAPTURE;
+    case content::mojom::MediaStreamType::MEDIA_GUM_TAB_AUDIO_CAPTURE:
+      *out = content::MediaStreamType::MEDIA_GUM_TAB_AUDIO_CAPTURE;
       return true;
-    case content::mojom::MediaStreamType::MEDIA_TAB_VIDEO_CAPTURE:
-      *out = content::MediaStreamType::MEDIA_TAB_VIDEO_CAPTURE;
+    case content::mojom::MediaStreamType::MEDIA_GUM_TAB_VIDEO_CAPTURE:
+      *out = content::MediaStreamType::MEDIA_GUM_TAB_VIDEO_CAPTURE;
       return true;
-    case content::mojom::MediaStreamType::MEDIA_DESKTOP_VIDEO_CAPTURE:
-      *out = content::MediaStreamType::MEDIA_DESKTOP_VIDEO_CAPTURE;
+    case content::mojom::MediaStreamType::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE:
+      *out = content::MediaStreamType::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE;
       return true;
-    case content::mojom::MediaStreamType::MEDIA_DESKTOP_AUDIO_CAPTURE:
-      *out = content::MediaStreamType::MEDIA_DESKTOP_AUDIO_CAPTURE;
+    case content::mojom::MediaStreamType::MEDIA_GUM_DESKTOP_AUDIO_CAPTURE:
+      *out = content::MediaStreamType::MEDIA_GUM_DESKTOP_AUDIO_CAPTURE;
+      return true;
+    case content::mojom::MediaStreamType::MEDIA_DISPLAY_VIDEO_CAPTURE:
+      *out = content::MediaStreamType::MEDIA_DISPLAY_VIDEO_CAPTURE;
       return true;
     case content::mojom::MediaStreamType::NUM_MEDIA_TYPES:
       *out = content::MediaStreamType::NUM_MEDIA_TYPES;
diff --git a/content/common/media/media_stream_typemap_traits.h b/content/common/media/media_stream_mojom_traits.h
similarity index 92%
rename from content/common/media/media_stream_typemap_traits.h
rename to content/common/media/media_stream_mojom_traits.h
index 473b57b..69abea7 100644
--- a/content/common/media/media_stream_typemap_traits.h
+++ b/content/common/media/media_stream_mojom_traits.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CONTENT_COMMON_MEDIA_MEDIA_STREAM_TYPEMAP_TRAITS_H_
-#define CONTENT_COMMON_MEDIA_MEDIA_STREAM_TYPEMAP_TRAITS_H_
+#ifndef CONTENT_COMMON_MEDIA_MEDIA_STREAM_MOJOM_TRAITS_H_
+#define CONTENT_COMMON_MEDIA_MEDIA_STREAM_MOJOM_TRAITS_H_
 
 #include "content/common/media/media_stream.mojom.h"
 #include "content/common/media/media_stream_controls.h"
@@ -76,4 +76,4 @@
 
 }  // namespace mojo
 
-#endif  // CONTENT_COMMON_MEDIA_MEDIA_STREAM_TYPEMAP_TRAITS_H_
+#endif  // CONTENT_COMMON_MEDIA_MEDIA_STREAM_MOJOM_TRAITS_H_
diff --git a/content/public/common/media_stream_request.cc b/content/public/common/media_stream_request.cc
index 301f7f100..532cccd 100644
--- a/content/public/common/media_stream_request.cc
+++ b/content/public/common/media_stream_request.cc
@@ -11,20 +11,21 @@
 
 bool IsAudioInputMediaType(MediaStreamType type) {
   return (type == MEDIA_DEVICE_AUDIO_CAPTURE ||
-          type == MEDIA_TAB_AUDIO_CAPTURE ||
-          type == MEDIA_DESKTOP_AUDIO_CAPTURE);
+          type == MEDIA_GUM_TAB_AUDIO_CAPTURE ||
+          type == MEDIA_GUM_DESKTOP_AUDIO_CAPTURE);
 }
 
 bool IsVideoInputMediaType(MediaStreamType type) {
   return (type == MEDIA_DEVICE_VIDEO_CAPTURE ||
-          type == MEDIA_TAB_VIDEO_CAPTURE ||
-          type == MEDIA_DESKTOP_VIDEO_CAPTURE);
+          type == MEDIA_GUM_TAB_VIDEO_CAPTURE ||
+          type == MEDIA_GUM_DESKTOP_VIDEO_CAPTURE);
 }
 
 bool IsScreenCaptureMediaType(MediaStreamType type) {
-  return (type == MEDIA_TAB_AUDIO_CAPTURE || type == MEDIA_TAB_VIDEO_CAPTURE ||
-          type == MEDIA_DESKTOP_AUDIO_CAPTURE ||
-          type == MEDIA_DESKTOP_VIDEO_CAPTURE);
+  return (type == MEDIA_GUM_TAB_AUDIO_CAPTURE ||
+          type == MEDIA_GUM_TAB_VIDEO_CAPTURE ||
+          type == MEDIA_GUM_DESKTOP_AUDIO_CAPTURE ||
+          type == MEDIA_GUM_DESKTOP_VIDEO_CAPTURE);
 }
 
 bool IsDeviceMediaType(MediaStreamType type) {
diff --git a/content/public/common/media_stream_request.h b/content/public/common/media_stream_request.h
index 77d4062..66e1385 100644
--- a/content/public/common/media_stream_request.h
+++ b/content/public/common/media_stream_request.h
@@ -30,15 +30,20 @@
   MEDIA_DEVICE_AUDIO_CAPTURE,
   MEDIA_DEVICE_VIDEO_CAPTURE,
 
+  // Below MEDIA_GUM_* types represent the streams generated by
+  // getUserMedia() calls with constraints that are collected with legacy
+  // APIs for desktop and tab capture.
   // Mirroring of a browser tab.
-  MEDIA_TAB_AUDIO_CAPTURE,
-  MEDIA_TAB_VIDEO_CAPTURE,
-
+  MEDIA_GUM_TAB_AUDIO_CAPTURE,
+  MEDIA_GUM_TAB_VIDEO_CAPTURE,
   // Desktop media sources.
-  MEDIA_DESKTOP_VIDEO_CAPTURE,
-
+  MEDIA_GUM_DESKTOP_VIDEO_CAPTURE,
   // Capture system audio (post-mix loopback stream).
-  MEDIA_DESKTOP_AUDIO_CAPTURE,
+  MEDIA_GUM_DESKTOP_AUDIO_CAPTURE,
+
+  // Enables the capture of a user's display, generated by getDisplayMedia()
+  // call.
+  MEDIA_DISPLAY_VIDEO_CAPTURE,
 
   NUM_MEDIA_TYPES
 };
diff --git a/content/public/test/browser_test_utils.cc b/content/public/test/browser_test_utils.cc
index cac1283..ad2d49a0 100644
--- a/content/public/test/browser_test_utils.cc
+++ b/content/public/test/browser_test_utils.cc
@@ -157,8 +157,9 @@
 
 // Specifying a prototype so that we can add the WARN_UNUSED_RESULT attribute.
 bool ExecuteScriptHelper(RenderFrameHost* render_frame_host,
-                         const std::string& original_script,
+                         const std::string& script,
                          bool user_gesture,
+                         int world_id,
                          std::unique_ptr<base::Value>* result)
     WARN_UNUSED_RESULT;
 
@@ -168,15 +169,23 @@
 bool ExecuteScriptHelper(RenderFrameHost* render_frame_host,
                          const std::string& script,
                          bool user_gesture,
+                         int world_id,
                          std::unique_ptr<base::Value>* result) {
   // TODO(lukasza): Only get messages from the specific |render_frame_host|.
   DOMMessageQueue dom_message_queue(
       WebContents::FromRenderFrameHost(render_frame_host));
-  if (user_gesture) {
-    render_frame_host->ExecuteJavaScriptWithUserGestureForTests(
-        base::UTF8ToUTF16(script));
+  base::string16 script16 = base::UTF8ToUTF16(script);
+  if (world_id == ISOLATED_WORLD_ID_GLOBAL) {
+    if (user_gesture)
+      render_frame_host->ExecuteJavaScriptWithUserGestureForTests(script16);
+    else
+      render_frame_host->ExecuteJavaScriptForTests(script16);
   } else {
-    render_frame_host->ExecuteJavaScriptForTests(base::UTF8ToUTF16(script));
+    // Note that |user_gesture| here is ignored. We allow a value of |true|
+    // because it's the default, but in blink, the execution will occur with
+    // no user gesture.
+    render_frame_host->ExecuteJavaScriptInIsolatedWorld(
+        script16, RenderFrameHost::JavaScriptResultCallback(), world_id);
   }
   std::string json;
   if (!dom_message_queue.WaitForMessage(&json)) {
@@ -208,6 +217,8 @@
   // depend on the message loop pumping done by ExecuteScriptHelper below (this
   // is fragile - these tests should wait on a more specific thing instead).
 
+  // TODO(nick): This function can't be replaced with a call to ExecJs(), since
+  // ExecJs calls eval() which might be blocked by the page's CSP.
   std::string expected_response = "ExecuteScript-" + base::GenerateGUID();
   std::string new_script = base::StringPrintf(
       R"( %s;  // Original script.
@@ -215,7 +226,8 @@
       script.c_str(), expected_response.c_str());
 
   std::unique_ptr<base::Value> value;
-  if (!ExecuteScriptHelper(frame, new_script, user_gesture, &value) ||
+  if (!ExecuteScriptHelper(frame, new_script, user_gesture,
+                           ISOLATED_WORLD_ID_GLOBAL, &value) ||
       !value.get()) {
     return false;
   }
@@ -228,43 +240,6 @@
   return true;
 }
 
-// Specifying a prototype so that we can add the WARN_UNUSED_RESULT attribute.
-bool ExecuteScriptInIsolatedWorldHelper(RenderFrameHost* render_frame_host,
-                                        const int world_id,
-                                        const std::string& original_script,
-                                        std::unique_ptr<base::Value>* result)
-    WARN_UNUSED_RESULT;
-
-bool ExecuteScriptInIsolatedWorldHelper(RenderFrameHost* render_frame_host,
-                                        const int world_id,
-                                        const std::string& script,
-                                        std::unique_ptr<base::Value>* result) {
-  // TODO(lukasza): Only get messages from the specific |render_frame_host|.
-  DOMMessageQueue dom_message_queue(
-      WebContents::FromRenderFrameHost(render_frame_host));
-  render_frame_host->ExecuteJavaScriptInIsolatedWorld(
-      base::UTF8ToUTF16(script),
-      content::RenderFrameHost::JavaScriptResultCallback(), world_id);
-  std::string json;
-  if (!dom_message_queue.WaitForMessage(&json)) {
-    DLOG(ERROR) << "Cannot communicate with DOMMessageQueue.";
-    return false;
-  }
-
-  // Nothing more to do for callers that ignore the returned JS value.
-  if (!result)
-    return true;
-
-  base::JSONReader reader(base::JSON_ALLOW_TRAILING_COMMAS);
-  *result = reader.ReadToValue(json);
-  if (!*result) {
-    DLOG(ERROR) << reader.GetErrorMessage();
-    return false;
-  }
-
-  return true;
-}
-
 void BuildSimpleWebKeyEvent(blink::WebInputEvent::Type type,
                             ui::DomKey key,
                             ui::DomCode code,
@@ -1139,52 +1114,27 @@
                                    const std::string& script, double* result) {
   DCHECK(result);
   std::unique_ptr<base::Value> value;
-  if (!ExecuteScriptHelper(adapter.render_frame_host(), script, true, &value) ||
-      !value.get()) {
-    return false;
-  }
-
-  return value->GetAsDouble(result);
+  return ExecuteScriptHelper(adapter.render_frame_host(), script, true,
+                             ISOLATED_WORLD_ID_GLOBAL, &value) &&
+         value && value->GetAsDouble(result);
 }
 
 bool ExecuteScriptAndExtractInt(const ToRenderFrameHost& adapter,
                                 const std::string& script, int* result) {
   DCHECK(result);
   std::unique_ptr<base::Value> value;
-  if (!ExecuteScriptHelper(adapter.render_frame_host(), script, true, &value) ||
-      !value.get()) {
-    return false;
-  }
-
-  return value->GetAsInteger(result);
+  return ExecuteScriptHelper(adapter.render_frame_host(), script, true,
+                             ISOLATED_WORLD_ID_GLOBAL, &value) &&
+         value && value->GetAsInteger(result);
 }
 
 bool ExecuteScriptAndExtractBool(const ToRenderFrameHost& adapter,
                                  const std::string& script, bool* result) {
   DCHECK(result);
   std::unique_ptr<base::Value> value;
-  if (!ExecuteScriptHelper(adapter.render_frame_host(), script, true, &value) ||
-      !value.get()) {
-    return false;
-  }
-
-  return value->GetAsBoolean(result);
-}
-
-bool ExecuteScriptInIsolatedWorldAndExtractBool(
-    const ToRenderFrameHost& adapter,
-    const int world_id,
-    const std::string& script,
-    bool* result) {
-  DCHECK(result);
-  std::unique_ptr<base::Value> value;
-  if (!ExecuteScriptInIsolatedWorldHelper(adapter.render_frame_host(), world_id,
-                                          script, &value) ||
-      !value.get()) {
-    return false;
-  }
-
-  return value->GetAsBoolean(result);
+  return ExecuteScriptHelper(adapter.render_frame_host(), script, true,
+                             ISOLATED_WORLD_ID_GLOBAL, &value) &&
+         value && value->GetAsBoolean(result);
 }
 
 bool ExecuteScriptAndExtractString(const ToRenderFrameHost& adapter,
@@ -1192,12 +1142,9 @@
                                    std::string* result) {
   DCHECK(result);
   std::unique_ptr<base::Value> value;
-  if (!ExecuteScriptHelper(adapter.render_frame_host(), script, true, &value) ||
-      !value.get()) {
-    return false;
-  }
-
-  return value->GetAsString(result);
+  return ExecuteScriptHelper(adapter.render_frame_host(), script, true,
+                             ISOLATED_WORLD_ID_GLOBAL, &value) &&
+         value && value->GetAsString(result);
 }
 
 bool ExecuteScriptWithoutUserGestureAndExtractDouble(
@@ -1207,7 +1154,7 @@
   DCHECK(result);
   std::unique_ptr<base::Value> value;
   return ExecuteScriptHelper(adapter.render_frame_host(), script, false,
-                             &value) &&
+                             ISOLATED_WORLD_ID_GLOBAL, &value) &&
          value && value->GetAsDouble(result);
 }
 
@@ -1218,7 +1165,7 @@
   DCHECK(result);
   std::unique_ptr<base::Value> value;
   return ExecuteScriptHelper(adapter.render_frame_host(), script, false,
-                             &value) &&
+                             ISOLATED_WORLD_ID_GLOBAL, &value) &&
          value && value->GetAsInteger(result);
 }
 
@@ -1229,7 +1176,7 @@
   DCHECK(result);
   std::unique_ptr<base::Value> value;
   return ExecuteScriptHelper(adapter.render_frame_host(), script, false,
-                             &value) &&
+                             ISOLATED_WORLD_ID_GLOBAL, &value) &&
          value && value->GetAsBoolean(result);
 }
 
@@ -1240,10 +1187,341 @@
   DCHECK(result);
   std::unique_ptr<base::Value> value;
   return ExecuteScriptHelper(adapter.render_frame_host(), script, false,
-                             &value) &&
+                             ISOLATED_WORLD_ID_GLOBAL, &value) &&
          value && value->GetAsString(result);
 }
 
+// EvalJsResult methods.
+EvalJsResult::EvalJsResult(base::Value value, const std::string& error)
+    : value(error.empty() ? std::move(value) : base::Value()), error(error) {}
+
+EvalJsResult::EvalJsResult(const EvalJsResult& other)
+    : value(other.value.Clone()), error(other.error) {}
+
+const std::string& EvalJsResult::ExtractString() const {
+  CHECK(error.empty())
+      << "Can't ExtractString() because the script encountered a problem: "
+      << error;
+  CHECK(value.is_string()) << "Can't ExtractString() because script result: "
+                           << value << "is not a string.";
+  return value.GetString();
+}
+
+int EvalJsResult::ExtractInt() const {
+  CHECK(error.empty())
+      << "Can't ExtractInt() because the script encountered a problem: "
+      << error;
+  CHECK(value.is_int()) << "Can't ExtractInt() because script result: " << value
+                        << "is not an int.";
+  return value.GetInt();
+}
+
+bool EvalJsResult::ExtractBool() const {
+  CHECK(error.empty())
+      << "Can't ExtractBool() because the script encountered a problem: "
+      << error;
+  CHECK(value.is_bool()) << "Can't ExtractBool() because script result: "
+                         << value << "is not a bool.";
+  return value.GetBool();
+}
+
+double EvalJsResult::ExtractDouble() const {
+  CHECK(error.empty())
+      << "Can't ExtractDouble() because the script encountered a problem: "
+      << error;
+  CHECK(value.is_double() || value.is_int())
+      << "Can't ExtractDouble() because script result: " << value
+      << "is not a double or int.";
+  return value.GetDouble();
+}
+
+base::ListValue EvalJsResult::ExtractList() const {
+  CHECK(error.empty())
+      << "Can't ExtractList() because the script encountered a problem: "
+      << error;
+  CHECK(value.is_list()) << "Can't ExtractList() because script result: "
+                         << value << "is not a list.";
+  return base::ListValue(value.GetList());
+}
+
+void PrintTo(const EvalJsResult& bar, ::std::ostream* os) {
+  if (!bar.error.empty()) {
+    *os << bar.error;
+  } else {
+    *os << bar.value;
+  }
+}
+
+namespace {
+
+// Parse a JS stack trace out of |js_error|, detect frames that match
+// |source_name|, and interleave the appropriate lines of source code from
+// |source| into the error report. This is meant to be useful for scripts that
+// are passed to ExecuteScript functions, and hence dynamically generated.
+//
+// An adjustment of |column_adjustment_for_line_one| characters is subtracted
+// when mapping positions from line 1 of |source|. This is to offset the effect
+// of boilerplate added by the script runner.
+//
+// TODO(nick): Elide snippets to 80 chars, since it is common for sources to not
+// include newlines.
+std::string AnnotateAndAdjustJsStackTraces(const std::string& js_error,
+                                           std::string source_name,
+                                           const std::string& source,
+                                           int column_adjustment_for_line_one) {
+  // Escape wildcards in |source_name| for use in MatchPattern.
+  base::ReplaceChars(source_name, "\\", "\\\\", &source_name);
+  base::ReplaceChars(source_name, "*", "\\*", &source_name);
+  base::ReplaceChars(source_name, "?", "\\?", &source_name);
+
+  // This vector maps line numbers to the corresponding text in |source|.
+  const std::vector<base::StringPiece> source_lines = base::SplitStringPiece(
+      source, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+
+  // |source_frame_pattern| should match any line that looks like a stack frame
+  // from a source file named |source_name|.
+  const std::string source_frame_pattern =
+      base::StringPrintf("    at * (%s:*:*)", source_name.c_str());
+
+  // This is the amount of indentation that is applied to the lines of inserted
+  // annotations.
+  const std::string indent(8, ' ');
+  const base::StringPiece elision_mark = "";
+
+  // Loop over each line of |js_error|, and append each to |annotated_error| --
+  // possibly rewriting to include extra context.
+  std::ostringstream annotated_error;
+  for (const base::StringPiece& error_line : base::SplitStringPiece(
+           js_error, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL)) {
+    // Does this look like a stack frame whose URL source matches |source_name|?
+    if (base::MatchPattern(error_line, source_frame_pattern)) {
+      // When a match occurs, annotate the stack trace with the corresponding
+      // line from |source|, along with a ^^^ underneath, indicating the column
+      // position.
+      std::vector<base::StringPiece> error_line_parts = base::SplitStringPiece(
+          error_line, ":", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+      CHECK(error_line_parts.size() >= 2);
+
+      int column_number = 0;
+      base::StringToInt(error_line_parts.back(), &column_number);
+      error_line_parts.pop_back();
+      int line_number = 0;
+      base::StringToInt(error_line_parts.back(), &line_number);
+      error_line_parts.pop_back();
+
+      // Protect against out-of-range matches.
+      if ((line_number > 0) && (column_number > 0) &&
+          static_cast<size_t>(line_number) <= source_lines.size()) {
+        // Apply adjustment requested by caller to columns on the first line.
+        // This allows us to add preamble boilerplate to the script, but still
+        // locate errors correctly.
+        if (line_number == 1 && column_number > column_adjustment_for_line_one)
+          column_number -= column_adjustment_for_line_one;
+
+        // Some source lines are huge. Elide |source_line| so that it doesn't
+        // occupy more than one actual line.
+        std::string source_line = source_lines[line_number - 1].as_string();
+
+        int max_column_number = 60 - indent.length();
+        if (column_number > max_column_number) {
+          source_line = source_line.substr(column_number - max_column_number);
+          column_number = max_column_number;
+          source_line.replace(0, elision_mark.length(), elision_mark.data(),
+                              elision_mark.length());
+        }
+
+        size_t max_length = 80 - indent.length();
+        if (source_line.length() > max_length) {
+          source_line =
+              source_line.substr(0, max_length - elision_mark.length());
+          elision_mark.AppendToString(&source_line);
+        }
+
+        annotated_error << base::JoinString(error_line_parts, ":") << ":"
+                        << line_number << ":" << column_number << "):\n"
+                        << indent << source_line << '\n'
+                        << indent << std::string(column_number - 1, ' ')
+                        << "^^^^^\n";
+        continue;
+      }
+    }
+    // This line was not rewritten -- just append it as-is.
+    annotated_error << error_line << "\n";
+  }
+  return annotated_error.str();
+}
+
+}  // namespace
+
+testing::AssertionResult ExecJs(const ToRenderFrameHost& execution_target,
+                                const std::string& script,
+                                int options,
+                                int world_id) {
+  CHECK(!(options & EXECUTE_SCRIPT_USE_MANUAL_REPLY))
+      << "USE_MANUAL_REPLY does not make sense with ExecJs.";
+
+  // ExecJs() doesn't care about the result, so disable promise resolution.
+  // Instead of using ExecJs() to wait for an async event, callers may use
+  // EvalJs() with a sentinel result value like "success".
+  options |= EXECUTE_SCRIPT_NO_RESOLVE_PROMISES;
+
+  // TODO(nick): Do we care enough about folks shooting themselves in the foot
+  // here with e.g. ASSERT_TRUE(ExecJs("window == window.top")) -- when they
+  // mean EvalJs -- to fail a CHECK() when eval_result.value.is_bool()?
+  EvalJsResult eval_result =
+      EvalJs(execution_target, script, options, world_id);
+
+  // NOTE: |eval_result.value| is intentionally ignored by ExecJs().
+  if (!eval_result.error.empty())
+    return testing::AssertionFailure() << eval_result.error;
+  return testing::AssertionSuccess();
+}
+
+EvalJsResult EvalJs(const ToRenderFrameHost& execution_target,
+                    const std::string& script,
+                    int options,
+                    int world_id) {
+  // The sourceURL= parameter provides a string that replaces <anonymous> in
+  // stack traces, if an Error is thrown. 'std::string' is meant to communicate
+  // that this is a dynamic argument originating from C++ code.
+  const char* kSourceURL = "__const_std::string&_script__";
+  std::string modified_script =
+      base::StringPrintf("%s;\n//# sourceURL=%s", script.c_str(), kSourceURL);
+
+  // An extra eval() indirection is used here to catch syntax errors and return
+  // them as assertion failures. This eval() operation deliberately occurs in
+  // the global scope, so 'var' declarations in |script| will persist for later
+  // script executions. (As an aside: global/local scope for eval depends on
+  // whether 'eval' is called directly or indirectly; 'window.eval()' is
+  // indirect).
+  //
+  // The call to eval() itself is inside a .then() handler so that syntax errors
+  // result in Promise rejection. Calling eval() either throws (in the event of
+  // a SyntaxError) or returns the script's completion value.
+  //
+  // The result of eval() (i.e., the statement completion value of |script|) is
+  // wrapped in an array and passed to a second .then() handler. If eval()
+  // returned a Promise and the |resolve_promises| option is set, this handler
+  // calls Promise.all to reply after the returned Promise resolves.
+  //
+  // If |script| evaluated successfully, the third.then() handler maps the
+  // resolved |result| of eval() to a |reply| that is a one-element list
+  // containing the value (this element can be any JSON-serializable type). If
+  // the manual reply option is being used, no reply is emitted after successful
+  // execution -- the script is expected to call send() itself. The call to
+  // Promise.reject() squelches this reply, and the final .then() handler is not
+  // called.
+  //
+  // If an uncaught error was thrown, or eval() returns a Promise that is
+  // rejected, the third .then() handler maps the |error| to a |reply| that is
+  // a string value.
+  //
+  // The fourth and final .then() handler passes the |reply| (whether
+  // successful or unsuccessful) to domAutomationController.send(), so that it's
+  // transmitted back here in browser process C++ land. A GUID token is also
+  // included, that protects against |script| directly calling
+  // domAutomationController.send() itself, which is disallowed in EvalJs.
+  bool use_automatic_reply = !(options & EXECUTE_SCRIPT_USE_MANUAL_REPLY);
+  bool resolve_promises = !(options & EXECUTE_SCRIPT_NO_RESOLVE_PROMISES);
+
+  std::string token = "EvalJs-" + base::GenerateGUID();
+  std::string runner_script = JsReplace(
+      R"(Promise.resolve($1)
+         .then(script => [window.eval(script)])
+         .then((result) => $2 ? Promise.all(result) : result )
+         .then((result) => $3 ? result : Promise.reject(),
+               (error) => 'a JavaScript error:' +
+                          (error && error.stack ? '\n' + error.stack
+                                                : ' "' + error + '"'))
+         .then((reply) => window.domAutomationController.send([$4, reply]));
+      //# sourceURL=EvalJs-runner.js)",
+      modified_script, resolve_promises, use_automatic_reply, token);
+
+  bool user_gesture = !(options & EXECUTE_SCRIPT_NO_USER_GESTURE);
+  std::ostringstream error_stream;
+  std::unique_ptr<base::Value> response;
+  if (!execution_target.render_frame_host()->IsRenderFrameLive()) {
+    error_stream << "Error: EvalJs won't work on an already-crashed frame.";
+  } else if (!ExecuteScriptHelper(execution_target.render_frame_host(),
+                                  runner_script, user_gesture, world_id,
+                                  &response)) {
+    error_stream << "Internal Error: ExecuteScriptHelper failed";
+  } else if (!response) {
+    error_stream << "Internal Error: no value";
+  } else {
+    bool is_reply_from_runner_script =
+        response->is_list() && response->GetList().size() == 2 &&
+        response->GetList()[0].is_string() &&
+        response->GetList()[0].GetString() == token;
+
+    bool is_error =
+        is_reply_from_runner_script && response->GetList()[1].is_string();
+    bool is_automatic_success_reply =
+        is_reply_from_runner_script && response->GetList()[1].is_list() &&
+        response->GetList()[1].GetList().size() == 1;
+
+    if (is_error) {
+      // This is a response generated by the error handler in our runner
+      // script. This occurs when the script throws an exception, or when
+      // eval throws a SyntaxError.
+      //
+      // Parse the stack trace here, and interleave lines of source code from
+      // |script| to aid debugging.
+      std::string error_text = response->GetList()[1].GetString();
+
+      if (base::StartsWith(error_text,
+                           "a JavaScript error:\nEvalError: Refused",
+                           base::CompareCase::SENSITIVE)) {
+        error_text =
+            "EvalJs encountered an EvalError, because eval() is blocked by the "
+            "document's CSP on this page. To test content that is protected by "
+            "CSP, consider using EvalJs with an isolated world. Details: " +
+            error_text;
+      }
+
+      CHECK(!error_text.empty());
+      error_stream << AnnotateAndAdjustJsStackTraces(error_text, kSourceURL,
+                                                     script, 0);
+    } else if (!use_automatic_reply) {
+      // When |script| itself calls domAutomationController.send() on success,
+      // |response| could be anything; so there's no more checking we can do:
+      // return |response| as success, with an empty error.
+      return EvalJsResult(std::move(*response), std::string());
+    } else if (is_automatic_success_reply) {
+      // Got a response from the runner script that indicates success (of the
+      // form [token, [completion_value]]. Return the completion value, with an
+      // empty error.
+      return EvalJsResult(std::move(response->GetList()[1].GetList()[0]),
+                          std::string());
+    } else {
+      // The response was not well-formed (it failed the token match), so it's
+      // not from our runner script. Fail with an explanation of the raw
+      // message. This allows us to reject other calls
+      // domAutomationController.send().
+      error_stream
+          << "Internal Error: expected a 2-element list of the form "
+          << "['" << token << "', [result]]; but got instead: " << *response
+          << " ... This is potentially because a script tried to call "
+             "domAutomationController.send itself -- that is only allowed "
+             "when using EvalJsWithManualReply().  When using EvalJs(), result "
+             "values are just the result of calling eval() on the script -- "
+             "the completion value is the value of the last executed "
+             "statement.  When using ExecJs(), there is no result value.";
+    }
+  }
+
+  // Something went wrong. Return an empty value and a non-empty error.
+  return EvalJsResult(base::Value(), error_stream.str());
+}
+
+EvalJsResult EvalJsWithManualReply(const ToRenderFrameHost& execution_target,
+                                   const std::string& script,
+                                   int options,
+                                   int world_id) {
+  return EvalJs(execution_target, script,
+                options | EXECUTE_SCRIPT_USE_MANUAL_REPLY, world_id);
+}
+
 namespace {
 void AddToSetIfFrameMatchesPredicate(
     std::set<RenderFrameHost*>* frame_set,
@@ -1432,23 +1710,26 @@
 bool WaitForRenderFrameReady(RenderFrameHost* rfh) {
   if (!rfh)
     return false;
+  // TODO(nick): This can't switch to EvalJs yet, because of hardcoded
+  // dependencies on 'pageLoadComplete' in some interstitial implementations.
   std::string result;
-  EXPECT_TRUE(
-      content::ExecuteScriptAndExtractString(
-          rfh,
-          "(function() {"
-          "  var done = false;"
-          "  function checkState() {"
-          "    if (!done && document.readyState == 'complete') {"
-          "      done = true;"
-          "      window.domAutomationController.send('pageLoadComplete');"
-          "    }"
-          "  }"
-          "  checkState();"
-          "  document.addEventListener('readystatechange', checkState);"
-          "})();",
-          &result));
-  return result == "pageLoadComplete";
+  EXPECT_TRUE(ExecuteScriptAndExtractString(
+      rfh,
+      "(async function() {"
+      "  if (document.readyState != 'complete') {"
+      "    await new Promise((resolve) =>"
+      "      document.addEventListener('readystatechange', event => {"
+      "        if (document.readyState == 'complete') {"
+      "          resolve();"
+      "        }"
+      "      }));"
+      "  }"
+      "})().then(() => {"
+      "  window.domAutomationController.send('pageLoadComplete');"
+      "});",
+      &result));
+  EXPECT_EQ("pageLoadComplete", result);
+  return "pageLoadComplete" == result;
 }
 
 void EnableAccessibilityForWebContents(WebContents* web_contents) {
diff --git a/content/public/test/browser_test_utils.h b/content/public/test/browser_test_utils.h
index f772a8d6..7c9ec79 100644
--- a/content/public/test/browser_test_utils.h
+++ b/content/public/test/browser_test_utils.h
@@ -33,10 +33,12 @@
 #include "content/public/browser/web_contents_delegate.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/common/context_menu_params.h"
+#include "content/public/common/isolated_world_ids.h"
 #include "content/public/common/page_type.h"
 #include "ipc/message_filter.h"
 #include "services/network/public/mojom/network_service.mojom.h"
 #include "storage/common/fileapi/file_system_types.h"
+#include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/web_input_event.h"
 #include "third_party/blink/public/platform/web_mouse_event.h"
 #include "third_party/blink/public/platform/web_mouse_wheel_event.h"
@@ -320,6 +322,10 @@
 RenderFrameHost* ConvertToRenderFrameHost(RenderFrameHost* render_view_host);
 RenderFrameHost* ConvertToRenderFrameHost(WebContents* web_contents);
 
+// Semi-deprecated: in new code, prefer ExecJs() -- it works the same, but has
+// better error handling. (Note: still use ExecuteScript() on pages with a
+// Content Security Policy).
+//
 // Executes the passed |script| in the specified frame with the user gesture.
 //
 // Appends |domAutomationController.send(...)| to the end of |script| and waits
@@ -335,8 +341,9 @@
 // with a malformed or unexpected value).
 //
 // See also:
-// - ExecuteScriptAsync
-// - ExecuteScriptAndExtractBool/Int/String/etc.
+// - ExecJs (preferred replacement with better errror handling)
+// - EvalJs (if you want to retrieve a value)
+// - ExecuteScriptAsync (if you don't want to block for |script| completion)
 // - DOMMessageQueue (to manually wait for domAutomationController.send(...))
 bool ExecuteScript(const ToRenderFrameHost& adapter,
                    const std::string& script) WARN_UNUSED_RESULT;
@@ -359,6 +366,10 @@
 // sets |result| to the value passed to "window.domAutomationController.send" by
 // the executed script. They return true on success, false if the script
 // execution failed or did not evaluate to the expected type.
+//
+// Semi-deprecated: Consider using EvalJs() or EvalJsWithManualReply() instead,
+// which handle errors better and don't require an out-param. If the target
+// document doesn't have a CSP. See the comment on EvalJs() for migration tips.
 bool ExecuteScriptAndExtractDouble(const ToRenderFrameHost& adapter,
                                    const std::string& script,
                                    double* result) WARN_UNUSED_RESULT;
@@ -372,101 +383,6 @@
                                    const std::string& script,
                                    std::string* result) WARN_UNUSED_RESULT;
 
-// JsLiteralHelper is a helper class that determines what types are legal to
-// pass to StringifyJsLiteral. Legal types include int, string, StringPiece,
-// char*, bool, double, GURL, url::Origin, and base::Value&&.
-template <typename T>
-struct JsLiteralHelper {
-  // This generic version enables passing any type from which base::Value can be
-  // instantiated. This covers int, string, double, bool, base::Value&&, etc.
-  template <typename U>
-  static base::Value Convert(U&& arg) {
-    return base::Value(std::forward<U>(arg));
-  }
-};
-
-// Specialization allowing GURL to be passed to StringifyJsLiteral.
-template <>
-struct JsLiteralHelper<GURL> {
-  static base::Value Convert(const GURL& url) {
-    return base::Value(url.spec());
-  }
-};
-
-// Specialization allowing url::Origin to be passed to StringifyJsLiteral.
-template <>
-struct JsLiteralHelper<url::Origin> {
-  static base::Value Convert(const url::Origin& url) {
-    return base::Value(url.Serialize());
-  }
-};
-
-// Convert a value to a corresponding JS literal.
-//
-// |value| can be any type explicitly convertible to base::Value
-// (including int/string/StringPiece/char*/double/bool), or any type that
-// JsLiteralHelper is specialized for -- like URL and url::Origin, which emit
-// string literals.
-template <typename T>
-std::string StringifyJsLiteral(T&& value) {
-  using ValueType = std::remove_cv_t<std::remove_reference_t<T>>;
-  base::Value value_as_base_value =
-      JsLiteralHelper<ValueType>::Convert(std::forward<T>(value));
-  std::string value_as_json;
-  CHECK(base::JSONWriter::Write(value_as_base_value, &value_as_json));
-  return value_as_json;
-}
-
-// Base case for StringifyJsLiterals() variadic template (see below).
-inline void StringifyJsLiterals(std::vector<std::string>* list) {}
-
-// Call StringifyJsLiteral() on an arbitrary mix of values, appending the
-// results to |list|. |first| and |rest...| can have any type accepted by
-// StringifyJsLiteral.
-template <typename T, typename... Args>
-void StringifyJsLiterals(std::vector<std::string>* list,
-                         T&& first,
-                         Args&&... rest) {
-  list->push_back(StringifyJsLiteral(std::forward<T>(first)));
-  StringifyJsLiterals(list, std::forward<Args>(rest)...);
-}
-
-// Replaces $1, $2, $3, etc in |script_template| with JS literal values
-// constructed from |args|, similar to base::ReplaceStringPlaceholders.
-//
-// Unlike StringPrintf or manual concatenation, this version will properly
-// escape string content, even if it contains slashes or quotation marks.
-//
-// Each |arg| can be any type explicitly convertible to base::Value
-// (including int/string/StringPiece/char*/double/bool), or any type that
-// JsLiteralHelper is specialized for -- like URL and url::Origin, which emit
-// string literals. |args| can be a mix of different types.
-//
-// Example 1:
-//
-//   GURL page_url("http://example.com");
-//   EXPECT_TRUE(ExecuteScript(
-//       shell(), JsReplace("window.open($1, '_blank');", page_url)));
-//
-// $1 is replaced with a double-quoted JS string literal: "http://example.com".
-// Note that quotes around $1 are not required.
-//
-// Example 2:
-//
-//   bool forced_reload = true;
-//   EXPECT_TRUE(ExecuteScript(
-//       shell(), JsReplace("window.location.reload($1);", forced_reload)));
-//
-// This becomes "window.location.reload(true);" -- because bool values are
-// supported by base::Value. Numbers, lists, and dicts also work.
-template <typename... Args>
-std::string JsReplace(base::StringPiece script_template, Args&&... args) {
-  std::vector<std::string> replacements;
-  StringifyJsLiterals(&replacements, std::forward<Args>(args)...);
-  return base::ReplaceStringPlaceholders(script_template, replacements,
-                                         nullptr);
-}
-
 // Same as above but the script executed without user gesture.
 bool ExecuteScriptWithoutUserGestureAndExtractDouble(
     const ToRenderFrameHost& adapter,
@@ -485,17 +401,328 @@
     const std::string& script,
     std::string* result) WARN_UNUSED_RESULT;
 
-// This function behaves similarly to ExecuteScriptAndExtractBool but runs the
-// the script in the specified isolated world.
-bool ExecuteScriptInIsolatedWorldAndExtractBool(
-    const ToRenderFrameHost& adapter,
-    const int world_id,
-    const std::string& script,
-    bool* result) WARN_UNUSED_RESULT;
+// JsLiteralHelper is a helper class that determines what types are legal to
+// pass to StringifyJsLiteral. Legal types include int, string, StringPiece,
+// char*, bool, double, GURL, url::Origin, and base::Value&&.
+template <typename T>
+struct JsLiteralHelper {
+  // This generic version enables passing any type from which base::Value can be
+  // instantiated. This covers int, string, double, bool, base::Value&&, etc.
+  template <typename U>
+  static base::Value Convert(U&& arg) {
+    return base::Value(std::forward<U>(arg));
+  }
 
-// Walks the frame tree of the specified WebContents and returns the sole frame
-// that matches the specified predicate function. This function will DCHECK if
-// no frames match the specified predicate, or if more than one frame matches.
+  template <>
+  static base::Value Convert(const base::Value& value) {
+    return value.Clone();
+  }
+
+  template <>
+  static base::Value Convert(const base::ListValue& value) {
+    return value.Clone();
+  }
+};
+
+// Specialization allowing GURL to be passed to StringifyJsLiteral.
+template <>
+struct JsLiteralHelper<GURL> {
+  static base::Value Convert(const GURL& url) {
+    return base::Value(url.spec());
+  }
+};
+
+// Specialization allowing url::Origin to be passed to StringifyJsLiteral.
+template <>
+struct JsLiteralHelper<url::Origin> {
+  static base::Value Convert(const url::Origin& url) {
+    return base::Value(url.Serialize());
+  }
+};
+
+// Helper for variadic ListValueOf() -- zero-argument base case.
+inline void ConvertToBaseValueList(base::Value::ListStorage* list) {}
+
+// Helper for variadic ListValueOf() -- case with at least one argument.
+//
+// |first| can be any type explicitly convertible to base::Value
+// (including int/string/StringPiece/char*/double/bool), or any type that
+// JsLiteralHelper is specialized for -- like URL and url::Origin, which emit
+// string literals.
+template <typename T, typename... Args>
+void ConvertToBaseValueList(base::Value::ListStorage* list,
+                            T&& first,
+                            Args&&... rest) {
+  using ValueType = std::remove_cv_t<std::remove_reference_t<T>>;
+  list->push_back(JsLiteralHelper<ValueType>::Convert(std::forward<T>(first)));
+  ConvertToBaseValueList(list, std::forward<Args>(rest)...);
+}
+
+// Construct a list-type base::Value from a mix of arguments.
+//
+// Each |arg| can be any type explicitly convertible to base::Value
+// (including int/string/StringPiece/char*/double/bool), or any type that
+// JsLiteralHelper is specialized for -- like URL and url::Origin, which emit
+// string literals. |args| can be a mix of different types.
+template <typename... Args>
+base::ListValue ListValueOf(Args&&... args) {
+  base::ListValue result;
+  ConvertToBaseValueList(&result.GetList(), std::forward<Args>(args)...);
+  return result;
+}
+
+// Replaces $1, $2, $3, etc in |script_template| with JS literal values
+// constructed from |args|, similar to base::ReplaceStringPlaceholders.
+//
+// Unlike StringPrintf or manual concatenation, this version will properly
+// escape string content, even if it contains slashes or quotation marks.
+//
+// Each |arg| can be any type explicitly convertible to base::Value
+// (including int/string/StringPiece/char*/double/bool), or any type that
+// JsLiteralHelper is specialized for -- like URL and url::Origin, which emit
+// string literals. |args| can be a mix of different types.
+//
+// Example 1:
+//
+//   GURL page_url("http://example.com");
+//   EXPECT_TRUE(ExecuteScript(
+//       shell(), JsReplace("window.open($1, '_blank');", page_url)));
+//
+// $1 is replaced with a double-quoted JS string literal:
+// "http://example.com". Note that quotes around $1 are not required.
+//
+// Example 2:
+//
+//   bool forced_reload = true;
+//   EXPECT_TRUE(ExecuteScript(
+//       shell(), JsReplace("window.location.reload($1);", forced_reload)));
+//
+// This becomes "window.location.reload(true);" -- because bool values are
+// supported by base::Value. Numbers, lists, and dicts also work.
+template <typename... Args>
+std::string JsReplace(base::StringPiece script_template, Args&&... args) {
+  base::Value::ListStorage values;
+  ConvertToBaseValueList(&values, std::forward<Args>(args)...);
+  std::vector<std::string> replacements(values.size());
+  for (size_t i = 0; i < values.size(); ++i) {
+    CHECK(base::JSONWriter::Write(values[i], &replacements[i]));
+  }
+  return base::ReplaceStringPlaceholders(script_template, replacements,
+                                         nullptr);
+}
+
+// The return value of EvalJs. Captures the value (or the error) arising from
+// script execution. When used with gtest assertions, EvalJsResult generally
+// behaves like its wrapped value.
+//
+// An EvalJsResult can be consumed in two ways:
+//
+//  (1) [preferred] Pass it directly to an EXPECT_EQ() macro. It has
+//      overloaded operator== against std::string, bool, int, double,
+//      nullptr_t, and base::Value. This will produce readable assertion
+//      failures if there is a type mismatch, or if an exception was thrown --
+//      errors are never equal to anything.
+//
+//      For boolean results, note that EXPECT_TRUE(..) and EXPECT_FALSE()
+//      won't compile; use EXPECT_EQ(true, ...) instead. This is intentional,
+//      since EXPECT_TRUE() could be read ambiguously as either "expect
+//      successful execution", "expect truthy value of any type", or "expect
+//      boolean value 'true'".
+//
+//  (2) [use when necessary] Extract the underlying value of an expected type,
+//      by calling ExtractString(), ExtractInt(), etc. This will produce a
+//      CHECK failure if the execution didn't result in the appropriate type
+//      of result, or if an exception was thrown.
+struct EvalJsResult {
+  const base::Value value;  // Value; if things went well.
+  const std::string error;  // Error; if things went badly.
+
+  // Creates an ExecuteScript result. If |error| is non-empty, |value| will be
+  // ignored.
+  EvalJsResult(base::Value value, const std::string& error);
+
+  // Copy ctor.
+  EvalJsResult(const EvalJsResult& value);
+
+  // Extract a result value of the requested type, or die trying.
+  //
+  // If there was an error, or if returned value is of a different type, these
+  // will fail with a CHECK. Use Extract methods only when accessing the
+  // result value is necessary; prefer operator== and EXPECT_EQ() instead:
+  // they don't CHECK, and give better error messages.
+  const std::string& ExtractString() const WARN_UNUSED_RESULT;
+  int ExtractInt() const WARN_UNUSED_RESULT;
+  bool ExtractBool() const WARN_UNUSED_RESULT;
+  double ExtractDouble() const WARN_UNUSED_RESULT;
+  base::ListValue ExtractList() const WARN_UNUSED_RESULT;
+};
+
+// Enables EvalJsResult to be used directly in ASSERT/EXPECT macros:
+//
+//    ASSERT_EQ("ab", EvalJs(rfh, "'a' + 'b'"))
+//    ASSERT_EQ(2, EvalJs(rfh, "1 + 1"))
+//    ASSERT_EQ(nullptr, EvalJs(rfh, "var a = 1 + 1"))
+//
+// Error values never return true for any comparison operator.
+template <typename T>
+bool operator==(const T& a, const EvalJsResult& b) {
+  return b.error.empty() && (JsLiteralHelper<T>::Convert(a) == b.value);
+}
+
+template <typename T>
+bool operator!=(const T& a, const EvalJsResult& b) {
+  return b.error.empty() && (JsLiteralHelper<T>::Convert(a) != b.value);
+}
+
+template <typename T>
+bool operator>=(const T& a, const EvalJsResult& b) {
+  return b.error.empty() && (JsLiteralHelper<T>::Convert(a) >= b.value);
+}
+
+template <typename T>
+bool operator<=(const T& a, const EvalJsResult& b) {
+  return b.error.empty() && (JsLiteralHelper<T>::Convert(a) <= b.value);
+}
+
+template <typename T>
+bool operator<(const T& a, const EvalJsResult& b) {
+  return b.error.empty() && (JsLiteralHelper<T>::Convert(a) < b.value);
+}
+
+template <typename T>
+bool operator>(const T& a, const EvalJsResult& b) {
+  return b.error.empty() && (JsLiteralHelper<T>::Convert(a) > b.value);
+}
+
+inline bool operator==(nullptr_t a, const EvalJsResult& b) {
+  return b.error.empty() && (base::Value() == b.value);
+}
+
+// Provides informative failure messages when the result of EvalJs() is
+// used in a failing ASSERT_EQ or EXPECT_EQ.
+void PrintTo(const EvalJsResult& bar, ::std::ostream* os);
+
+enum EvalJsOptions {
+  EXECUTE_SCRIPT_DEFAULT_OPTIONS = 0,
+
+  // By default, EvalJs runs with a user gesture. This bit flag disables
+  // that.
+  EXECUTE_SCRIPT_NO_USER_GESTURE = (1 << 0),
+
+  // This bit controls how the result is obtained. By default, EvalJs's runner
+  // script will call domAutomationController.send() with the completion
+  // value. Setting this bit will disable that, requiring |script| to provide
+  // its own call to domAutomationController.send() instead.
+  EXECUTE_SCRIPT_USE_MANUAL_REPLY = (1 << 1),
+
+  // By default, when the script passed to EvalJs evaluates to a Promise, the
+  // execution continues until the Promise resolves, and the resolved value is
+  // returned. Setting this bit disables such Promise resolution.
+  EXECUTE_SCRIPT_NO_RESOLVE_PROMISES = (1 << 2),
+};
+
+// EvalJs() -- run |script| in |execution_target| and return its value or error.
+//
+// Example simple usage:
+//
+//   EXPECT_EQ("https://abcd.com", EvalJs(render_frame_host, "self.origin"));
+//   EXPECT_EQ(5, EvalJs(render_frame_host, "history.length"));
+//   EXPECT_EQ(false, EvalJs(render_frame_host, "history.length > 5"));
+//
+// The result value of |script| is its "statement completion value" -- the same
+// semantics used by Javascript's own eval() function. If |script|
+// raises exceptions, or is syntactically invalid, an error is captured instead,
+// including a full stack trace.
+//
+// The return value of EvalJs() may be used directly in EXPECT_EQ()
+// macros, and compared for against std::string, int, or any other type for
+// which base::Value has a constructor.  If an error was thrown by the script,
+// any comparison operators will always return false.
+//
+// If |script|'s captured completion value is a Promise, this function blocks
+// until the Promise is resolved. This enables a usage pattern where |script|
+// may call an async function, and use the await keyword to wait for
+// events to fire. For example:
+//
+//   EXPECT_EQ(200, EvalJs(rfh, "(async () => { var resp = (await fetch(url));"
+//                              "               return resp.status; })()");
+//
+// In the above example, the immediately-invoked function expression results in
+// a Promise (that's what async functions do); EvalJs will continue blocking
+// until the Promise resolves, which happens when the async function returns
+// the HTTP status code -- which is expected, in this case, to be 200.
+//
+// Quick migration guide for users of the classic ExecuteScriptAndExtract*():
+//  - If your page has a Content SecurityPolicy, don't migrate [yet]; CSP can
+//    interfere with the internal mechanism used here.
+//  - Get rid of the out-param. You call EvalJs no matter what your return
+//    type is.
+//  - If possible, pass the result of EvalJs() into the second argument of an
+//    EXPECT_EQ macro. This will trigger failure (and a nice message) if an
+//    error occurs.
+//  - Eliminate calls to domAutomationController.send() in |script|. In simple
+//    cases, |script| is just an expression you want the value of.
+//  - When a script previously installed a callback or event listener that
+//    invoked domAutomationController.send(x) asynchronously, there is a choice:
+//     * Preferred, but more rewriting: Use EvalJs with a Promise which
+//       resolves to the value you previously passed to send().
+//     * Less rewriting of |script|, but with some drawbacks: Use
+//       EXECUTE_SCRIPT_USE_MANUAL_REPLY in |options|, or EvalJsWithManualReply.
+//       When specified, this means that |script| must continue to call
+//       domAutomationController.send(). Note that this option option disables
+//       some error-catching safeguards, but you still get the benefit of having
+//       an EvalJsResult that can be passed to EXPECT.
+//
+// Why prefer EvalJs over ExecuteScriptAndExtractString(), etc? Because:
+//
+//  - It's one function, that does everything, and more succinctly.
+//  - Can be used directly in EXPECT_EQ macros (no out- param pointers like
+//    ExecuteScriptAndExtractBool()) -- no temporary variable is required,
+//    usually resulting in fewer lines of code.
+//  - JS exceptions are reliably captured and will appear as C++ assertion
+//    failures.
+//  - JS stack traces arising from exceptions are annotated with the
+//    corresponding source code; this also appears in C++ assertion failures.
+//  - Delayed response is supported via Promises and JS async/await.
+//  - |script| doesn't need to call domAutomationController.send directly.
+//  - When a script doesn't produce a result, it's likely an assertion
+//    failure rather than a hang.  Doesn't get confused by crosstalk with
+//    other callers of domAutomationController.send() -- script results carry
+//    a GUID.
+//  - Lists, dicts, null values, etc. can be returned as base::Values.
+EvalJsResult EvalJs(const ToRenderFrameHost& execution_target,
+                    const std::string& script,
+                    int options = EXECUTE_SCRIPT_DEFAULT_OPTIONS,
+                    int world_id = ISOLATED_WORLD_ID_GLOBAL) WARN_UNUSED_RESULT;
+
+// Like EvalJs(), except that |script| must call domAutomationController.send()
+// itself. This is the same as specifying the EXECUTE_SCRIPT_USE_MANUAL_REPLY
+// option to EvalJs.
+EvalJsResult EvalJsWithManualReply(const ToRenderFrameHost& execution_target,
+                                   const std::string& script,
+                                   int options = EXECUTE_SCRIPT_DEFAULT_OPTIONS,
+                                   int world_id = ISOLATED_WORLD_ID_GLOBAL)
+    WARN_UNUSED_RESULT;
+
+// Run a script exactly the same as EvalJs(), but ignore the resulting value.
+//
+// Returns AssertionSuccess() if |script| ran successfully, and
+// AssertionFailure() if |script| contained a syntax error or threw an
+// exception.
+//
+// Unlike ExecuteScript(), this catches syntax errors and uncaught exceptions,
+// and gives more useful error messages when things go wrong. Prefer ExecJs to
+// ExecuteScript(), unless your page has a CSP.
+::testing::AssertionResult ExecJs(const ToRenderFrameHost& execution_target,
+                                  const std::string& script,
+                                  int options = EXECUTE_SCRIPT_DEFAULT_OPTIONS,
+                                  int world_id = ISOLATED_WORLD_ID_GLOBAL)
+    WARN_UNUSED_RESULT;
+
+// Walks the frame tree of the specified WebContents and returns the sole
+// frame that matches the specified predicate function. This function will
+// DCHECK if no frames match the specified predicate, or if more than one
+// frame matches.
 RenderFrameHost* FrameMatchingPredicate(
     WebContents* web_contents,
     const base::Callback<bool(RenderFrameHost*)>& predicate);
diff --git a/content/renderer/accessibility/render_accessibility_impl.cc b/content/renderer/accessibility/render_accessibility_impl.cc
index 9b9034b..52103050 100644
--- a/content/renderer/accessibility/render_accessibility_impl.cc
+++ b/content/renderer/accessibility/render_accessibility_impl.cc
@@ -274,7 +274,7 @@
   // Force the newly focused node to be re-serialized so we include its
   // inline text boxes.
   if (event == ax::mojom::Event::kFocus)
-    serializer_.DeleteClientSubtree(obj);
+    serializer_.InvalidateSubtree(obj);
 #endif
 
   // If some cell IDs have been added or removed, we need to update the whole
@@ -283,7 +283,7 @@
       event == ax::mojom::Event::kChildrenChanged) {
     WebAXObject table_like_object = obj.ParentObject();
     if (!table_like_object.IsDetached()) {
-      serializer_.DeleteClientSubtree(table_like_object);
+      serializer_.InvalidateSubtree(table_like_object);
       HandleAXEvent(table_like_object, ax::mojom::Event::kChildrenChanged);
     }
   }
@@ -294,7 +294,7 @@
       event == ax::mojom::Event::kChildrenChanged) {
     WebAXObject popup_like_object = obj.ParentObject();
     if (!popup_like_object.IsDetached()) {
-      serializer_.DeleteClientSubtree(popup_like_object);
+      serializer_.InvalidateSubtree(popup_like_object);
       HandleAXEvent(popup_like_object, ax::mojom::Event::kChildrenChanged);
     }
   }
@@ -459,7 +459,7 @@
       block = block.ParentObject();
     }
     if (!block.IsDetached() && !block.Equals(obj))
-      serializer_.DeleteClientSubtree(block);
+      serializer_.InvalidateSubtree(block);
 
     // Whenever there's a change within a table, invalidate the
     // whole table so that row and cell indexes are recomputed.
@@ -471,7 +471,7 @@
              !ui::IsTableLikeRole(AXRoleFromBlink(table.Role())))
         table = table.ParentObject();
       if (!table.IsDetached())
-        serializer_.DeleteClientSubtree(table);
+        serializer_.InvalidateSubtree(table);
     }
 
     VLOG(1) << "Accessibility event: " << ui::ToString(event.event_type)
@@ -744,7 +744,7 @@
 
   // This object may not be a leaf node. Force the whole subtree to be
   // re-serialized.
-  serializer_.DeleteClientSubtree(obj);
+  serializer_.InvalidateSubtree(obj);
 
   // Explicitly send a tree change update event now.
   HandleAXEvent(obj, ax::mojom::Event::kTreeChanged);
@@ -763,7 +763,7 @@
   if (document.IsNull())
     return;
 
-  serializer_.DeleteClientSubtree(obj);
+  serializer_.InvalidateSubtree(obj);
   HandleAXEvent(obj, ax::mojom::Event::kImageFrameUpdated);
 }
 
diff --git a/content/renderer/media/stream/media_stream_constraints_util_audio.cc b/content/renderer/media/stream/media_stream_constraints_util_audio.cc
index faf854c..ac494175f 100644
--- a/content/renderer/media/stream/media_stream_constraints_util_audio.cc
+++ b/content/renderer/media/stream/media_stream_constraints_util_audio.cc
@@ -710,8 +710,8 @@
                            const blink::WebMediaConstraints& constraints) {
   DCHECK(source);
   if (source->device().type != MEDIA_DEVICE_AUDIO_CAPTURE &&
-      source->device().type != MEDIA_TAB_AUDIO_CAPTURE &&
-      source->device().type != MEDIA_DESKTOP_AUDIO_CAPTURE) {
+      source->device().type != MEDIA_GUM_TAB_AUDIO_CAPTURE &&
+      source->device().type != MEDIA_GUM_DESKTOP_AUDIO_CAPTURE) {
     return AudioCaptureSettings();
   }
 
@@ -722,13 +722,13 @@
         constraints.Basic().media_stream_source.GetName());
   }
 
-  if (source->device().type == MEDIA_TAB_AUDIO_CAPTURE &&
+  if (source->device().type == MEDIA_GUM_TAB_AUDIO_CAPTURE &&
       !media_stream_source.empty() &&
       media_stream_source != kMediaStreamSourceTab) {
     return AudioCaptureSettings(
         constraints.Basic().media_stream_source.GetName());
   }
-  if (source->device().type == MEDIA_DESKTOP_AUDIO_CAPTURE &&
+  if (source->device().type == MEDIA_GUM_DESKTOP_AUDIO_CAPTURE &&
       !media_stream_source.empty() &&
       media_stream_source != kMediaStreamSourceSystem &&
       media_stream_source != kMediaStreamSourceDesktop) {
diff --git a/content/renderer/media/stream/media_stream_constraints_util_audio_unittest.cc b/content/renderer/media/stream/media_stream_constraints_util_audio_unittest.cc
index 08326c9..007f334 100644
--- a/content/renderer/media/stream/media_stream_constraints_util_audio_unittest.cc
+++ b/content/renderer/media/stream/media_stream_constraints_util_audio_unittest.cc
@@ -136,8 +136,8 @@
     if (media_source.empty())
       return MEDIA_DEVICE_AUDIO_CAPTURE;
     else if (media_source == kMediaStreamSourceTab)
-      return MEDIA_TAB_AUDIO_CAPTURE;
-    return MEDIA_DESKTOP_AUDIO_CAPTURE;
+      return MEDIA_GUM_TAB_AUDIO_CAPTURE;
+    return MEDIA_GUM_DESKTOP_AUDIO_CAPTURE;
   }
 
   std::unique_ptr<ProcessedLocalAudioSource> GetProcessedLocalAudioSource(
diff --git a/content/renderer/media/stream/media_stream_constraints_util_video_content.cc b/content/renderer/media/stream/media_stream_constraints_util_video_content.cc
index 42e92fb..273a64b 100644
--- a/content/renderer/media/stream/media_stream_constraints_util_video_content.cc
+++ b/content/renderer/media/stream/media_stream_constraints_util_video_content.cc
@@ -300,7 +300,7 @@
 
   // This default comes from the old algorithm.
   media::ResolutionChangePolicy default_resolution_policy =
-      stream_type == MEDIA_TAB_VIDEO_CAPTURE
+      stream_type == MEDIA_GUM_TAB_VIDEO_CAPTURE
           ? media::ResolutionChangePolicy::FIXED_RESOLUTION
           : media::ResolutionChangePolicy::ANY_WITHIN_LIMIT;
 
diff --git a/content/renderer/media/stream/media_stream_constraints_util_video_content_unittest.cc b/content/renderer/media/stream/media_stream_constraints_util_video_content_unittest.cc
index 2a94b3a..7fcd5e8c 100644
--- a/content/renderer/media/stream/media_stream_constraints_util_video_content_unittest.cc
+++ b/content/renderer/media/stream/media_stream_constraints_util_video_content_unittest.cc
@@ -60,7 +60,7 @@
 class MediaStreamConstraintsUtilVideoContentTest : public testing::Test {
  protected:
   VideoCaptureSettings SelectSettings(
-      MediaStreamType stream_type = MEDIA_DESKTOP_VIDEO_CAPTURE) {
+      MediaStreamType stream_type = MEDIA_GUM_DESKTOP_VIDEO_CAPTURE) {
     blink::WebMediaConstraints constraints =
         constraint_factory_.CreateWebMediaConstraints();
     return SelectSettingsVideoContentCapture(constraints, stream_type,
@@ -2034,7 +2034,7 @@
   }
   {
     constraint_factory_.Reset();
-    auto result = SelectSettings(MEDIA_TAB_VIDEO_CAPTURE);
+    auto result = SelectSettings(MEDIA_GUM_TAB_VIDEO_CAPTURE);
     EXPECT_EQ(kDefaultScreenCastWidth, result.Width());
     EXPECT_EQ(kDefaultScreenCastHeight, result.Height());
     // Default policy for tab capture is fixed resolution.
diff --git a/content/renderer/media/stream/media_stream_device_observer_unittest.cc b/content/renderer/media/stream/media_stream_device_observer_unittest.cc
index f0bbab2..e96bef4 100644
--- a/content/renderer/media/stream/media_stream_device_observer_unittest.cc
+++ b/content/renderer/media/stream/media_stream_device_observer_unittest.cc
@@ -60,7 +60,7 @@
   // OpenDevice request 2
   base::RunLoop run_loop2;
   mock_dispatcher_host_.OpenDevice(
-      kRequestId2, "screen_capture", MEDIA_DESKTOP_VIDEO_CAPTURE,
+      kRequestId2, "screen_capture", MEDIA_GUM_DESKTOP_VIDEO_CAPTURE,
       base::BindOnce(&MediaStreamDeviceObserverTest::OnDeviceOpened,
                      base::Unretained(this), run_loop2.QuitClosure()));
   run_loop2.Run();
diff --git a/content/renderer/media/stream/user_media_processor.cc b/content/renderer/media/stream/user_media_processor.cc
index 0b1a8752..a6f73a1b 100644
--- a/content/renderer/media/stream/user_media_processor.cc
+++ b/content/renderer/media/stream/user_media_processor.cc
@@ -70,10 +70,10 @@
           : constraints.Basic().media_stream_source.Exact()[0].Utf8();
   if (!source_constraint.empty()) {
     if (source_constraint == kMediaStreamSourceTab) {
-      *stream_type = MEDIA_TAB_AUDIO_CAPTURE;
+      *stream_type = MEDIA_GUM_TAB_AUDIO_CAPTURE;
     } else if (source_constraint == kMediaStreamSourceDesktop ||
                source_constraint == kMediaStreamSourceSystem) {
-      *stream_type = MEDIA_DESKTOP_AUDIO_CAPTURE;
+      *stream_type = MEDIA_GUM_DESKTOP_AUDIO_CAPTURE;
     }
   } else {
     *stream_type = MEDIA_DEVICE_AUDIO_CAPTURE;
@@ -96,10 +96,10 @@
           : constraints.Basic().media_stream_source.Exact()[0].Utf8();
   if (!source_constraint.empty()) {
     if (source_constraint == kMediaStreamSourceTab) {
-      *stream_type = MEDIA_TAB_VIDEO_CAPTURE;
+      *stream_type = MEDIA_GUM_TAB_VIDEO_CAPTURE;
     } else if (source_constraint == kMediaStreamSourceDesktop ||
                source_constraint == kMediaStreamSourceScreen) {
-      *stream_type = MEDIA_DESKTOP_VIDEO_CAPTURE;
+      *stream_type = MEDIA_GUM_DESKTOP_VIDEO_CAPTURE;
     }
   } else {
     *stream_type = MEDIA_DEVICE_VIDEO_CAPTURE;
diff --git a/content/test/browser_test_utils_browsertest.cc b/content/test/browser_test_utils_browsertest.cc
index d40003dd..0b19f2e1 100644
--- a/content/test/browser_test_utils_browsertest.cc
+++ b/content/test/browser_test_utils_browsertest.cc
@@ -10,6 +10,7 @@
 #include "content/shell/browser/shell.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gmock/include/gmock/gmock.h"
 
 namespace content {
 
@@ -79,4 +80,99 @@
   EXPECT_EQ(observer.redirect_url(), observer.navigation_url());
 }
 
+using EvalJsBrowserTest = ContentBrowserTest;
+
+IN_PROC_BROWSER_TEST_F(EvalJsBrowserTest, EvalJsErrors) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html"));
+
+  {
+    // Test syntax errors.
+    auto result = EvalJs(shell(), "}}");
+    EXPECT_FALSE(true == result);
+    EXPECT_FALSE(false == result);  // EXPECT_FALSE(EvalJs()) shouldn't compile.
+    EXPECT_FALSE(0 == result);
+    EXPECT_FALSE(1 == result);
+    EXPECT_FALSE("}}" == result);  // EXPECT_EQ should fail
+    EXPECT_FALSE("}}" != result);  // As should EXPECT_NE
+    EXPECT_FALSE(nullptr == result);
+
+    std::string expected_error = R"(a JavaScript error:
+SyntaxError: Unexpected token }
+    at eval (<anonymous>)
+    at Promise.resolve.then.script (EvalJs-runner.js:2:34)
+)";
+    EXPECT_FALSE(expected_error == result);
+    EXPECT_EQ(expected_error, result.error);
+  }
+
+  {
+    // Test throwing exceptions.
+    auto result = EvalJs(shell(), "55; throw new Error('whoops');");
+    EXPECT_FALSE(55 == result);
+    EXPECT_FALSE(1 == result);
+    EXPECT_FALSE("whoops" == result);
+
+    std::string expected_error = R"(a JavaScript error:
+Error: whoops
+    at eval (__const_std::string&_script__:1:11):
+        55; throw new Error('whoops');
+                  ^^^^^
+    at eval (<anonymous>)
+    at Promise.resolve.then.script (EvalJs-runner.js:2:34)
+)";
+    EXPECT_FALSE(expected_error == result);
+    EXPECT_EQ(expected_error, result.error);
+  }
+
+  {
+    // Test reference errors in a multi-line script.
+    auto result = EvalJs(shell(), R"(
+    22;
+    var x = 200 + 300;
+    var y = z + x;
+    'sweet';)");
+    EXPECT_FALSE(22 == result);
+    EXPECT_FALSE("sweet" == result);
+
+    std::string expected_error = R"(a JavaScript error:
+ReferenceError: z is not defined
+    at eval (__const_std::string&_script__:4:13):
+            var y = z + x;
+                    ^^^^^
+    at eval (<anonymous>)
+    at Promise.resolve.then.script (EvalJs-runner.js:2:34)
+)";
+    EXPECT_FALSE(expected_error == result);
+    EXPECT_EQ(expected_error, result.error);
+  }
+}
+
+IN_PROC_BROWSER_TEST_F(EvalJsBrowserTest, EvalJsWithManualReply) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html"));
+
+  std::string script = "window.domAutomationController.send(20); 'hi';";
+
+  // Calling domAutomationController is required for EvalJsWithManualReply.
+  EXPECT_EQ(20, EvalJsWithManualReply(shell(), script));
+
+  // Calling domAutomationController is an error with EvalJs.
+  auto result = EvalJs(shell(), script);
+  EXPECT_FALSE(20 == result);
+  EXPECT_FALSE("hi" == result);
+  EXPECT_THAT(result.error,
+              ::testing::StartsWith(
+                  "Internal Error: expected a 2-element list of the form "));
+  EXPECT_THAT(
+      result.error,
+      ::testing::EndsWith("This is potentially because a script tried to call "
+                          "domAutomationController.send itself -- that is only "
+                          "allowed when using EvalJsWithManualReply().  When "
+                          "using EvalJs(), result values are just the result "
+                          "of calling eval() on the script -- the completion "
+                          "value is the value of the last executed statement.  "
+                          "When using ExecJs(), there is no result value."));
+}
+
 }  // namespace content
diff --git a/content/test/data/accessibility/html/reparent-crash-expected-blink.txt b/content/test/data/accessibility/html/reparent-crash-expected-blink.txt
new file mode 100644
index 0000000..c9fd0c7
--- /dev/null
+++ b/content/test/data/accessibility/html/reparent-crash-expected-blink.txt
@@ -0,0 +1,5 @@
+rootWebArea
+++listItem name='@NO_CHILDREN_DUMP'
+++genericContainer
+++++staticText name='Done'
+++++++inlineTextBox name='Done'
diff --git a/content/test/data/accessibility/html/reparent-crash.html b/content/test/data/accessibility/html/reparent-crash.html
new file mode 100644
index 0000000..4e25a7611
--- /dev/null
+++ b/content/test/data/accessibility/html/reparent-crash.html
@@ -0,0 +1,29 @@
+<!--
+@WAIT-FOR:Done
+
+This is a regression test for a bug that caused a crash when an
+inline node was reparented. The actual test output is not
+interesting, all that matters is that it correctly waits for
+"Done" to appear in the accessibility tree and doesn't generate
+an invalid tree update in the process.
+-->
+
+<img id="cl"></img>
+<li aria-label="@NO_CHILDREN_DUMP">
+  <div id="aw"><pre id="d3"><object id="b" aria-hidden=true></object></pre></div>
+</li>
+<object id="z"><div id="b5"></div> <option></option></object>
+<div id="s">Status</div>
+
+<script>
+  $ = document.querySelector.bind(document);
+  $('#d3').appendChild($('#z'));
+  $('#b').offsetTop;
+  setInterval(function() {
+    $('#z').appendChild($('#cl'));
+  }, 1);
+  $('#z').removeChild($('#b5'));
+  setTimeout(function() {
+    $('#s').innerText = "Done";
+  }, 10);
+</script>
diff --git a/device/vr/public/mojom/README.md b/device/vr/public/mojom/README.md
index f2dff80..a803ea92 100644
--- a/device/vr/public/mojom/README.md
+++ b/device/vr/public/mojom/README.md
@@ -28,8 +28,9 @@
 VRService - lives in the browser process, corresponds to a single frame.  Root
 object to obtain other XR objects.
 
-VRDisplayHost - lives in the browser process.  Allows a client to start a
-session (either immersive/exclusive/presenting or magic window).
+XRDevice - lives in the browser process, implemented as XRDeviceImpl. Allows a
+client to start a session (either immersive/exclusive/presenting or
+non-immersive).
 
 VRServiceClient - lives in the renderer process.  Is notified when VRDisplays
 are connected.
diff --git a/device/vr/public/mojom/isolated_xr_service.mojom b/device/vr/public/mojom/isolated_xr_service.mojom
index b3cbef8c..9366552 100644
--- a/device/vr/public/mojom/isolated_xr_service.mojom
+++ b/device/vr/public/mojom/isolated_xr_service.mojom
@@ -56,7 +56,7 @@
 
 // An XRRuntime may live in the browser process or a utility process.  The
 // browser process is the client, and may in turn expose device information to
-// render processes using vr_service interfaces, such as VRDisplayHost.
+// render processes using vr_service interfaces, such as XRDevice.
 interface XRRuntime {
   // Attempt to start a session. Called by the browser process, but the result
   // will probably be passed to the renderer process to allow getting data and
diff --git a/device/vr/public/mojom/vr_service.mojom b/device/vr/public/mojom/vr_service.mojom
index a00fd3c..eb75e876 100644
--- a/device/vr/public/mojom/vr_service.mojom
+++ b/device/vr/public/mojom/vr_service.mojom
@@ -253,13 +253,13 @@
 };
 
 interface VRServiceClient {
-  OnDisplayConnected(VRDisplayHost display, VRDisplayClient& request,
+  OnDisplayConnected(XRDevice device, VRDisplayClient& request,
                      VRDisplayInfo display_info);
 };
 
 // Provides a communication channel from the renderer to the browser-side host
 // of a (device/) VrDisplayImpl.
-interface VRDisplayHost {
+interface XRDevice {
   // Request to initialize a session in the browser process. The return value
   // indicates if the session was successfully initialized or not.
   // TODO(https://crbug.com/842025): Refactor VR device interfaces to better
diff --git a/device/vr/test/fake_vr_service_client.cc b/device/vr/test/fake_vr_service_client.cc
index a46445f..7cb9183 100644
--- a/device/vr/test/fake_vr_service_client.cc
+++ b/device/vr/test/fake_vr_service_client.cc
@@ -13,7 +13,7 @@
 FakeVRServiceClient::~FakeVRServiceClient() {}
 
 void FakeVRServiceClient::OnDisplayConnected(
-    mojom::VRDisplayHostPtr display,
+    mojom::XRDevicePtr device,
     mojom::VRDisplayClientRequest request,
     mojom::VRDisplayInfoPtr displayInfo) {
   displays_.push_back(std::move(displayInfo));
diff --git a/device/vr/test/fake_vr_service_client.h b/device/vr/test/fake_vr_service_client.h
index 74fd128..62d5d70 100644
--- a/device/vr/test/fake_vr_service_client.h
+++ b/device/vr/test/fake_vr_service_client.h
@@ -19,7 +19,7 @@
   FakeVRServiceClient(mojom::VRServiceClientRequest request);
   ~FakeVRServiceClient() override;
 
-  void OnDisplayConnected(mojom::VRDisplayHostPtr display,
+  void OnDisplayConnected(mojom::XRDevicePtr device,
                           mojom::VRDisplayClientRequest request,
                           mojom::VRDisplayInfoPtr displayInfo) override;
   void SetLastDeviceId(unsigned int id);
diff --git a/extensions/browser/guest_view/mime_handler_view/mime_handler_view_browsertest.cc b/extensions/browser/guest_view/mime_handler_view/mime_handler_view_browsertest.cc
index 292e3a7..99a6758 100644
--- a/extensions/browser/guest_view/mime_handler_view/mime_handler_view_browsertest.cc
+++ b/extensions/browser/guest_view/mime_handler_view/mime_handler_view_browsertest.cc
@@ -10,6 +10,8 @@
 #include "base/path_service.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "chrome/browser/extensions/extension_apitest.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/test/base/ui_test_utils.h"
@@ -19,6 +21,7 @@
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_renderer_host.h"
 #include "extensions/browser/api/extensions_api_client.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/guest_view/extensions_guest_view_manager_delegate.h"
@@ -41,8 +44,7 @@
 // The test extension id is set by the key value in the manifest.
 const char kExtensionId[] = "oickdpebdnfbgkcaoklfcdhjniefkcji";
 
-class MimeHandlerViewTest : public extensions::ExtensionApiTest,
-                            public testing::WithParamInterface<bool> {
+class MimeHandlerViewTest : public extensions::ExtensionApiTest {
  public:
   MimeHandlerViewTest() {
     GuestViewManager::set_factory_for_testing(&factory_);
@@ -60,24 +62,6 @@
     ASSERT_TRUE(StartEmbeddedTestServer());
   }
 
-  // TODO(ekaramad): These tests run for OOPIF guests too, except that they
-  // still use BrowserPlugin code path. They are activated to make sure we can
-  // still show PDF when the rest of the guests migrate to OOPIF. Eventually,
-  // MimeHandlerViewGuest will be based on OOPIF and we can remove this comment
-  // (https://crbug.com/642826).
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    extensions::ExtensionApiTest::SetUpCommandLine(command_line);
-
-    bool use_cross_process_frames_for_guests = GetParam();
-    if (use_cross_process_frames_for_guests) {
-      scoped_feature_list_.InitAndEnableFeature(
-          features::kGuestViewCrossProcessFrames);
-    } else {
-      scoped_feature_list_.InitAndDisableFeature(
-          features::kGuestViewCrossProcessFrames);
-    }
-  }
-
   // TODO(paulmeyer): This function is implemented over and over by the
   // different GuestView test classes. It really needs to be refactored out to
   // some kind of GuestViewTest base class.
@@ -142,27 +126,23 @@
   int basic_count_ = 0;
 };
 
-INSTANTIATE_TEST_CASE_P(MimeHandlerViewTests,
-                        MimeHandlerViewTest,
-                        testing::Bool());
-
-IN_PROC_BROWSER_TEST_P(MimeHandlerViewTest, PostMessage) {
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, PostMessage) {
   RunTest("test_postmessage.html");
 }
 
-IN_PROC_BROWSER_TEST_P(MimeHandlerViewTest, Basic) {
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, Basic) {
   RunTest("testBasic.csv");
 }
 
-IN_PROC_BROWSER_TEST_P(MimeHandlerViewTest, Embedded) {
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, Embedded) {
   RunTest("test_embedded.html");
 }
 
-IN_PROC_BROWSER_TEST_P(MimeHandlerViewTest, Iframe) {
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, Iframe) {
   RunTest("test_iframe.html");
 }
 
-IN_PROC_BROWSER_TEST_P(MimeHandlerViewTest, Abort) {
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, Abort) {
   if (base::FeatureList::IsEnabled(network::features::kNetworkService)) {
     // With the network service, abortStream isn't needed since we pass a Mojo
     // pipe to the renderer. If the plugin chooses to cancel the main request
@@ -177,28 +157,28 @@
   RunTest("testAbort.csv");
 }
 
-IN_PROC_BROWSER_TEST_P(MimeHandlerViewTest, NonAsciiHeaders) {
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, NonAsciiHeaders) {
   RunTest("testNonAsciiHeaders.csv");
 }
 
-IN_PROC_BROWSER_TEST_P(MimeHandlerViewTest, DataUrl) {
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, DataUrl) {
   const char* kDataUrlCsv = "data:text/csv;base64,Y29udGVudCB0byByZWFkCg==";
   RunTestWithUrl(GURL(kDataUrlCsv));
 }
 
-IN_PROC_BROWSER_TEST_P(MimeHandlerViewTest, EmbeddedDataUrlObject) {
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, EmbeddedDataUrlObject) {
   RunTest("test_embedded_data_url_object.html");
 }
 
-IN_PROC_BROWSER_TEST_P(MimeHandlerViewTest, EmbeddedDataUrlEmbed) {
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, EmbeddedDataUrlEmbed) {
   RunTest("test_embedded_data_url_embed.html");
 }
 
-IN_PROC_BROWSER_TEST_P(MimeHandlerViewTest, EmbeddedDataUrlLong) {
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, EmbeddedDataUrlLong) {
   RunTest("test_embedded_data_url_long.html");
 }
 
-IN_PROC_BROWSER_TEST_P(MimeHandlerViewTest, ResizeBeforeAttach) {
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, ResizeBeforeAttach) {
   // Delay the creation of the guest's WebContents in order to delay the guest's
   // attachment to the embedder. This will allow us to resize the <object> tag
   // after the guest is created, but before it is attached in
@@ -220,20 +200,20 @@
 }
 
 // Regression test for crbug.com/587709.
-IN_PROC_BROWSER_TEST_P(MimeHandlerViewTest, SingleRequest) {
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, SingleRequest) {
   GURL url(embedded_test_server()->GetURL("/testBasic.csv"));
   RunTest("testBasic.csv");
   EXPECT_EQ(1, basic_count());
 }
 
 // Test that a mime handler view can keep a background page alive.
-IN_PROC_BROWSER_TEST_P(MimeHandlerViewTest, BackgroundPage) {
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, BackgroundPage) {
   extensions::ProcessManager::SetEventPageIdleTimeForTesting(1);
   extensions::ProcessManager::SetEventPageSuspendingTimeForTesting(1);
   RunTest("testBackgroundPage.csv");
 }
 
-IN_PROC_BROWSER_TEST_P(MimeHandlerViewTest, TargetBlankAnchor) {
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, TargetBlankAnchor) {
   RunTest("testTargetBlankAnchor.csv");
   ASSERT_EQ(2, browser()->tab_strip_model()->count());
   content::WaitForLoadStop(browser()->tab_strip_model()->GetWebContentsAt(1));
@@ -242,7 +222,7 @@
       browser()->tab_strip_model()->GetWebContentsAt(1)->GetLastCommittedURL());
 }
 
-IN_PROC_BROWSER_TEST_P(MimeHandlerViewTest, BeforeUnload_NoDialog) {
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, BeforeUnload_NoDialog) {
   ASSERT_NO_FATAL_FAILURE(RunTest("testBeforeUnloadNoDialog.csv"));
   auto* web_contents = browser()->tab_strip_model()->GetWebContentsAt(0);
   content::PrepContentsForBeforeUnloadTest(web_contents);
@@ -257,7 +237,7 @@
   ui_test_utils::NavigateToURL(browser(), GURL("about:blank"));
 }
 
-IN_PROC_BROWSER_TEST_P(MimeHandlerViewTest, BeforeUnload_ShowDialog) {
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, BeforeUnload_ShowDialog) {
   ASSERT_NO_FATAL_FAILURE(RunTest("testBeforeUnloadShowDialog.csv"));
   auto* web_contents = browser()->tab_strip_model()->GetWebContentsAt(0);
   content::PrepContentsForBeforeUnloadTest(web_contents);
@@ -275,3 +255,58 @@
   EXPECT_FALSE(before_unload_dialog->is_reload());
   before_unload_dialog->OnAccept(base::string16(), false);
 }
+
+// TODO(mcnee): These tests are BrowserPlugin specific. Once
+// MimeHandlerViewGuest is no longer based on BrowserPlugin, remove these tests.
+// (See https://crbug.com/533069 and https://crbug.com/659750). These category
+// of tests are solely testing BrowserPlugin features.
+class MimeHandlerViewBrowserPluginSpecificTest : public MimeHandlerViewTest {
+ public:
+  MimeHandlerViewBrowserPluginSpecificTest() {}
+
+  ~MimeHandlerViewBrowserPluginSpecificTest() override {}
+
+ protected:
+  // None of these test create new tabs, so the embedder should be the first
+  // tab.
+  content::WebContents* GetEmbedderWebContents() {
+    return browser()->tab_strip_model()->GetWebContentsAt(0);
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(MimeHandlerViewBrowserPluginSpecificTest);
+};
+
+// This test verifies that when BrowserPlugin-based guest has touch handlers,
+// the embedder knows about it.
+IN_PROC_BROWSER_TEST_F(MimeHandlerViewBrowserPluginSpecificTest,
+                       AcceptTouchEvents) {
+  RunTest("testBasic.csv");
+  content::RenderViewHost* embedder_rvh =
+      GetEmbedderWebContents()->GetRenderViewHost();
+  bool embedder_has_touch_handler =
+      content::RenderViewHostTester::HasTouchEventHandler(embedder_rvh);
+  EXPECT_FALSE(embedder_has_touch_handler);
+
+  auto* guest_web_contents = GetGuestViewManager()->WaitForSingleGuestCreated();
+  ASSERT_TRUE(ExecuteScript(
+      guest_web_contents,
+      "document.addEventListener('touchstart', dummyTouchStartHandler);"));
+  // Wait until embedder has touch handlers.
+  while (!content::RenderViewHostTester::HasTouchEventHandler(embedder_rvh)) {
+    base::RunLoop run_loop;
+    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
+    run_loop.Run();
+  }
+
+  ASSERT_TRUE(ExecuteScript(
+      guest_web_contents,
+      "document.removeEventListener('touchstart', dummyTouchStartHandler);"));
+  // Wait until embedder not longer has any touch handlers.
+  while (content::RenderViewHostTester::HasTouchEventHandler(embedder_rvh)) {
+    base::RunLoop run_loop;
+    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
+    run_loop.Run();
+  }
+}
\ No newline at end of file
diff --git a/gpu/command_buffer/service/gl_context_virtual.cc b/gpu/command_buffer/service/gl_context_virtual.cc
index 86674b4..05980b0 100644
--- a/gpu/command_buffer/service/gl_context_virtual.cc
+++ b/gpu/command_buffer/service/gl_context_virtual.cc
@@ -110,10 +110,6 @@
 void GLContextVirtual::BackpressureFenceWait(uint64_t fence) {
   shared_context_->BackpressureFenceWait(fence);
 }
-
-void GLContextVirtual::FlushForDebugging() {
-  shared_context_->FlushForDebugging();
-}
 #endif
 
 GLContextVirtual::~GLContextVirtual() {
diff --git a/gpu/command_buffer/service/gl_context_virtual.h b/gpu/command_buffer/service/gl_context_virtual.h
index bdca6091..f0156ead 100644
--- a/gpu/command_buffer/service/gl_context_virtual.h
+++ b/gpu/command_buffer/service/gl_context_virtual.h
@@ -50,7 +50,6 @@
 #if defined(OS_MACOSX)
   uint64_t BackpressureFenceCreate() override;
   void BackpressureFenceWait(uint64_t fence) override;
-  void FlushForDebugging() override;
 #endif
 
  protected:
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index afd2203..3d451369 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -24,7 +24,6 @@
 #include "base/containers/queue.h"
 #include "base/containers/span.h"
 #include "base/debug/alias.h"
-#include "base/debug/crash_logging.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/numerics/ranges.h"
@@ -5618,14 +5617,6 @@
   int process_pos = 0;
   unsigned int command = 0;
 
-// Instrumentation for https://crbug.com/863817. Keep a list of all commands
-// that are flushed, to see what calls may be the culprit.
-#if defined(OS_MACOSX)
-  static auto* crash_key = base::debug::AllocateCrashKeyString(
-      "mac_gl_commands_in_flush", base::debug::CrashKeySize::Size256);
-  std::string crash_info;
-#endif
-
   while (process_pos < num_entries && result == error::kNoError &&
          commands_to_process_--) {
     const unsigned int size = cmd_data->value_header.size;
@@ -5641,19 +5632,6 @@
       break;
     }
 
-// Continued instrumentation for https://crbug.com/863817. If we run out of
-// buffer space then flush.
-#if defined(OS_MACOSX)
-    std::string command_name = GetCommandName(command);
-    if (!crash_info.empty() && crash_info.size() + command_name.size() > 256) {
-      TRACE_EVENT0("gpu", "Egregious Flush");
-      context_->FlushForDebugging();
-      crash_info = "";
-    }
-    crash_info += command_name + ",";
-    base::debug::SetCrashKeyString(crash_key, crash_info);
-#endif
-
     if (DebugImpl && log_commands()) {
       LOG(ERROR) << "[" << logger_.GetLogPrefix() << "]"
                  << "cmd: " << GetCommandName(command);
@@ -5712,15 +5690,6 @@
     }
   }
 
-// Continued instrumentation for https://crbug.com/863817
-#if defined(OS_MACOSX)
-  {
-    TRACE_EVENT0("gpu", "Egregious Flush");
-    context_->FlushForDebugging();
-    base::debug::ClearCrashKeyString(crash_key);
-  }
-#endif
-
   *entries_processed = process_pos;
 
   if (error::IsError(result)) {
diff --git a/infra/config/global/cr-buildbucket.cfg b/infra/config/global/cr-buildbucket.cfg
index 5de37e6..7e7cf81 100644
--- a/infra/config/global/cr-buildbucket.cfg
+++ b/infra/config/global/cr-buildbucket.cfg
@@ -1513,7 +1513,6 @@
     builders {
       name: "Android ChromeDriver Tests (dbg)"
       dimensions: "os:Ubuntu-14.04"
-      dimensions: "id:build91-b1"
       mixins: "fyi-ci"
     }
     builders {
@@ -1691,7 +1690,6 @@
     builders {
       name: "Android Remoting Tests"
       dimensions: "os:Ubuntu-14.04"
-      dimensions: "id:build65-b1"
       mixins: "fyi-ci"
     }
     builders {
@@ -2001,7 +1999,6 @@
     builders {
       name: "Android Find Annotated Test"
       dimensions: "os:Ubuntu-14.04"
-      dimensions: "id:build65-b1"
       mixins: "fyi-ci"
     }
     builders {
diff --git a/ios/chrome/browser/browser_state/BUILD.gn b/ios/chrome/browser/browser_state/BUILD.gn
index 7d2f9d9..2cb9cac 100644
--- a/ios/chrome/browser/browser_state/BUILD.gn
+++ b/ios/chrome/browser/browser_state/BUILD.gn
@@ -116,7 +116,6 @@
     "//ios/chrome/browser/ui/overlays",
     "//ios/chrome/browser/ui/voice",
     "//ios/chrome/browser/undo",
-    "//ios/chrome/browser/unified_consent:unified_consent",
     "//ios/net",
     "//ios/public/provider/chrome/browser",
     "//ios/public/provider/chrome/browser/signin",
diff --git a/ios/chrome/browser/browser_state/chrome_browser_state_manager_impl.cc b/ios/chrome/browser/browser_state/chrome_browser_state_manager_impl.cc
index 29a2a8f..0d77f27 100644
--- a/ios/chrome/browser/browser_state/chrome_browser_state_manager_impl.cc
+++ b/ios/chrome/browser/browser_state/chrome_browser_state_manager_impl.cc
@@ -41,7 +41,6 @@
 #include "ios/chrome/browser/signin/gaia_cookie_manager_service_factory.h"
 #include "ios/chrome/browser/signin/signin_manager_factory.h"
 #include "ios/chrome/browser/sync/profile_sync_service_factory.h"
-#include "ios/chrome/browser/unified_consent/unified_consent_service_factory.h"
 
 namespace {
 
@@ -224,9 +223,6 @@
   ios::AccountFetcherServiceFactory::GetForBrowserState(browser_state)
       ->SetupInvalidationsOnProfileLoad(invalidation_service);
   ios::AccountReconcilorFactory::GetForBrowserState(browser_state);
-  // Initialization needs to happen after the browser context is available
-  // because ProfileSyncService needs the URL context getter.
-  UnifiedConsentServiceFactory::GetForBrowserState(browser_state);
   DesktopPromotionSyncServiceFactory::GetForBrowserState(browser_state);
 }
 
diff --git a/ios/chrome/browser/ui/bookmarks/cells/BUILD.gn b/ios/chrome/browser/ui/bookmarks/cells/BUILD.gn
index 38351dee..f5b4fa4 100644
--- a/ios/chrome/browser/ui/bookmarks/cells/BUILD.gn
+++ b/ios/chrome/browser/ui/bookmarks/cells/BUILD.gn
@@ -24,6 +24,7 @@
 
   deps = [
     "//base",
+    "//base:i18n",
     "//components/bookmarks/browser:browser",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser",
diff --git a/ios/chrome/browser/ui/bookmarks/cells/bookmark_folder_item.mm b/ios/chrome/browser/ui/bookmarks/cells/bookmark_folder_item.mm
index faa783d..830816da 100644
--- a/ios/chrome/browser/ui/bookmarks/cells/bookmark_folder_item.mm
+++ b/ios/chrome/browser/ui/bookmarks/cells/bookmark_folder_item.mm
@@ -4,6 +4,7 @@
 
 #import "ios/chrome/browser/ui/bookmarks/cells/bookmark_folder_item.h"
 
+#include "base/i18n/rtl.h"
 #include "base/mac/foundation_util.h"
 #import "ios/chrome/browser/experimental_flags.h"
 #import "ios/chrome/browser/ui/bookmarks/bookmark_ui_constants.h"
@@ -202,10 +203,14 @@
       self.accessoryView = [[UIImageView alloc]
           initWithImage:[UIImage imageNamed:@"bookmark_blue_check"]];
       break;
-    case TableViewBookmarkFolderAccessoryTypeDisclosureIndicator:
+    case TableViewBookmarkFolderAccessoryTypeDisclosureIndicator: {
       self.accessoryView = [[UIImageView alloc]
           initWithImage:[UIImage imageNamed:@"table_view_cell_chevron"]];
+      // TODO(crbug.com/870841): Use default accessory type.
+      if (base::i18n::IsRTL())
+        self.accessoryView.transform = CGAffineTransformMakeRotation(M_PI);
       break;
+    }
     case TableViewBookmarkFolderAccessoryTypeNone:
       self.accessoryView = nil;
       break;
diff --git a/ios/chrome/browser/ui/bookmarks/cells/bookmark_parent_folder_item.mm b/ios/chrome/browser/ui/bookmarks/cells/bookmark_parent_folder_item.mm
index ad2bbf0..e88534ac 100644
--- a/ios/chrome/browser/ui/bookmarks/cells/bookmark_parent_folder_item.mm
+++ b/ios/chrome/browser/ui/bookmarks/cells/bookmark_parent_folder_item.mm
@@ -4,6 +4,7 @@
 
 #import "ios/chrome/browser/ui/bookmarks/cells/bookmark_parent_folder_item.h"
 
+#include "base/i18n/rtl.h"
 #include "base/mac/foundation_util.h"
 #import "ios/chrome/browser/experimental_flags.h"
 #import "ios/chrome/browser/ui/bookmarks/bookmark_ui_constants.h"
@@ -125,6 +126,9 @@
   UIImageView* navigationChevronImage = [[UIImageView alloc]
       initWithImage:[UIImage imageNamed:@"table_view_cell_chevron"]];
   self.accessoryView = navigationChevronImage;
+  // TODO(crbug.com/870841): Use default accessory type.
+  if (base::i18n::IsRTL())
+    self.accessoryView.transform = CGAffineTransformMakeRotation(M_PI);
 
   return self;
 }
diff --git a/ios/chrome/browser/ui/settings/BUILD.gn b/ios/chrome/browser/ui/settings/BUILD.gn
index 96712f5..2ec37d6 100644
--- a/ios/chrome/browser/ui/settings/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/BUILD.gn
@@ -114,6 +114,7 @@
     "resources:settings_search_engine",
     "resources:settings_sync",
     "resources:settings_voice_search",
+    "resources:sync_and_google_services",
     "//base",
     "//base:i18n",
     "//components/autofill/core/browser",
diff --git a/ios/chrome/browser/ui/settings/resources/BUILD.gn b/ios/chrome/browser/ui/settings/resources/BUILD.gn
index 610c042..da19098 100644
--- a/ios/chrome/browser/ui/settings/resources/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/resources/BUILD.gn
@@ -138,3 +138,12 @@
     "settings_voice_search.imageset/settings_voice_search@3x.png",
   ]
 }
+
+imageset("sync_and_google_services") {
+  sources = [
+    "sync_and_google_services.imageset/Contents.json",
+    "sync_and_google_services.imageset/sync_and_google_services.png",
+    "sync_and_google_services.imageset/sync_and_google_services@2x.png",
+    "sync_and_google_services.imageset/sync_and_google_services@3x.png",
+  ]
+}
diff --git a/ios/chrome/browser/ui/settings/resources/sync_and_google_services.imageset/Contents.json b/ios/chrome/browser/ui/settings/resources/sync_and_google_services.imageset/Contents.json
new file mode 100644
index 0000000..2db5ced
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/resources/sync_and_google_services.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+    "images": [
+        {
+            "idiom": "universal",
+            "scale": "1x",
+            "filename": "sync_and_google_services.png"
+        },
+        {
+            "idiom": "universal",
+            "scale": "2x",
+            "filename": "sync_and_google_services@2x.png"
+        },
+        {
+            "idiom": "universal",
+            "scale": "3x",
+            "filename": "sync_and_google_services@3x.png"
+        }
+    ],
+    "info": {
+        "version": 1,
+        "author": "xcode"
+    }
+}
diff --git a/ios/chrome/browser/ui/settings/resources/sync_and_google_services.imageset/sync_and_google_services.png b/ios/chrome/browser/ui/settings/resources/sync_and_google_services.imageset/sync_and_google_services.png
new file mode 100644
index 0000000..041cc34
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/resources/sync_and_google_services.imageset/sync_and_google_services.png
Binary files differ
diff --git a/ios/chrome/browser/ui/settings/resources/sync_and_google_services.imageset/sync_and_google_services@2x.png b/ios/chrome/browser/ui/settings/resources/sync_and_google_services.imageset/sync_and_google_services@2x.png
new file mode 100644
index 0000000..1309597
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/resources/sync_and_google_services.imageset/sync_and_google_services@2x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/settings/resources/sync_and_google_services.imageset/sync_and_google_services@3x.png b/ios/chrome/browser/ui/settings/resources/sync_and_google_services.imageset/sync_and_google_services@3x.png
new file mode 100644
index 0000000..68a2857
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/resources/sync_and_google_services.imageset/sync_and_google_services@3x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/settings/settings_collection_view_controller.mm b/ios/chrome/browser/ui/settings/settings_collection_view_controller.mm
index 14391b9..144ca95 100644
--- a/ios/chrome/browser/ui/settings/settings_collection_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/settings_collection_view_controller.mm
@@ -93,6 +93,20 @@
 
 const CGFloat kAccountProfilePhotoDimension = 40.0f;
 
+NSString* const kSyncAndGoogleServicesImageName = @"sync_and_google_services";
+NSString* const kSettingsSearchEngineImageName = @"settings_search_engine";
+NSString* const kSettingsPasswordsImageName = @"settings_passwords";
+NSString* const kSettingsAutofillFormsImageName = @"settings_autofill_forms";
+NSString* const kSettingsVoiceSearchImageName = @"settings_voice_search";
+NSString* const kSettingsPrivacyImageName = @"settings_privacy";
+NSString* const kSettingsContentSettingsImageName =
+    @"settings_content_settings";
+NSString* const kSettingsBandwidthImageName = @"settings_bandwidth";
+NSString* const kSettingsAboutChromeImageName = @"settings_about_chrome";
+NSString* const kSettingsDebugImageName = @"settings_debug";
+NSString* const kSettingsArticleSuggestionsImageName =
+    @"settings_article_suggestions";
+
 typedef NS_ENUM(NSInteger, SectionIdentifier) {
   SectionIdentifierSignIn = kSectionIdentifierEnumZero,
   SectionIdentifierAccount,
@@ -506,7 +520,7 @@
                              text:l10n_util::GetNSString(
                                       IDS_IOS_GOOGLE_SERVICES_SETTINGS_TITLE)
                        detailText:nil
-                    iconImageName:nil];
+                    iconImageName:kSyncAndGoogleServicesImageName];
 }
 
 - (CollectionViewItem*)accountCellItem {
@@ -530,7 +544,7 @@
                           text:l10n_util::GetNSString(
                                    IDS_IOS_SEARCH_ENGINE_SETTING_TITLE)
                     detailText:defaultSearchEngineName
-                 iconImageName:@"settings_search_engine"];
+                 iconImageName:kSettingsSearchEngineImageName];
   _defaultSearchEngineItem.accessibilityIdentifier =
       kSettingsSearchEngineCellId;
   return _defaultSearchEngineItem;
@@ -541,7 +555,7 @@
       [self detailItemWithType:ItemTypeSavedPasswords
                           text:l10n_util::GetNSString(IDS_IOS_PASSWORDS)
                     detailText:nil
-                 iconImageName:@"settings_passwords"];
+                 iconImageName:kSettingsPasswordsImageName];
 
   return _savePasswordsDetailItem;
 }
@@ -556,7 +570,7 @@
       [self detailItemWithType:ItemTypeAutofill
                           text:l10n_util::GetNSString(IDS_IOS_AUTOFILL)
                     detailText:autofillDetail
-                 iconImageName:@"settings_autofill_forms"];
+                 iconImageName:kSettingsAutofillFormsImageName];
 
   return _autoFillDetailItem;
 }
@@ -574,7 +588,7 @@
                           text:l10n_util::GetNSString(
                                    IDS_IOS_VOICE_SEARCH_SETTING_TITLE)
                     detailText:languageName
-                 iconImageName:@"settings_voice_search"];
+                 iconImageName:kSettingsVoiceSearchImageName];
   _voiceSearchDetailItem.accessibilityIdentifier = kSettingsVoiceSearchCellId;
   return _voiceSearchDetailItem;
 }
@@ -585,7 +599,7 @@
                           text:l10n_util::GetNSString(
                                    IDS_OPTIONS_ADVANCED_SECTION_TITLE_PRIVACY)
                     detailText:nil
-                 iconImageName:@"settings_privacy"];
+                 iconImageName:kSettingsPrivacyImageName];
 }
 
 - (CollectionViewItem*)contentSettingsDetailItem {
@@ -593,7 +607,7 @@
       detailItemWithType:ItemTypeContentSettings
                     text:l10n_util::GetNSString(IDS_IOS_CONTENT_SETTINGS_TITLE)
               detailText:nil
-           iconImageName:@"settings_content_settings"];
+           iconImageName:kSettingsContentSettingsImageName];
 }
 
 - (CollectionViewItem*)bandwidthManagementDetailItem {
@@ -601,21 +615,21 @@
                              text:l10n_util::GetNSString(
                                       IDS_IOS_BANDWIDTH_MANAGEMENT_SETTINGS)
                        detailText:nil
-                    iconImageName:@"settings_bandwidth"];
+                    iconImageName:kSettingsBandwidthImageName];
 }
 
 - (CollectionViewItem*)aboutChromeDetailItem {
   return [self detailItemWithType:ItemTypeAboutChrome
                              text:l10n_util::GetNSString(IDS_IOS_PRODUCT_NAME)
                        detailText:nil
-                    iconImageName:@"settings_about_chrome"];
+                    iconImageName:kSettingsAboutChromeImageName];
 }
 
 - (SettingsSwitchItem*)showMemoryDebugSwitchItem {
   SettingsSwitchItem* showMemoryDebugSwitchItem =
       [self switchItemWithType:ItemTypeMemoryDebugging
                          title:@"Show memory debug tools"
-                 iconImageName:@"settings_debug"
+                 iconImageName:kSettingsDebugImageName
                withDefaultsKey:nil];
   showMemoryDebugSwitchItem.on = [_showMemoryDebugToolsEnabled value];
 
@@ -627,7 +641,7 @@
       [self switchItemWithType:ItemTypeArticlesForYou
                          title:l10n_util::GetNSString(
                                    IDS_IOS_CONTENT_SUGGESTIONS_SETTING_TITLE)
-                 iconImageName:@"settings_article_suggestions"
+                 iconImageName:kSettingsArticleSuggestionsImageName
                withDefaultsKey:nil];
   articlesForYouSwitchItem.on = [_articlesEnabled value];
 
@@ -638,14 +652,14 @@
 - (SettingsSwitchItem*)viewSourceSwitchItem {
   return [self switchItemWithType:ItemTypeViewSource
                             title:@"View source menu"
-                    iconImageName:@"settings_debug"
+                    iconImageName:kSettingsDebugImageName
                   withDefaultsKey:kDevViewSourceKey];
 }
 
 - (SettingsSwitchItem*)logJavascriptConsoleSwitchItem {
   return [self switchItemWithType:ItemTypeLogJavascript
                             title:@"Log JS"
-                    iconImageName:@"settings_debug"
+                    iconImageName:kSettingsDebugImageName
                   withDefaultsKey:kLogJavascriptKey];
 }
 
@@ -653,14 +667,14 @@
   return [self detailItemWithType:ItemTypeCollectionCellCatalog
                              text:@"Collection Cell Catalog"
                        detailText:nil
-                    iconImageName:@"settings_debug"];
+                    iconImageName:kSettingsDebugImageName];
 }
 
 - (SettingsDetailItem*)tableViewCatalogDetailItem {
   return [self detailItemWithType:ItemTypeTableCellCatalog
                              text:@"TableView Cell Catalog"
                        detailText:nil
-                    iconImageName:@"settings_debug"];
+                    iconImageName:kSettingsDebugImageName];
 }
 #endif  // CHROMIUM_BUILD && !defined(NDEBUG)
 
diff --git a/ios/chrome/browser/ui/tab_grid/grid/grid_layout.h b/ios/chrome/browser/ui/tab_grid/grid/grid_layout.h
index 8668078..54c0844 100644
--- a/ios/chrome/browser/ui/tab_grid/grid/grid_layout.h
+++ b/ios/chrome/browser/ui/tab_grid/grid/grid_layout.h
@@ -11,6 +11,10 @@
 // square-ish. Item sizes adapt to the size classes they are shown in. Item
 // deletions are animated.
 @interface GridLayout : UICollectionViewFlowLayout
+
+// Whether to animate item insertions and deletions.
+@property(nonatomic, assign) BOOL animatesItemUpdates;
+
 @end
 
 // A specialization of GridLayout that shows the UI in its "reordering" state,
diff --git a/ios/chrome/browser/ui/tab_grid/grid/grid_layout.mm b/ios/chrome/browser/ui/tab_grid/grid/grid_layout.mm
index 8fe4fb62..16895e7 100644
--- a/ios/chrome/browser/ui/tab_grid/grid/grid_layout.mm
+++ b/ios/chrome/browser/ui/tab_grid/grid/grid_layout.mm
@@ -16,9 +16,17 @@
 @end
 
 @implementation GridLayout
+@synthesize animatesItemUpdates = _animatesItemUpdates;
 @synthesize indexPathsOfDeletingItems = _indexPathsOfDeletingItems;
 @synthesize indexPathsOfInsertingItems = _indexPathsOfInsertingItems;
 
+- (instancetype)init {
+  if (self = [super init]) {
+    _animatesItemUpdates = YES;
+  }
+  return self;
+}
+
 #pragma mark - UICollectionViewLayout
 
 // This is called whenever the layout is invalidated, including during rotation.
@@ -93,6 +101,10 @@
 - (UICollectionViewLayoutAttributes*)
 finalLayoutAttributesForDisappearingItemAtIndexPath:
     (NSIndexPath*)itemIndexPath {
+  // Return initial layout if animations are disabled.
+  if (!self.animatesItemUpdates) {
+    return [self layoutAttributesForItemAtIndexPath:itemIndexPath];
+  }
   // Note that this method is called for any item whose index path changing from
   // |itemIndexPath|, which includes any items that were in the layout and whose
   // index path is changing. For an item whose index path is changing, this
@@ -120,6 +132,10 @@
 
 - (UICollectionViewLayoutAttributes*)
 initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath*)itemIndexPath {
+  // Return final layout if animations are disabled.
+  if (!self.animatesItemUpdates) {
+    return [self layoutAttributesForItemAtIndexPath:itemIndexPath];
+  }
   // Note that this method is called for any item whose index path is becoming
   // |itemIndexPath|, which includes any items that were in the layout but whose
   // index path is changing. For an item whose index path is changing, this
diff --git a/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.h b/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.h
index d7257c3..42665fa5 100644
--- a/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.h
+++ b/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.h
@@ -59,6 +59,9 @@
 // Returns the layout of the grid for use in an animated transition.
 - (GridTransitionLayout*)transitionLayout;
 
+// Notifies the grid that it is about to be dismissed.
+- (void)prepareForDismissal;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_TAB_GRID_GRID_GRID_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.mm b/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.mm
index e978996..1d101c0 100644
--- a/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.mm
+++ b/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.mm
@@ -64,7 +64,7 @@
 // Animator to show or hide the empty state.
 @property(nonatomic, strong) UIViewPropertyAnimator* emptyStateAnimator;
 // The default layout for the tab grid.
-@property(nonatomic, strong) UICollectionViewLayout* defaultLayout;
+@property(nonatomic, strong) GridLayout* defaultLayout;
 // The layout used while the grid is being reordered.
 @property(nonatomic, strong) UICollectionViewLayout* reorderingLayout;
 
@@ -142,6 +142,7 @@
 - (void)viewWillAppear:(BOOL)animated {
   [super viewWillAppear:animated];
   self.updatesCollectionView = YES;
+  self.defaultLayout.animatesItemUpdates = YES;
   [self.collectionView reloadData];
   // Selection is invalid if there are no items.
   if (self.items.count == 0) {
@@ -243,6 +244,12 @@
                                          selectionItem:selectionItem];
 }
 
+- (void)prepareForDismissal {
+  // Stop animating the collection view to prevent the insertion animation from
+  // interfering with the tab presentation animation.
+  self.defaultLayout.animatesItemUpdates = NO;
+}
+
 #pragma mark - UICollectionViewDataSource
 
 - (NSInteger)collectionView:(UICollectionView*)collectionView
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 a01feb2..d633c9c5 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
@@ -987,6 +987,7 @@
 - (void)openNewTabInPage:(TabGridPage)page focusOmnibox:(BOOL)focusOmnibox {
   switch (page) {
     case TabGridPageIncognitoTabs:
+      [self.incognitoTabsViewController prepareForDismissal];
       [self.incognitoTabsDelegate addNewItem];
       // Record when new incognito tab is created.
       // TODO(crbug.com/856965) : Rename metrics.
@@ -994,6 +995,7 @@
           base::UserMetricsAction("MobileTabSwitcherCreateIncognitoTab"));
       break;
     case TabGridPageRegularTabs:
+      [self.regularTabsViewController prepareForDismissal];
       [self.regularTabsDelegate addNewItem];
       // Record when new regular tab is created.
       // TODO(crbug.com/856965) : Rename metrics.
diff --git a/ios/chrome/browser/ui/table_view/cells/BUILD.gn b/ios/chrome/browser/ui/table_view/cells/BUILD.gn
index 5a5d5d64..cb18582 100644
--- a/ios/chrome/browser/ui/table_view/cells/BUILD.gn
+++ b/ios/chrome/browser/ui/table_view/cells/BUILD.gn
@@ -36,6 +36,7 @@
     "resources:table_view_cell_chevron",
     "resources:table_view_cell_favicon_background",
     "//base",
+    "//base:i18n",
     "//ios/chrome/browser/ui:ui_util",
     "//ios/chrome/browser/ui/authentication:authentication_ui",
     "//ios/chrome/browser/ui/colors:colors",
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_accessory_item.mm b/ios/chrome/browser/ui/table_view/cells/table_view_accessory_item.mm
index 61949ba..a4ff17e4 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_accessory_item.mm
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_accessory_item.mm
@@ -4,6 +4,7 @@
 
 #import "ios/chrome/browser/ui/table_view/cells/table_view_accessory_item.h"
 
+#include "base/i18n/rtl.h"
 #include "base/mac/foundation_util.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h"
 #import "ios/chrome/browser/ui/table_view/chrome_table_view_styler.h"
@@ -76,6 +77,9 @@
     [disclosureImageView
         setContentHuggingPriority:UILayoutPriorityDefaultHigh
                           forAxis:UILayoutConstraintAxisHorizontal];
+    // TODO(crbug.com/870841): Use default accessory type.
+    if (base::i18n::IsRTL())
+      disclosureImageView.transform = CGAffineTransformMakeRotation(M_PI);
 
     // Horizontal stack view holds imageView, title, and disclosureView.
     UIStackView* horizontalStack =
diff --git a/media/mojo/interfaces/OWNERS b/media/mojo/interfaces/OWNERS
index 2c44a46..8e9e507b 100644
--- a/media/mojo/interfaces/OWNERS
+++ b/media/mojo/interfaces/OWNERS
@@ -1,5 +1,7 @@
 per-file *.mojom=set noparent
 per-file *.mojom=file://ipc/SECURITY_OWNERS
+per-file *_mojom_traits*.*=set noparent
+per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
 per-file *_struct_traits*.*=set noparent
 per-file *_struct_traits*.*=file://ipc/SECURITY_OWNERS
 per-file *.typemap=set noparent
diff --git a/media/mojo/interfaces/jpeg_decode_accelerator.typemap b/media/mojo/interfaces/jpeg_decode_accelerator.typemap
index b3e3b989..9f647c6 100644
--- a/media/mojo/interfaces/jpeg_decode_accelerator.typemap
+++ b/media/mojo/interfaces/jpeg_decode_accelerator.typemap
@@ -10,10 +10,10 @@
 ]
 
 traits_headers =
-    [ "//media/mojo/interfaces/jpeg_decode_accelerator_typemap_traits.h" ]
+    [ "//media/mojo/interfaces/jpeg_decode_accelerator_mojom_traits.h" ]
 
 sources = [
-  "//media/mojo/interfaces/jpeg_decode_accelerator_typemap_traits.cc",
+  "//media/mojo/interfaces/jpeg_decode_accelerator_mojom_traits.cc",
 ]
 
 deps = [
diff --git a/media/mojo/interfaces/jpeg_decode_accelerator_typemap_traits.cc b/media/mojo/interfaces/jpeg_decode_accelerator_mojom_traits.cc
similarity index 98%
rename from media/mojo/interfaces/jpeg_decode_accelerator_typemap_traits.cc
rename to media/mojo/interfaces/jpeg_decode_accelerator_mojom_traits.cc
index 3297e8f..8584483 100644
--- a/media/mojo/interfaces/jpeg_decode_accelerator_typemap_traits.cc
+++ b/media/mojo/interfaces/jpeg_decode_accelerator_mojom_traits.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "media/mojo/interfaces/jpeg_decode_accelerator_typemap_traits.h"
+#include "media/mojo/interfaces/jpeg_decode_accelerator_mojom_traits.h"
 
 #include "base/logging.h"
 #include "media/base/ipc/media_param_traits_macros.h"
diff --git a/media/mojo/interfaces/jpeg_decode_accelerator_typemap_traits.h b/media/mojo/interfaces/jpeg_decode_accelerator_mojom_traits.h
similarity index 89%
rename from media/mojo/interfaces/jpeg_decode_accelerator_typemap_traits.h
rename to media/mojo/interfaces/jpeg_decode_accelerator_mojom_traits.h
index 3b5fd31b..eb85f18b 100644
--- a/media/mojo/interfaces/jpeg_decode_accelerator_typemap_traits.h
+++ b/media/mojo/interfaces/jpeg_decode_accelerator_mojom_traits.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef MEDIA_MOJO_INTERFACES_JPEG_DECODE_ACCELERATOR_TYPEMAP_TRAITS_H_
-#define MEDIA_MOJO_INTERFACES_JPEG_DECODE_ACCELERATOR_TYPEMAP_TRAITS_H_
+#ifndef MEDIA_MOJO_INTERFACES_JPEG_DECODE_ACCELERATOR_MOJOM_TRAITS_H_
+#define MEDIA_MOJO_INTERFACES_JPEG_DECODE_ACCELERATOR_MOJOM_TRAITS_H_
 
 #include "base/numerics/safe_conversions.h"
 #include "media/base/bitstream_buffer.h"
@@ -61,4 +61,4 @@
 
 }  // namespace mojo
 
-#endif  // MEDIA_MOJO_INTERFACES_JPEG_DECODE_ACCELERATOR_TYPEMAP_TRAITS_H_
+#endif  // MEDIA_MOJO_INTERFACES_JPEG_DECODE_ACCELERATOR_MOJOM_TRAITS_H_
diff --git a/media/mojo/interfaces/jpeg_encode_accelerator.typemap b/media/mojo/interfaces/jpeg_encode_accelerator.typemap
index 04d83a8d..b5b310b 100644
--- a/media/mojo/interfaces/jpeg_encode_accelerator.typemap
+++ b/media/mojo/interfaces/jpeg_encode_accelerator.typemap
@@ -7,10 +7,10 @@
 public_headers = [ "//media/video/jpeg_encode_accelerator.h" ]
 
 traits_headers =
-    [ "//media/mojo/interfaces/jpeg_encode_accelerator_typemap_traits.h" ]
+    [ "//media/mojo/interfaces/jpeg_encode_accelerator_mojom_traits.h" ]
 
 sources = [
-  "//media/mojo/interfaces/jpeg_encode_accelerator_typemap_traits.cc",
+  "//media/mojo/interfaces/jpeg_encode_accelerator_mojom_traits.cc",
 ]
 
 deps = [
diff --git a/media/mojo/interfaces/jpeg_encode_accelerator_typemap_traits.cc b/media/mojo/interfaces/jpeg_encode_accelerator_mojom_traits.cc
similarity index 96%
rename from media/mojo/interfaces/jpeg_encode_accelerator_typemap_traits.cc
rename to media/mojo/interfaces/jpeg_encode_accelerator_mojom_traits.cc
index ba38fd1..1e5a6a4 100644
--- a/media/mojo/interfaces/jpeg_encode_accelerator_typemap_traits.cc
+++ b/media/mojo/interfaces/jpeg_encode_accelerator_mojom_traits.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "media/mojo/interfaces/jpeg_encode_accelerator_typemap_traits.h"
+#include "media/mojo/interfaces/jpeg_encode_accelerator_mojom_traits.h"
 
 #include "base/logging.h"
 
diff --git a/media/mojo/interfaces/jpeg_encode_accelerator_typemap_traits.h b/media/mojo/interfaces/jpeg_encode_accelerator_mojom_traits.h
similarity index 75%
rename from media/mojo/interfaces/jpeg_encode_accelerator_typemap_traits.h
rename to media/mojo/interfaces/jpeg_encode_accelerator_mojom_traits.h
index 6257881..19eb26a 100644
--- a/media/mojo/interfaces/jpeg_encode_accelerator_typemap_traits.h
+++ b/media/mojo/interfaces/jpeg_encode_accelerator_mojom_traits.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef MEDIA_MOJO_INTERFACES_JPEG_ENCODE_ACCELERATOR_TYPEMAP_TRAITS_H_
-#define MEDIA_MOJO_INTERFACES_JPEG_ENCODE_ACCELERATOR_TYPEMAP_TRAITS_H_
+#ifndef MEDIA_MOJO_INTERFACES_JPEG_ENCODE_ACCELERATOR_MOJOM_TRAITS_H_
+#define MEDIA_MOJO_INTERFACES_JPEG_ENCODE_ACCELERATOR_MOJOM_TRAITS_H_
 
 #include "media/mojo/interfaces/jpeg_encode_accelerator.mojom.h"
 #include "media/video/jpeg_encode_accelerator.h"
@@ -22,4 +22,4 @@
 
 }  // namespace mojo
 
-#endif  // MEDIA_MOJO_INTERFACES_JPEG_ENCODE_ACCELERATOR_TYPEMAP_TRAITS_H_
+#endif  // MEDIA_MOJO_INTERFACES_JPEG_ENCODE_ACCELERATOR_MOJOM_TRAITS_H_
diff --git a/media/mojo/interfaces/video_encode_accelerator.typemap b/media/mojo/interfaces/video_encode_accelerator.typemap
index 6d0f4c3..24a7712f 100644
--- a/media/mojo/interfaces/video_encode_accelerator.typemap
+++ b/media/mojo/interfaces/video_encode_accelerator.typemap
@@ -7,11 +7,11 @@
 public_headers = [ "//media/video/video_encode_accelerator.h" ]
 
 traits_headers =
-    [ "//media/mojo/interfaces/video_encode_accelerator_typemap_traits.h" ]
+    [ "//media/mojo/interfaces/video_encode_accelerator_mojom_traits.h" ]
 
 sources = [
-  "//media/mojo/interfaces/video_encode_accelerator_typemap_traits.cc",
-  "//media/mojo/interfaces/video_encode_accelerator_typemap_traits.h",
+  "//media/mojo/interfaces/video_encode_accelerator_mojom_traits.cc",
+  "//media/mojo/interfaces/video_encode_accelerator_mojom_traits.h",
 ]
 
 public_deps = [
diff --git a/media/mojo/interfaces/video_encode_accelerator_typemap_traits.cc b/media/mojo/interfaces/video_encode_accelerator_mojom_traits.cc
similarity index 98%
rename from media/mojo/interfaces/video_encode_accelerator_typemap_traits.cc
rename to media/mojo/interfaces/video_encode_accelerator_mojom_traits.cc
index a95c177..b80b0100 100644
--- a/media/mojo/interfaces/video_encode_accelerator_typemap_traits.cc
+++ b/media/mojo/interfaces/video_encode_accelerator_mojom_traits.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "media/mojo/interfaces/video_encode_accelerator_typemap_traits.h"
+#include "media/mojo/interfaces/video_encode_accelerator_mojom_traits.h"
 
 #include "base/logging.h"
 #include "base/optional.h"
diff --git a/media/mojo/interfaces/video_encode_accelerator_typemap_traits.h b/media/mojo/interfaces/video_encode_accelerator_mojom_traits.h
similarity index 93%
rename from media/mojo/interfaces/video_encode_accelerator_typemap_traits.h
rename to media/mojo/interfaces/video_encode_accelerator_mojom_traits.h
index bc85ca2..13c26c2a 100644
--- a/media/mojo/interfaces/video_encode_accelerator_typemap_traits.h
+++ b/media/mojo/interfaces/video_encode_accelerator_mojom_traits.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef MEDIA_MOJO_INTERFACES_VIDEO_ENCODE_ACCELERATOR_TYPEMAP_TRAITS_H_
-#define MEDIA_MOJO_INTERFACES_VIDEO_ENCODE_ACCELERATOR_TYPEMAP_TRAITS_H_
+#ifndef MEDIA_MOJO_INTERFACES_VIDEO_ENCODE_ACCELERATOR_MOJOM_TRAITS_H_
+#define MEDIA_MOJO_INTERFACES_VIDEO_ENCODE_ACCELERATOR_MOJOM_TRAITS_H_
 
 #include "media/base/ipc/media_param_traits.h"
 #include "media/mojo/interfaces/media_types.mojom.h"
@@ -114,4 +114,4 @@
 
 }  // namespace mojo
 
-#endif  // MEDIA_MOJO_INTERFACES_VIDEO_ENCODE_ACCELERATOR_TYPEMAP_TRAITS_H_
+#endif  // MEDIA_MOJO_INTERFACES_VIDEO_ENCODE_ACCELERATOR_MOJOM_TRAITS_H_
diff --git a/remoting/android/java/src/org/chromium/chromoting/OAuthTokenConsumer.java b/remoting/android/java/src/org/chromium/chromoting/OAuthTokenConsumer.java
index 8d55278..3eabbfa 100644
--- a/remoting/android/java/src/org/chromium/chromoting/OAuthTokenConsumer.java
+++ b/remoting/android/java/src/org/chromium/chromoting/OAuthTokenConsumer.java
@@ -122,7 +122,7 @@
                 return null;
             }
         }
-                .execute();
+                .executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
     }
 
     private void handleErrorOnMainThread(final OAuthTokenFetcher.Callback callback,
diff --git a/remoting/android/java/src/org/chromium/chromoting/base/OAuthTokenFetcher.java b/remoting/android/java/src/org/chromium/chromoting/base/OAuthTokenFetcher.java
index ee6e31868..5b97493 100644
--- a/remoting/android/java/src/org/chromium/chromoting/base/OAuthTokenFetcher.java
+++ b/remoting/android/java/src/org/chromium/chromoting/base/OAuthTokenFetcher.java
@@ -116,7 +116,7 @@
                 return null;
             }
         }
-                .execute();
+                .executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
     }
 
     private void handleTokenReceived(final String token) {
diff --git a/remoting/client/audio/BUILD.gn b/remoting/client/audio/BUILD.gn
index f5faa7ae..63571057 100644
--- a/remoting/client/audio/BUILD.gn
+++ b/remoting/client/audio/BUILD.gn
@@ -4,8 +4,12 @@
 
 source_set("audio") {
   sources = [
+    "async_audio_data_supplier.cc",
+    "async_audio_data_supplier.h",
     "async_audio_frame_supplier.h",
     "audio_frame_supplier.h",
+    "audio_jitter_buffer.cc",
+    "audio_jitter_buffer.h",
     "audio_player.cc",
     "audio_player.h",
     "audio_player_android.cc",
@@ -37,6 +41,7 @@
   testonly = true
 
   sources = [
+    "audio_jitter_buffer_unittest.cc",
     "audio_player_buffer_unittest.cc",
     "audio_player_unittest.cc",
   ]
diff --git a/remoting/client/audio/async_audio_data_supplier.cc b/remoting/client/audio/async_audio_data_supplier.cc
new file mode 100644
index 0000000..7f25f49
--- /dev/null
+++ b/remoting/client/audio/async_audio_data_supplier.cc
@@ -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.
+
+#include "remoting/client/audio/async_audio_data_supplier.h"
+
+#include "base/logging.h"
+
+namespace remoting {
+
+AsyncAudioDataSupplier::GetDataRequest::GetDataRequest(void* data_arg,
+                                                       size_t bytes_needed_arg)
+    : data(data_arg), bytes_needed(bytes_needed_arg) {
+  DCHECK(data);
+  DCHECK_GT(bytes_needed, 0u);
+}
+
+AsyncAudioDataSupplier::GetDataRequest::~GetDataRequest() = default;
+
+AsyncAudioDataSupplier::AsyncAudioDataSupplier() = default;
+
+AsyncAudioDataSupplier::~AsyncAudioDataSupplier() = default;
+
+}  // namespace remoting
diff --git a/remoting/client/audio/async_audio_data_supplier.h b/remoting/client/audio/async_audio_data_supplier.h
new file mode 100644
index 0000000..eed0b7d
--- /dev/null
+++ b/remoting/client/audio/async_audio_data_supplier.h
@@ -0,0 +1,46 @@
+// 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 REMOTING_CLIENT_AUDIO_ASYNC_AUDIO_DATA_SUPPLIER_H_
+#define REMOTING_CLIENT_AUDIO_ASYNC_AUDIO_DATA_SUPPLIER_H_
+
+#include <memory>
+
+namespace remoting {
+
+// This interface allows caller to asynchronously request for audio data.
+class AsyncAudioDataSupplier {
+ public:
+  struct GetDataRequest {
+    // |data| must outlive |this|.
+    GetDataRequest(void* data, size_t bytes_needed);
+    virtual ~GetDataRequest();
+
+    // Called when |data| has been filled with |bytes_needed| bytes of data.
+    //
+    // Caution: Do not add or drop requests (i.e. calling AsyncGetData() or
+    // ClearGetDataRequests()) directly inside OnDataFilled(), which has
+    // undefined behavior. Consider posting a task when necessary.
+    virtual void OnDataFilled() = 0;
+
+    void* const data;
+    const size_t bytes_needed;
+
+    size_t bytes_extracted = 0;
+  };
+
+  AsyncAudioDataSupplier();
+  virtual ~AsyncAudioDataSupplier();
+
+  // Requests for more data from the supplier.
+  virtual void AsyncGetData(std::unique_ptr<GetDataRequest> request) = 0;
+
+  // Drops all pending get-data requests. You may want to call this before the
+  // caller is destroyed if the caller has a shorter lifetime than the supplier.
+  virtual void ClearGetDataRequests() = 0;
+};
+
+}  // namespace remoting
+
+#endif  // REMOTING_CLIENT_AUDIO_ASYNC_AUDIO_DATA_SUPPLIER_H_
diff --git a/remoting/client/audio/audio_jitter_buffer.cc b/remoting/client/audio/audio_jitter_buffer.cc
new file mode 100644
index 0000000..c7fe0cd
--- /dev/null
+++ b/remoting/client/audio/audio_jitter_buffer.cc
@@ -0,0 +1,194 @@
+// 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 "remoting/client/audio/audio_jitter_buffer.h"
+
+#include <algorithm>
+#include <string>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+
+namespace {
+
+// AudioJitterBuffer maintains a list of AudioPackets whose total playback
+// duration <= |kMaxQueueLatency|.
+// Once the buffer has run out of AudioPackets (latency reaches 0), it waits
+// until the total latency reaches |kUnderrunRecoveryLatency| before it starts
+// feeding the get-data requests. This helps reduce the frequency of stopping
+// when the buffer underruns.
+// If the total latency has reached |kMaxQueueLatency|, the oldest packets
+// will get dropped until the latency is reduced to no more than
+// |kOverrunRecoveryLatency|. This helps reduce the number of glitches when
+// the buffer overruns.
+// Otherwise the total latency can freely fluctuate between 0 and
+// |kMaxQueueLatency|.
+
+constexpr base::TimeDelta kMaxQueueLatency =
+    base::TimeDelta::FromMilliseconds(150);
+constexpr base::TimeDelta kUnderrunRecoveryLatency =
+    base::TimeDelta::FromMilliseconds(60);
+constexpr base::TimeDelta kOverrunRecoveryLatency =
+    base::TimeDelta::FromMilliseconds(90);
+
+}  // namespace
+
+namespace remoting {
+
+bool AudioJitterBuffer::StreamFormat::operator==(
+    const StreamFormat& other) const {
+  return bytes_per_sample == other.bytes_per_sample &&
+         channels == other.channels && sample_rate == other.sample_rate;
+}
+
+bool AudioJitterBuffer::StreamFormat::operator!=(
+    const StreamFormat& other) const {
+  return !(*this == other);
+}
+
+AudioJitterBuffer::AudioJitterBuffer(
+    OnFormatChangedCallback on_format_changed) {
+  DETACH_FROM_THREAD(thread_checker_);
+  on_format_changed_ = std::move(on_format_changed);
+}
+
+AudioJitterBuffer::~AudioJitterBuffer() = default;
+
+void AudioJitterBuffer::AddAudioPacket(std::unique_ptr<AudioPacket> packet) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  CHECK_EQ(1, packet->data_size());
+  DCHECK_EQ(AudioPacket::ENCODING_RAW, packet->encoding());
+  DCHECK_NE(AudioPacket::SAMPLING_RATE_INVALID, packet->sampling_rate());
+
+  StreamFormat stream_format;
+  stream_format.bytes_per_sample = packet->bytes_per_sample();
+  stream_format.channels = packet->channels();
+  stream_format.sample_rate = packet->sampling_rate();
+
+  DCHECK_GT(stream_format.bytes_per_sample, 0);
+  DCHECK_GT(stream_format.channels, 0);
+  DCHECK_GT(stream_format.sample_rate, 0);
+  DCHECK_EQ(packet->data(0).size() %
+                (stream_format.channels * stream_format.bytes_per_sample),
+            0u);
+
+  if (!stream_format_ || *stream_format_ != stream_format) {
+    ResetBuffer(stream_format);
+  }
+
+  // Push the new data to the back of the queue.
+  queued_bytes_ += packet->data(0).size();
+  queued_packets_.push_back(std::move(packet));
+
+  if (underrun_protection_mode_ &&
+      queued_bytes_ > GetBufferSizeFromTime(kUnderrunRecoveryLatency)) {
+    // The buffer has enough data to start feeding the requests.
+    underrun_protection_mode_ = false;
+  }
+
+  DropOverrunPackets();
+  ProcessGetDataRequests();
+}
+
+void AudioJitterBuffer::AsyncGetData(std::unique_ptr<GetDataRequest> request) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(stream_format_);
+  DCHECK_EQ(request->bytes_needed % stream_format_->bytes_per_sample, 0u);
+  queued_requests_.push_back(std::move(request));
+  ProcessGetDataRequests();
+}
+
+void AudioJitterBuffer::ClearGetDataRequests() {
+  queued_requests_.clear();
+}
+
+void AudioJitterBuffer::ResetBuffer(const StreamFormat& new_format) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  queued_packets_.clear();
+  queued_bytes_ = 0;
+  first_packet_offset_ = 0;
+  ClearGetDataRequests();
+  stream_format_ = std::make_unique<StreamFormat>(new_format);
+  underrun_protection_mode_ = true;
+  if (on_format_changed_) {
+    on_format_changed_.Run(*stream_format_);
+  }
+}
+
+void AudioJitterBuffer::ProcessGetDataRequests() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  if (underrun_protection_mode_) {
+    return;
+  }
+
+  // Get the active request if there is one.
+  while (!queued_requests_.empty() && !queued_packets_.empty()) {
+    auto& active_request = queued_requests_.front();
+
+    // Copy any available data into the active request up to as much requested.
+    while (active_request->bytes_extracted < active_request->bytes_needed &&
+           !queued_packets_.empty()) {
+      uint8_t* next_data = static_cast<uint8_t*>(active_request->data) +
+                           active_request->bytes_extracted;
+
+      const std::string& packet_data = queued_packets_.front()->data(0);
+      size_t bytes_to_copy = std::min(
+          packet_data.size() - first_packet_offset_,
+          active_request->bytes_needed - active_request->bytes_extracted);
+
+      memcpy(next_data, packet_data.data() + first_packet_offset_,
+             bytes_to_copy);
+
+      first_packet_offset_ += bytes_to_copy;
+      active_request->bytes_extracted += bytes_to_copy;
+      queued_bytes_ -= bytes_to_copy;
+      DCHECK_GE(queued_bytes_, 0u);
+      // Pop off the packet if we've already consumed all its bytes.
+      if (queued_packets_.front()->data(0).size() == first_packet_offset_) {
+        queued_packets_.pop_front();
+        first_packet_offset_ = 0;
+      }
+    }
+
+    // If this request is fulfilled, call the callback and pop it off the queue.
+    if (active_request->bytes_extracted == active_request->bytes_needed) {
+      active_request->OnDataFilled();
+      queued_requests_.pop_front();
+    }
+  }
+
+  if (queued_packets_.empty()) {
+    // Buffer overrun.
+    underrun_protection_mode_ = true;
+  }
+}
+
+size_t AudioJitterBuffer::GetBufferSizeFromTime(
+    base::TimeDelta duration) const {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(stream_format_);
+  return duration.InMilliseconds() * stream_format_->sample_rate *
+         stream_format_->bytes_per_sample * stream_format_->channels /
+         base::Time::kMillisecondsPerSecond;
+}
+
+void AudioJitterBuffer::DropOverrunPackets() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  if (queued_bytes_ <= GetBufferSizeFromTime(kMaxQueueLatency)) {
+    return;
+  }
+
+  size_t new_size = GetBufferSizeFromTime(kOverrunRecoveryLatency);
+  while (queued_bytes_ > new_size) {
+    queued_bytes_ -=
+        queued_packets_.front()->data(0).size() - first_packet_offset_;
+    DCHECK_GE(queued_bytes_, 0u);
+    queued_packets_.pop_front();
+    first_packet_offset_ = 0;
+  }
+}
+
+}  // namespace remoting
diff --git a/remoting/client/audio/audio_jitter_buffer.h b/remoting/client/audio/audio_jitter_buffer.h
new file mode 100644
index 0000000..2fbf0966
--- /dev/null
+++ b/remoting/client/audio/audio_jitter_buffer.h
@@ -0,0 +1,102 @@
+// 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 REMOTING_CLIENT_AUDIO_AUDIO_JITTER_BUFFER_H_
+#define REMOTING_CLIENT_AUDIO_AUDIO_JITTER_BUFFER_H_
+
+#include <list>
+#include <memory>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "remoting/client/audio/async_audio_data_supplier.h"
+#include "remoting/proto/audio.pb.h"
+
+namespace remoting {
+
+// This is a jitter buffer that queues up audio packets and get-data requests
+// and feeds the requests with the data when the buffer has enough data.
+class AudioJitterBuffer : public AsyncAudioDataSupplier {
+ public:
+  struct StreamFormat {
+    bool operator==(const StreamFormat& other) const;
+    bool operator!=(const StreamFormat& other) const;
+
+    int bytes_per_sample = 0;
+    int channels = 0;
+    int sample_rate = 0;
+  };
+
+  using OnFormatChangedCallback =
+      base::RepeatingCallback<void(const StreamFormat& format)>;
+
+  // |callback| is called once the jitter buffer gets the first packet or the
+  // stream format has been changed.
+  // Pending get-data requests will be dropped when the stream format is
+  // changed.
+  explicit AudioJitterBuffer(OnFormatChangedCallback on_format_changed);
+  ~AudioJitterBuffer() override;
+
+  void AddAudioPacket(std::unique_ptr<AudioPacket> packet);
+
+  // AsyncAudioDataSupplier implementations.
+  void AsyncGetData(std::unique_ptr<GetDataRequest> request) override;
+  void ClearGetDataRequests() override;
+
+ private:
+  friend class AudioJitterBufferTest;
+
+  // Clears the jitter buffer, drops all pending requests, and notify
+  // |on_format_changed_| that the format has been changed.
+  void ResetBuffer(const StreamFormat& new_format);
+
+  // Feeds data from the jitter buffer into the pending requests. OnDataFilled()
+  // will be called and request will be removed from the queue when a request
+  // has been filled up.
+  void ProcessGetDataRequests();
+
+  // Calculates the number of bytes needed to store audio data of the given
+  // duration based on |stream_format_|.
+  size_t GetBufferSizeFromTime(base::TimeDelta duration) const;
+
+  // Drops audio packets in |queued_packets_| such that the total latency
+  // doesn't exceed |kMaxQueueLatency|.
+  void DropOverrunPackets();
+
+  // The stream format of the last audio packet. This is nullptr if the buffer
+  // has never received any packet.
+  std::unique_ptr<StreamFormat> stream_format_;
+
+  // AudioPackets queued up by the jitter buffer before they are consumed by
+  // GetDataRequests.
+  std::list<std::unique_ptr<AudioPacket>> queued_packets_;
+
+  // Number of bytes that is queued in |queued_packets_|.
+  size_t queued_bytes_ = 0;
+
+  // The byte offset when reading data from the first packet of
+  // |queued_packets_|. Equal to the number of bytes consumed from the first
+  // packet.
+  size_t first_packet_offset_ = 0;
+
+  // Called when the stream format is changed.
+  OnFormatChangedCallback on_format_changed_;
+
+  // GetDataRequests that are not yet fulfilled.
+  std::list<std::unique_ptr<GetDataRequest>> queued_requests_;
+
+  // The buffer will not feed data to the requests if this is true.
+  bool underrun_protection_mode_ = true;
+
+  THREAD_CHECKER(thread_checker_);
+
+  DISALLOW_COPY_AND_ASSIGN(AudioJitterBuffer);
+};
+
+}  // namespace remoting
+
+#endif  // REMOTING_CLIENT_AUDIO_AUDIO_JITTER_BUFFER_H_
diff --git a/remoting/client/audio/audio_jitter_buffer_unittest.cc b/remoting/client/audio/audio_jitter_buffer_unittest.cc
new file mode 100644
index 0000000..25a9ed46
--- /dev/null
+++ b/remoting/client/audio/audio_jitter_buffer_unittest.cc
@@ -0,0 +1,351 @@
+// 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 <cstdint>
+#include <list>
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "remoting/client/audio/audio_jitter_buffer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace remoting {
+
+namespace {
+
+constexpr AudioPacket::BytesPerSample kBytesPerSample =
+    AudioPacket::BYTES_PER_SAMPLE_2;
+constexpr AudioPacket::Channels kChannels = AudioPacket::CHANNELS_STEREO;
+constexpr uint32_t kAudioSampleBytes = kChannels * kBytesPerSample;
+
+constexpr uint32_t kNumConsumerBuffers = 3;
+constexpr uint32_t kConsumerBufferMaxByteSize = 5000 * kAudioSampleBytes;
+
+constexpr uint8_t kDefaultBufferData = 0x5A;
+constexpr uint8_t kDummyAudioData = 0x8B;
+
+std::unique_ptr<AudioPacket> CreateAudioPacketWithSamplingRate(
+    AudioPacket::SamplingRate rate,
+    size_t bytes) {
+  std::unique_ptr<AudioPacket> packet = std::make_unique<AudioPacket>();
+  packet->set_encoding(AudioPacket::ENCODING_RAW);
+  packet->set_sampling_rate(rate);
+  packet->set_bytes_per_sample(kBytesPerSample);
+  packet->set_channels(kChannels);
+
+  std::string data;
+  data.resize(bytes, kDummyAudioData);
+  packet->add_data(data);
+
+  return packet;
+}
+
+// Check that the first |bytes_written| bytes are filled with audio data and
+// the rest of the buffer is unchanged.
+void CheckDataBytes(const uint8_t* buffer, size_t bytes_written) {
+  uint32_t i = 0;
+  for (; i < bytes_written; i++) {
+    ASSERT_EQ(kDummyAudioData, *(buffer + i));
+  }
+  // Rest of audio frame must be unchanged.
+  for (; i < kConsumerBufferMaxByteSize; i++) {
+    ASSERT_EQ(kDefaultBufferData, *(buffer + i));
+  }
+}
+
+}  // namespace
+
+class AudioJitterBufferTest : public ::testing::Test {
+ protected:
+  void SetUp() override;
+  void TearDown() override;
+
+  void SetSampleRate(AudioPacket::SamplingRate sample_rate);
+  std::unique_ptr<AudioPacket> CreatePacket(int time_ms);
+  void AsyncConsumeData(size_t duration);
+  void VerifyStreamFormat();
+  void VerifyBuffersNotLost();
+  size_t ByteFromTime(int time_ms) const;
+  size_t GetNumQueuedPackets() const;
+  int GetNumQueuedTime() const;
+  size_t GetNumQueuedRequests() const;
+
+  std::unique_ptr<AudioJitterBuffer> audio_;
+  std::list<std::unique_ptr<uint8_t[]>> consumer_buffers_;
+
+ private:
+  struct SimpleGetDataRequest;
+
+  void OnFormatChanged(const AudioJitterBuffer::StreamFormat& format);
+
+  AudioPacket::SamplingRate sample_rate_;
+  std::unique_ptr<AudioJitterBuffer::StreamFormat> stream_format_;
+};
+
+struct AudioJitterBufferTest::SimpleGetDataRequest
+    : public AsyncAudioDataSupplier::GetDataRequest {
+ public:
+  SimpleGetDataRequest(AudioJitterBufferTest* test, size_t bytes_to_write);
+  ~SimpleGetDataRequest() override;
+
+  void OnDataFilled() override;
+
+ private:
+  AudioJitterBufferTest* test_;
+  std::unique_ptr<uint8_t[]> buffer_;
+  size_t bytes_to_write_;
+};
+
+// Test fixture definitions
+
+void AudioJitterBufferTest::SetUp() {
+  audio_ = std::make_unique<AudioJitterBuffer>(base::BindRepeating(
+      &AudioJitterBufferTest::OnFormatChanged, base::Unretained(this)));
+  consumer_buffers_.clear();
+  for (uint32_t i = 0u; i < kNumConsumerBuffers; i++) {
+    consumer_buffers_.push_back(
+        std::make_unique<uint8_t[]>(kConsumerBufferMaxByteSize));
+  }
+  SetSampleRate(AudioPacket::SAMPLING_RATE_48000);
+}
+
+void AudioJitterBufferTest::TearDown() {
+  VerifyBuffersNotLost();
+  audio_.reset();
+  consumer_buffers_.clear();
+}
+
+void AudioJitterBufferTest::SetSampleRate(
+    AudioPacket::SamplingRate sample_rate) {
+  sample_rate_ = sample_rate;
+}
+
+std::unique_ptr<AudioPacket> AudioJitterBufferTest::CreatePacket(int time_ms) {
+  return CreateAudioPacketWithSamplingRate(sample_rate_, ByteFromTime(time_ms));
+}
+
+void AudioJitterBufferTest::AsyncConsumeData(size_t duration) {
+  size_t bytes_to_write = ByteFromTime(duration);
+  ASSERT_LE(bytes_to_write, kConsumerBufferMaxByteSize);
+  ASSERT_FALSE(consumer_buffers_.empty());
+  audio_->AsyncGetData(
+      std::make_unique<SimpleGetDataRequest>(this, bytes_to_write));
+}
+
+void AudioJitterBufferTest::VerifyStreamFormat() {
+  ASSERT_TRUE(stream_format_);
+  ASSERT_EQ(kBytesPerSample, stream_format_->bytes_per_sample);
+  ASSERT_EQ(kChannels, stream_format_->channels);
+  ASSERT_EQ(sample_rate_, stream_format_->sample_rate);
+}
+
+void AudioJitterBufferTest::VerifyBuffersNotLost() {
+  size_t queued_requests = GetNumQueuedRequests();
+  ASSERT_EQ(kNumConsumerBuffers, queued_requests + consumer_buffers_.size());
+}
+
+size_t AudioJitterBufferTest::ByteFromTime(int time_ms) const {
+  return time_ms * sample_rate_ * kAudioSampleBytes /
+         base::Time::kMillisecondsPerSecond;
+}
+
+size_t AudioJitterBufferTest::GetNumQueuedPackets() const {
+  return audio_->queued_packets_.size();
+}
+
+int AudioJitterBufferTest::GetNumQueuedTime() const {
+  return audio_->queued_bytes_ * base::Time::kMillisecondsPerSecond /
+         kAudioSampleBytes / sample_rate_;
+}
+
+size_t AudioJitterBufferTest::GetNumQueuedRequests() const {
+  return audio_->queued_requests_.size();
+}
+void AudioJitterBufferTest::OnFormatChanged(
+    const AudioJitterBuffer::StreamFormat& format) {
+  stream_format_ = std::make_unique<AudioJitterBuffer::StreamFormat>(format);
+}
+
+// SimpleGetDataRequest definitions
+
+AudioJitterBufferTest::SimpleGetDataRequest::SimpleGetDataRequest(
+    AudioJitterBufferTest* test,
+    size_t bytes_to_write)
+    : GetDataRequest(test->consumer_buffers_.front().get(), bytes_to_write),
+      test_(test),
+      buffer_(std::move(test->consumer_buffers_.front())),
+      bytes_to_write_(bytes_to_write) {
+  test_->consumer_buffers_.pop_front();
+  memset(buffer_.get(), kDefaultBufferData, kConsumerBufferMaxByteSize);
+}
+
+AudioJitterBufferTest::SimpleGetDataRequest::~SimpleGetDataRequest() {
+  if (buffer_) {
+    test_->consumer_buffers_.push_back(std::move(buffer_));
+  }
+}
+
+void AudioJitterBufferTest::SimpleGetDataRequest::OnDataFilled() {
+  CheckDataBytes(buffer_.get(), bytes_to_write_);
+  test_->consumer_buffers_.push_back(std::move(buffer_));
+}
+
+// Test cases
+
+TEST_F(AudioJitterBufferTest, Init) {
+  ASSERT_EQ(0u, GetNumQueuedPackets());
+
+  audio_->AddAudioPacket(CreatePacket(20));
+  ASSERT_EQ(1u, GetNumQueuedPackets());
+  VerifyStreamFormat();
+}
+
+TEST_F(AudioJitterBufferTest, MultipleSamples) {
+  audio_->AddAudioPacket(CreatePacket(10));
+  ASSERT_EQ(10, GetNumQueuedTime());
+  ASSERT_EQ(1u, GetNumQueuedPackets());
+
+  audio_->AddAudioPacket(CreatePacket(20));
+  ASSERT_EQ(30, GetNumQueuedTime());
+  ASSERT_EQ(2u, GetNumQueuedPackets());
+}
+
+TEST_F(AudioJitterBufferTest, ExceedLatency) {
+  // Push about 4 seconds worth of samples.
+  for (uint32_t i = 0; i < 100; ++i) {
+    audio_->AddAudioPacket(CreatePacket(40));
+  }
+
+  // Verify that we don't have more than 0.5s.
+  ASSERT_LT(GetNumQueuedTime(), 500);
+}
+
+TEST_F(AudioJitterBufferTest, SingleAsyncRequest_UnderrunProtection) {
+  // Add samples that are enough to fulfill one request but still doesn't get
+  // passed the underrun protection.
+  audio_->AddAudioPacket(CreatePacket(10));
+
+  // Create an Audio Request.
+  AsyncConsumeData(10);
+
+  // The request is not fulfilled.
+  ASSERT_EQ(1u, GetNumQueuedPackets());
+  ASSERT_EQ(1u, GetNumQueuedRequests());
+}
+
+TEST_F(AudioJitterBufferTest, SingleAsyncRequest_Fulfilled) {
+  // Add samples that are enough to bypass underrun protection.
+  audio_->AddAudioPacket(CreatePacket(80));
+
+  // Create an Audio Request.
+  AsyncConsumeData(10);
+
+  // Request is fulfilled and buffer is returned.
+  ASSERT_EQ(1u, GetNumQueuedPackets());
+  ASSERT_EQ(0u, GetNumQueuedRequests());
+}
+
+TEST_F(AudioJitterBufferTest, TwoAsyncRequest_FulfillOneByOne) {
+  // Add just enough samples to fulfill one request.
+  audio_->AddAudioPacket(CreatePacket(80));
+  ASSERT_EQ(1u, GetNumQueuedPackets());
+
+  AsyncConsumeData(80);
+  // Request is immediately fulfilled.
+  ASSERT_EQ(0u, GetNumQueuedPackets());
+  ASSERT_EQ(0u, GetNumQueuedRequests());
+  VerifyBuffersNotLost();
+
+  // Add another request.
+  AsyncConsumeData(80);
+  ASSERT_EQ(0u, GetNumQueuedPackets());
+  ASSERT_EQ(1u, GetNumQueuedRequests());
+  VerifyBuffersNotLost();
+
+  // Add packet fulfill the request.
+  audio_->AddAudioPacket(CreatePacket(80));
+  ASSERT_EQ(0u, GetNumQueuedPackets());
+  ASSERT_EQ(0u, GetNumQueuedRequests());
+}
+
+TEST_F(AudioJitterBufferTest, TwoAsyncRequest_OnePacketFulfillsTwoRequests) {
+  // Add packet big enough to fulfill two requests.
+  audio_->AddAudioPacket(CreatePacket(100));
+  ASSERT_EQ(1u, GetNumQueuedPackets());
+
+  AsyncConsumeData(50);
+  // Request is immediately fulfilled.
+  ASSERT_EQ(1u, GetNumQueuedPackets());
+  ASSERT_EQ(0u, GetNumQueuedRequests());
+  VerifyBuffersNotLost();
+
+  // Add another request.
+  AsyncConsumeData(50);
+  ASSERT_EQ(0u, GetNumQueuedPackets());
+  ASSERT_EQ(0u, GetNumQueuedRequests());
+}
+
+TEST_F(AudioJitterBufferTest, TwoAsyncRequest_UnderrunProtectionKicksIn) {
+  audio_->AddAudioPacket(CreatePacket(80));
+  ASSERT_EQ(1u, GetNumQueuedPackets());
+
+  // Consumes all packets while still waiting for 20ms of more data.
+  AsyncConsumeData(100);
+  ASSERT_EQ(0u, GetNumQueuedPackets());
+  ASSERT_EQ(1u, GetNumQueuedRequests());
+  VerifyBuffersNotLost();
+
+  // The package does not get pass underrun protection.
+  audio_->AddAudioPacket(CreatePacket(20));
+  ASSERT_EQ(1u, GetNumQueuedPackets());
+  ASSERT_EQ(1u, GetNumQueuedRequests());
+
+  // Add a bigger packet, which bypasses underrun protection.
+  audio_->AddAudioPacket(CreatePacket(100));
+  ASSERT_EQ(1u, GetNumQueuedPackets());
+  ASSERT_EQ(0u, GetNumQueuedRequests());
+}
+
+TEST_F(AudioJitterBufferTest, TwoAsyncRequest_TwoPacketsFulfillTwoRequests) {
+  // Add sample that doesn't fulfill the first request.
+  audio_->AddAudioPacket(CreatePacket(70));
+
+  // Create two requests.
+  AsyncConsumeData(80);
+  AsyncConsumeData(80);
+
+  // The first packet has been used to fill the first request.
+  ASSERT_EQ(0u, GetNumQueuedPackets());
+  ASSERT_EQ(2u, GetNumQueuedRequests());
+  VerifyBuffersNotLost();
+
+  // Add the rest to fulfill both requests.
+  audio_->AddAudioPacket(CreatePacket(90));
+  ASSERT_EQ(0u, GetNumQueuedPackets());
+  ASSERT_EQ(0u, GetNumQueuedRequests());
+}
+
+TEST_F(AudioJitterBufferTest, ChangeSampleRate) {
+  ASSERT_EQ(0u, GetNumQueuedPackets());
+
+  audio_->AddAudioPacket(CreatePacket(20));
+  AsyncConsumeData(80);
+  ASSERT_EQ(1u, GetNumQueuedPackets());
+  ASSERT_EQ(1u, GetNumQueuedRequests());
+  VerifyBuffersNotLost();
+  VerifyStreamFormat();
+
+  SetSampleRate(AudioPacket::SAMPLING_RATE_44100);
+  audio_->AddAudioPacket(CreatePacket(20));
+  // Previous packet has been removed.
+  ASSERT_EQ(1u, GetNumQueuedPackets());
+
+  // Previous pending requests are cleared and callbacks has been run.
+  ASSERT_EQ(0u, GetNumQueuedRequests());
+  VerifyStreamFormat();
+}
+
+}  // namespace remoting
diff --git a/services/service_manager/sandbox/BUILD.gn b/services/service_manager/sandbox/BUILD.gn
index 9b0d5b0..1974757 100644
--- a/services/service_manager/sandbox/BUILD.gn
+++ b/services/service_manager/sandbox/BUILD.gn
@@ -8,6 +8,8 @@
 component("sandbox") {
   sources = [
     "export.h",
+    "features.cc",
+    "features.h",
     "sandbox.cc",
     "sandbox.h",
     "sandbox_delegate.h",
@@ -83,8 +85,6 @@
   }
   if (is_win) {
     sources += [
-      "features.cc",
-      "features.h",
       "win/sandbox_win.cc",
       "win/sandbox_win.h",
     ]
diff --git a/services/service_manager/sandbox/mac/BUILD.gn b/services/service_manager/sandbox/mac/BUILD.gn
index 277202f..1400738 100644
--- a/services/service_manager/sandbox/mac/BUILD.gn
+++ b/services/service_manager/sandbox/mac/BUILD.gn
@@ -5,6 +5,7 @@
 action_foreach("package_sb_files") {
   script = "package_sb_file.py"
   sources = [
+    "audio.sb",
     "cdm.sb",
     "common.sb",
     "common_v2.sb",
diff --git a/services/service_manager/sandbox/mac/audio.sb b/services/service_manager/sandbox/mac/audio.sb
new file mode 100644
index 0000000..775cc12
--- /dev/null
+++ b/services/service_manager/sandbox/mac/audio.sb
@@ -0,0 +1,46 @@
+; 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.
+
+; --- The contents of common.sb implicitly included here. ---
+
+; File access.
+(allow file-read*
+  (path (user-homedir-path "/Library/Caches/com.apple.coreaudio.components.plist"))
+  (regex (user-homedir-path #"/Library/Preferences/com.apple.coreaudio.*"))
+  (subpath (user-homedir-path "/Library/Audio/Plug-Ins"))
+  (subpath "/Library/Audio/Plug-Ins")
+  (subpath "/Library/Video/Plug-Ins")
+  (subpath "/Library/QuickTime")
+  (subpath "/System/Library/Components")
+  (subpath "/System/Library/Extensions"))
+
+(allow device-microphone)
+
+(allow iokit-open
+  (iokit-user-client-class "IOAudioControlUserClient")
+  (iokit-user-client-class "IOAudioEngineUserClient"))
+
+(allow ipc-posix-shm-read* ipc-posix-shm-write-data
+  (ipc-posix-name-regex #"^AudioIO"))
+
+; Mach IPC.
+(allow mach-lookup
+  (global-name "com.apple.audio.SystemSoundServer-OSX")
+  (global-name "com.apple.audio.VDCAssistant")
+  (global-name "com.apple.audio.coreaudiod")
+  (global-name "com.apple.audio.audiohald"))
+
+(if (>= os-version 1013)
+  (allow mach-lookup
+    (global-name "com.apple.audio.AudioComponentRegistrar")
+    (xpc-service-name "com.apple.audio.SandboxHelper")))
+
+; sysctls.
+(allow sysctl-read
+  (sysctl-name "hw.optional.avx2_0")
+  (sysctl-name "hw.optional.avx1_0")
+  (sysctl-name "hw.optional.sse4_2")
+  (sysctl-name "hw.optional.sse4_1")
+  (sysctl-name "hw.optional.sse3")
+  (sysctl-name "hw.optional.sse2"))
diff --git a/services/service_manager/sandbox/mac/common_v2.sb b/services/service_manager/sandbox/mac/common_v2.sb
index 8ec6abd..be20890 100644
--- a/services/service_manager/sandbox/mac/common_v2.sb
+++ b/services/service_manager/sandbox/mac/common_v2.sb
@@ -85,8 +85,9 @@
 (allow file-read* (subpath (param bundle-path)))
 
 ; Allow reads of system libraries and frameworks.
-(allow file-read-data
+(allow file-read*
   (subpath "/System/Library/Frameworks")
+  (subpath "/System/Library/Preferences/Logging")
   (subpath "/System/Library/PrivateFrameworks")
   (subpath "/usr/lib"))
 
diff --git a/services/service_manager/sandbox/mac/sandbox_mac.mm b/services/service_manager/sandbox/mac/sandbox_mac.mm
index d69fcc0..36b90f6 100644
--- a/services/service_manager/sandbox/mac/sandbox_mac.mm
+++ b/services/service_manager/sandbox/mac/sandbox_mac.mm
@@ -38,6 +38,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/sys_info.h"
 #include "sandbox/mac/sandbox_compiler.h"
+#include "services/service_manager/sandbox/mac/audio.sb.h"
 #include "services/service_manager/sandbox/mac/cdm.sb.h"
 #include "services/service_manager/sandbox/mac/common.sb.h"
 #include "services/service_manager/sandbox/mac/gpu.sb.h"
@@ -62,7 +63,6 @@
 // Mapping from sandbox process types to resource IDs containing the sandbox
 // profile for all process types known to service_manager.
 // TODO(tsepez): Implement profile for SANDBOX_TYPE_NETWORK.
-// TODO(https://crbug.com/850878): Implement profile for SANDBOX_TYPE_AUDIO.
 SandboxTypeToResourceIDMapping kDefaultSandboxTypeToResourceIDMapping[] = {
     {SANDBOX_TYPE_NO_SANDBOX, nullptr},
     {SANDBOX_TYPE_RENDERER, kSeatbeltPolicyString_renderer},
@@ -74,7 +74,7 @@
     {SANDBOX_TYPE_NACL_LOADER, kSeatbeltPolicyString_nacl_loader},
     {SANDBOX_TYPE_PDF_COMPOSITOR, kSeatbeltPolicyString_ppapi},
     {SANDBOX_TYPE_PROFILING, kSeatbeltPolicyString_utility},
-    {SANDBOX_TYPE_AUDIO, nullptr},
+    {SANDBOX_TYPE_AUDIO, kSeatbeltPolicyString_audio},
 };
 
 static_assert(arraysize(kDefaultSandboxTypeToResourceIDMapping) ==
diff --git a/services/service_manager/sandbox/sandbox_type.cc b/services/service_manager/sandbox/sandbox_type.cc
index cfd27c1..9a9568a 100644
--- a/services/service_manager/sandbox/sandbox_type.cc
+++ b/services/service_manager/sandbox/sandbox_type.cc
@@ -7,11 +7,8 @@
 #include <string>
 
 #include "base/feature_list.h"
-#include "services/service_manager/sandbox/switches.h"
-
-#if defined(OS_WIN)
 #include "services/service_manager/sandbox/features.h"
-#endif
+#include "services/service_manager/sandbox/switches.h"
 
 namespace service_manager {
 
@@ -24,7 +21,7 @@
       return true;
 #endif
     case SANDBOX_TYPE_AUDIO:
-#if defined(OS_WIN)
+#if defined(OS_WIN) || defined(OS_MACOSX)
       return !base::FeatureList::IsEnabled(
           service_manager::features::kAudioServiceSandbox);
 #else
diff --git a/services/tracing/coordinator.cc b/services/tracing/coordinator.cc
index 0ecbaee..d82a8ac 100644
--- a/services/tracing/coordinator.cc
+++ b/services/tracing/coordinator.cc
@@ -117,11 +117,12 @@
     // Bail out if we are in the middle of writing events for another label to
     // the stream, since we do not want to interleave chunks for different
     // fields. For example, we do not want to mix |traceEvent| chunks with
-    // |battor| chunks.
+    // |systrace| chunks.
     //
-    // If we receive a |battor| chunk from an agent while writing |traceEvent|
-    // chunks to the stream, we wait until all agents that send |traceEvent|
-    // chunks are done, and then, we start writing |battor| chunks.
+    // If we receive a |systemTraceEvents| chunk from an agent while writing
+    // |traceEvent| chunks to the stream, we wait until all agents that send
+    // |traceEvent| chunks are done, and then, we start writing
+    // |systemTraceEvents| chunks.
     if (!streaming_label_.empty() && streaming_label_ != label)
       return;
 
diff --git a/services/tracing/coordinator_unittest.cc b/services/tracing/coordinator_unittest.cc
index 0bdd1d9..b1d4616 100644
--- a/services/tracing/coordinator_unittest.cc
+++ b/services/tracing/coordinator_unittest.cc
@@ -69,7 +69,7 @@
 
   MockAgent* AddStringAgent() {
     auto agent = std::make_unique<MockAgent>();
-    agent_registry_->RegisterAgent(agent->CreateAgentPtr(), "battor",
+    agent_registry_->RegisterAgent(agent->CreateAgentPtr(), "power",
                                    mojom::TraceDataType::STRING, false,
                                    base::kNullProcessId);
     agents_.push_back(std::move(agent));
@@ -325,8 +325,8 @@
   if (!quit_closure_.is_null())
     run_loop.Run();
 
-  EXPECT_TRUE(output_ == "{\"traceEvents\":[e1,e2],\"battor\":\"e3e4\"}" ||
-              output_ == "{\"battor\":\"e3e4\",\"traceEvents\":[e1,e2]}");
+  EXPECT_TRUE(output_ == "{\"traceEvents\":[e1,e2],\"power\":\"e3e4\"}" ||
+              output_ == "{\"power\":\"e3e4\",\"traceEvents\":[e1,e2]}");
 
   // Each agent should have received exactly two calls.
   EXPECT_EQ(2u, agent1->call_stat().size());
diff --git a/services/tracing/public/mojom/tracing.mojom b/services/tracing/public/mojom/tracing.mojom
index c559e90..25923fd4 100644
--- a/services/tracing/public/mojom/tracing.mojom
+++ b/services/tracing/public/mojom/tracing.mojom
@@ -21,8 +21,8 @@
   STRING
 };
 
-// Tracing agents, like |chrome|, |etw|, |battor|, and |cros|, use this
-// interface to register themselves to the tracing service.
+// Tracing agents, like |chrome|, |etw|, and |cros|, use this interface to
+// register themselves to the tracing service.
 //
 // This is a separate interface from |Coordinator| for security and privacy
 // reasons: although we want to let almost every process be able to send tracing
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index 958f2eb..b60bcb71 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -1480,6 +1480,102 @@
       },
       {
         "args": [
+          "--enable-surface-synchronization",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "surface_sync_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "surface_sync_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "NRD91N",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ],
+          "expiration": 10800,
+          "hard_timeout": 960,
+          "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"
+            }
+          ],
+          "shards": 2
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
+          "--enable-features=VizDisplayCompositor",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "viz_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "viz_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "NRD91N",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ],
+          "expiration": 10800,
+          "hard_timeout": 960,
+          "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"
+            }
+          ],
+          "shards": 10
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
@@ -3787,6 +3883,62 @@
       },
       {
         "args": [
+          "--enable-surface-synchronization",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "surface_sync_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "surface_sync_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_type": "coho",
+              "os": "Android"
+            }
+          ],
+          "shards": 2
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
+          "--enable-features=VizDisplayCompositor",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "viz_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "viz_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_type": "coho",
+              "os": "Android"
+            }
+          ],
+          "shards": 10
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
@@ -5299,6 +5451,62 @@
       },
       {
         "args": [
+          "--enable-surface-synchronization",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "surface_sync_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "surface_sync_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_type": "gce_x86",
+              "os": "Android"
+            }
+          ],
+          "shards": 2
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
+          "--enable-features=VizDisplayCompositor",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "viz_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "viz_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_type": "gce_x86",
+              "os": "Android"
+            }
+          ],
+          "shards": 10
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
@@ -6344,7 +6552,8 @@
               "os": "Android"
             }
           ],
-          "hard_timeout": 960
+          "hard_timeout": 960,
+          "idempotent": false
         }
       }
     ]
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 2c29707..e8b50843 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -1336,6 +1336,98 @@
       },
       {
         "args": [
+          "--enable-surface-synchronization",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "surface_sync_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "surface_sync_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "KTU84P",
+              "device_type": "hammerhead",
+              "os": "Android"
+            }
+          ],
+          "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"
+            }
+          ],
+          "shards": 2
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
+          "--enable-features=VizDisplayCompositor",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "viz_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "viz_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "KTU84P",
+              "device_type": "hammerhead",
+              "os": "Android"
+            }
+          ],
+          "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"
+            }
+          ],
+          "shards": 10
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
@@ -3900,6 +3992,100 @@
       },
       {
         "args": [
+          "--enable-surface-synchronization",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "surface_sync_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "surface_sync_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "KTU84Z",
+              "device_type": "flo",
+              "os": "Android"
+            }
+          ],
+          "expiration": 10800,
+          "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"
+            }
+          ],
+          "shards": 2
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
+          "--enable-features=VizDisplayCompositor",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "viz_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "viz_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "KTU84Z",
+              "device_type": "flo",
+              "os": "Android"
+            }
+          ],
+          "expiration": 10800,
+          "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"
+            }
+          ],
+          "shards": 10
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
@@ -6528,6 +6714,102 @@
       },
       {
         "args": [
+          "--enable-surface-synchronization",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "surface_sync_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "surface_sync_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "LMY48I",
+              "device_type": "hammerhead",
+              "os": "Android"
+            }
+          ],
+          "expiration": 10800,
+          "hard_timeout": 960,
+          "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"
+            }
+          ],
+          "shards": 2
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
+          "--enable-features=VizDisplayCompositor",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "viz_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "viz_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "LMY48I",
+              "device_type": "hammerhead",
+              "os": "Android"
+            }
+          ],
+          "expiration": 10800,
+          "hard_timeout": 960,
+          "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"
+            }
+          ],
+          "shards": 10
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
@@ -9195,6 +9477,102 @@
       },
       {
         "args": [
+          "--enable-surface-synchronization",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "surface_sync_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "surface_sync_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "LMY49B",
+              "device_type": "flo",
+              "os": "Android"
+            }
+          ],
+          "expiration": 10800,
+          "hard_timeout": 120,
+          "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"
+            }
+          ],
+          "shards": 2
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
+          "--enable-features=VizDisplayCompositor",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "viz_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "viz_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "LMY49B",
+              "device_type": "flo",
+              "os": "Android"
+            }
+          ],
+          "expiration": 10800,
+          "hard_timeout": 120,
+          "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"
+            }
+          ],
+          "shards": 10
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
@@ -11891,6 +12269,100 @@
       },
       {
         "args": [
+          "--enable-surface-synchronization",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "surface_sync_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "surface_sync_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ],
+          "hard_timeout": 960,
+          "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"
+            }
+          ],
+          "shards": 2
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
+          "--enable-features=VizDisplayCompositor",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "viz_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "viz_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ],
+          "hard_timeout": 960,
+          "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"
+            }
+          ],
+          "shards": 10
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
@@ -14499,6 +14971,100 @@
       },
       {
         "args": [
+          "--enable-surface-synchronization",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "surface_sync_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "surface_sync_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "MRA58Z",
+              "device_type": "flo",
+              "os": "Android"
+            }
+          ],
+          "expiration": 10800,
+          "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"
+            }
+          ],
+          "shards": 2
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
+          "--enable-features=VizDisplayCompositor",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "viz_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "viz_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "MRA58Z",
+              "device_type": "flo",
+              "os": "Android"
+            }
+          ],
+          "expiration": 10800,
+          "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"
+            }
+          ],
+          "shards": 10
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
@@ -16388,10 +16954,10 @@
       },
       {
         "args": [
-          "--shared-prefs-file=//chrome/android/shared_preference_files/test/vr_ddview_don_setupcomplete.json",
+          "--shared-prefs-file=//chrome/android/shared_preference_files/test/vr_enable_vr_settings_service.json",
           "--replace-system-package=com.google.vr.vrcore,//third_party/gvr-android-sdk/test-apks/vr_services/vr_services_current.apk",
-          "--annotation=Restriction=DON_Enabled",
-          "--don-enabled",
+          "--annotation=Restriction=VR_Settings_Service",
+          "--vr-settings-service-enabled",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
@@ -16400,11 +16966,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "chrome_public_test_vr_apk-ddready-ddview-donenabled"
+            "chrome_public_test_vr_apk-ddready-dynamicsettings"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "chrome_public_test_vr_apk-ddready-ddview-donenabled",
+        "name": "chrome_public_test_vr_apk-ddready-dynamicsettings",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -16569,10 +17135,10 @@
       },
       {
         "args": [
-          "--shared-prefs-file=//chrome/android/shared_preference_files/test/vr_ddview_don_setupcomplete.json",
+          "--shared-prefs-file=//chrome/android/shared_preference_files/test/vr_enable_vr_settings_service.json",
           "--replace-system-package=com.google.vr.vrcore,//third_party/gvr-android-sdk/test-apks/vr_services/vr_services_current.apk",
-          "--annotation=Restriction=DON_Enabled",
-          "--don-enabled",
+          "--annotation=Restriction=VR_Settings_Service",
+          "--vr-settings-service-enabled",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
@@ -16581,11 +17147,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "chrome_public_test_vr_apk-ddready-ddview-donenabled"
+            "chrome_public_test_vr_apk-ddready-dynamicsettings"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "chrome_public_test_vr_apk-ddready-ddview-donenabled",
+        "name": "chrome_public_test_vr_apk-ddready-dynamicsettings",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -17494,6 +18060,53 @@
       },
       {
         "args": [
+          "--enable-surface-synchronization",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "experiment_percentage": 100,
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "surface_sync_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "surface_sync_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "KTU84P",
+              "device_type": "hammerhead",
+              "os": "Android"
+            }
+          ],
+          "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"
+            }
+          ],
+          "shards": 2
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
@@ -20120,6 +20733,100 @@
       },
       {
         "args": [
+          "--enable-surface-synchronization",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "experiment_percentage": 100,
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "surface_sync_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "surface_sync_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ],
+          "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"
+            }
+          ],
+          "shards": 2
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
+          "--enable-features=VizDisplayCompositor",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "experiment_percentage": 100,
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "viz_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "viz_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ],
+          "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"
+            }
+          ],
+          "shards": 10
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
@@ -21934,6 +22641,7 @@
             }
           ],
           "hard_timeout": 960,
+          "idempotent": false,
           "shards": 15
         }
       }
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 42f780a..7477a39 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -480,7 +480,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -1102,7 +1102,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
diff --git a/testing/buildbot/chromium.clang.json b/testing/buildbot/chromium.clang.json
index 1557c419..3c515f5 100644
--- a/testing/buildbot/chromium.clang.json
+++ b/testing/buildbot/chromium.clang.json
@@ -235,7 +235,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -434,16 +434,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -618,16 +608,6 @@
         "test": "sync_integration_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_sync_integration_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "sync_integration_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -652,16 +632,6 @@
         "test": "unit_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_unit_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "unit_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -945,7 +915,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -1144,16 +1114,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -1328,16 +1288,6 @@
         "test": "sync_integration_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_sync_integration_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "sync_integration_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -1362,16 +1312,6 @@
         "test": "unit_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_unit_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "unit_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -1787,7 +1727,7 @@
               "os": "Windows-10-15063"
             }
           ],
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -2946,7 +2886,7 @@
               "os": "Windows-10-15063"
             }
           ],
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -4093,7 +4033,7 @@
               "os": "Windows-10-15063"
             }
           ],
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -5097,7 +5037,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -5763,7 +5703,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -6429,7 +6369,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -7095,7 +7035,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -8377,6 +8317,98 @@
       },
       {
         "args": [
+          "--enable-surface-synchronization",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "surface_sync_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "surface_sync_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "KTU84P",
+              "device_type": "hammerhead",
+              "os": "Android"
+            }
+          ],
+          "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"
+            }
+          ],
+          "shards": 2
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
+          "--enable-features=VizDisplayCompositor",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "viz_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "viz_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "KTU84P",
+              "device_type": "hammerhead",
+              "os": "Android"
+            }
+          ],
+          "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"
+            }
+          ],
+          "shards": 10
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
@@ -10986,6 +11018,98 @@
       },
       {
         "args": [
+          "--enable-surface-synchronization",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "surface_sync_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "surface_sync_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ],
+          "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"
+            }
+          ],
+          "shards": 2
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
+          "--enable-features=VizDisplayCompositor",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "viz_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "viz_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ],
+          "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"
+            }
+          ],
+          "shards": 10
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
@@ -12988,7 +13112,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -13169,16 +13293,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -13353,16 +13467,6 @@
         "test": "sync_integration_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_sync_integration_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "sync_integration_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -13387,16 +13491,6 @@
         "test": "unit_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_unit_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "unit_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -13694,7 +13788,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -13857,16 +13951,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -14042,16 +14126,6 @@
         "test": "sync_integration_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_sync_integration_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "sync_integration_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -14077,16 +14151,6 @@
         "test": "unit_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_unit_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "unit_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -14370,7 +14434,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -14551,16 +14615,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -14729,16 +14783,6 @@
         "test": "sync_integration_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_sync_integration_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "sync_integration_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -14763,16 +14807,6 @@
         "test": "unit_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_unit_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "unit_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -15056,7 +15090,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -15237,16 +15271,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -15421,16 +15445,6 @@
         "test": "sync_integration_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_sync_integration_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "sync_integration_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -15455,16 +15469,6 @@
         "test": "unit_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_unit_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "unit_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -15933,16 +15937,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -16117,16 +16111,6 @@
         "test": "sync_integration_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_sync_integration_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "sync_integration_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -16151,16 +16135,6 @@
         "test": "unit_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_unit_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "unit_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -16339,7 +16313,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -16727,7 +16701,8 @@
         "isolate_name": "telemetry_gpu_unittests",
         "name": "telemetry_gpu_unittests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "idempotent": false
         }
       },
       {
@@ -16736,6 +16711,7 @@
         "swarming": {
           "can_use_on_swarming_builders": true,
           "hard_timeout": 960,
+          "idempotent": false,
           "shards": 12
         }
       },
@@ -16747,6 +16723,7 @@
         "name": "telemetry_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "idempotent": false,
           "shards": 4
         }
       },
@@ -16910,7 +16887,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -17468,7 +17445,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -18134,7 +18111,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -18800,7 +18777,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -19466,7 +19443,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -20132,7 +20109,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -20798,7 +20775,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -21464,7 +21441,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -22130,7 +22107,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -22796,7 +22773,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -23462,7 +23439,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -24151,7 +24128,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -24332,16 +24309,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -24516,16 +24483,6 @@
         "test": "sync_integration_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_sync_integration_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "sync_integration_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -24550,16 +24507,6 @@
         "test": "unit_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_unit_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "unit_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index d9da105..dbf36f5 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -82,10 +82,10 @@
       },
       {
         "args": [
-          "--shared-prefs-file=//chrome/android/shared_preference_files/test/vr_ddview_don_setupcomplete.json",
+          "--shared-prefs-file=//chrome/android/shared_preference_files/test/vr_enable_vr_settings_service.json",
           "--replace-system-package=com.google.vr.vrcore,//third_party/gvr-android-sdk/test-apks/vr_services/vr_services_current.apk",
-          "--annotation=Restriction=DON_Enabled",
-          "--don-enabled",
+          "--annotation=Restriction=VR_Settings_Service",
+          "--vr-settings-service-enabled",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
@@ -94,11 +94,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "chrome_public_test_vr_apk-marlin-ddview-nougat-donenabled"
+            "chrome_public_test_vr_apk-marlin-nougat-dynamicsettings"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "chrome_public_test_vr_apk-marlin-ddview-nougat-donenabled",
+        "name": "chrome_public_test_vr_apk-marlin-nougat-dynamicsettings",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -312,13 +312,13 @@
       },
       {
         "args": [
-          "--shared-prefs-file=src/chrome/android/shared_preference_files/test/vr_ddview_don_setupcomplete.json",
+          "--shared-prefs-file=src/chrome/android/shared_preference_files/test/vr_enable_vr_settings_service.json",
           "--additional-apk=src/third_party/gvr-android-sdk/test-apks/vr_services/vr_services_current.apk",
           "--additional-apk=src/third_party/gvr-android-sdk/test-apks/daydream_home/daydream_home_current.apk",
-          "--annotation=Restriction=DON_Enabled",
-          "--don-enabled"
+          "--annotation=Restriction=VR_Settings_Service",
+          "--vr-settings-service-enabled"
         ],
-        "name": "chrome_public_test_vr_apk (don enabled)",
+        "name": "chrome_public_test_vr_apk (dynamic settings)",
         "test": "chrome_public_test_vr_apk"
       }
     ]
@@ -452,7 +452,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -840,7 +840,8 @@
         "isolate_name": "telemetry_gpu_unittests",
         "name": "telemetry_gpu_unittests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "idempotent": false
         }
       },
       {
@@ -849,6 +850,7 @@
         "swarming": {
           "can_use_on_swarming_builders": true,
           "hard_timeout": 960,
+          "idempotent": false,
           "shards": 12
         }
       },
@@ -860,6 +862,7 @@
         "name": "telemetry_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "idempotent": false,
           "shards": 4
         }
       },
@@ -1028,6 +1031,7 @@
         "swarming": {
           "can_use_on_swarming_builders": true,
           "hard_timeout": 960,
+          "idempotent": false,
           "shards": 12
         }
       },
@@ -1041,6 +1045,7 @@
         "swarming": {
           "can_use_on_swarming_builders": true,
           "hard_timeout": 960,
+          "idempotent": false,
           "shards": 12
         }
       },
@@ -1049,6 +1054,7 @@
         "name": "telemetry_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "idempotent": false,
           "shards": 4
         }
       },
@@ -1060,6 +1066,7 @@
         "name": "telemetry_unittests_viz",
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "idempotent": false,
           "shards": 4
         }
       }
@@ -2969,6 +2976,7 @@
             }
           ],
           "hard_timeout": 3600,
+          "idempotent": false,
           "io_timeout": 3600,
           "shards": 12
         }
@@ -2994,6 +3002,7 @@
             }
           ],
           "hard_timeout": 3600,
+          "idempotent": false,
           "io_timeout": 3600,
           "shards": 6
         }
@@ -4233,7 +4242,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -4414,16 +4423,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -4598,16 +4597,6 @@
         "test": "sync_integration_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_sync_integration_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "sync_integration_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -4632,16 +4621,6 @@
         "test": "unit_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_unit_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "unit_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -4761,7 +4740,8 @@
         "isolate_name": "telemetry_gpu_unittests",
         "name": "telemetry_gpu_unittests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "idempotent": false
         }
       },
       {
@@ -4770,6 +4750,7 @@
         "swarming": {
           "can_use_on_swarming_builders": true,
           "hard_timeout": 960,
+          "idempotent": false,
           "shards": 12
         }
       },
@@ -4781,6 +4762,7 @@
         "name": "telemetry_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "idempotent": false,
           "shards": 4
         }
       },
@@ -5049,7 +5031,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -5230,16 +5212,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -5414,16 +5386,6 @@
         "test": "sync_integration_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_sync_integration_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "sync_integration_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -5448,16 +5410,6 @@
         "test": "unit_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_unit_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "unit_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -5577,7 +5529,8 @@
         "isolate_name": "telemetry_gpu_unittests",
         "name": "telemetry_gpu_unittests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "idempotent": false
         }
       },
       {
@@ -5586,6 +5539,7 @@
         "swarming": {
           "can_use_on_swarming_builders": true,
           "hard_timeout": 960,
+          "idempotent": false,
           "shards": 12
         }
       },
@@ -5597,6 +5551,7 @@
         "name": "telemetry_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "idempotent": false,
           "shards": 4
         }
       },
@@ -5834,7 +5789,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index 89bf0d8d..ca71057 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -775,7 +775,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -798,7 +799,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -821,7 +823,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -844,7 +847,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -871,7 +875,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -901,7 +906,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -939,7 +945,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -963,7 +970,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -986,7 +994,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1011,6 +1020,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 6
         }
       }
@@ -1199,7 +1209,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1222,7 +1233,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1245,7 +1257,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1268,7 +1281,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1295,7 +1309,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1325,7 +1340,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1363,7 +1379,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1387,7 +1404,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1410,7 +1428,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1435,6 +1454,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 6
         }
       }
@@ -1549,7 +1569,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1571,7 +1592,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1593,7 +1615,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1615,7 +1638,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1641,7 +1665,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1670,7 +1695,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1707,7 +1733,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1730,7 +1757,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1752,7 +1780,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1776,6 +1805,7 @@
               "os": "Android"
             }
           ],
+          "idempotent": false,
           "shards": 6
         }
       }
@@ -1945,7 +1975,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1968,7 +1999,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1991,7 +2023,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2014,7 +2047,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2041,7 +2075,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2071,7 +2106,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2109,7 +2145,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2133,7 +2170,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2156,7 +2194,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2181,6 +2220,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 6
         }
       }
@@ -2404,7 +2444,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2427,7 +2468,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2450,7 +2492,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2473,7 +2516,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2500,7 +2544,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2530,7 +2575,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2568,7 +2614,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2592,7 +2639,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2615,7 +2663,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2640,6 +2689,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 6
         }
       }
@@ -2828,7 +2878,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2851,7 +2902,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2874,7 +2926,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2897,7 +2950,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2924,7 +2978,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2954,7 +3009,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2992,7 +3048,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -3016,7 +3073,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -3039,7 +3097,8 @@
               "os": "Android",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -3064,6 +3123,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 6
         }
       }
@@ -3338,7 +3398,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -3360,7 +3421,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -3382,7 +3444,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -3404,7 +3467,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -3430,7 +3494,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -3459,7 +3524,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -3496,7 +3562,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -3519,7 +3586,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -3541,7 +3609,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -3564,7 +3633,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -3587,6 +3657,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -3610,6 +3681,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -3923,7 +3995,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 10800
+          "expiration": 10800,
+          "idempotent": false
         }
       },
       {
@@ -3946,7 +4019,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 10800
+          "expiration": 10800,
+          "idempotent": false
         }
       },
       {
@@ -3969,7 +4043,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 10800
+          "expiration": 10800,
+          "idempotent": false
         }
       },
       {
@@ -3992,7 +4067,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 10800
+          "expiration": 10800,
+          "idempotent": false
         }
       },
       {
@@ -4019,7 +4095,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 10800
+          "expiration": 10800,
+          "idempotent": false
         }
       },
       {
@@ -4049,7 +4126,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 10800
+          "expiration": 10800,
+          "idempotent": false
         }
       },
       {
@@ -4087,7 +4165,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 10800
+          "expiration": 10800,
+          "idempotent": false
         }
       },
       {
@@ -4111,7 +4190,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 10800
+          "expiration": 10800,
+          "idempotent": false
         }
       },
       {
@@ -4134,7 +4214,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 10800
+          "expiration": 10800,
+          "idempotent": false
         }
       },
       {
@@ -4158,7 +4239,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 10800
+          "expiration": 10800,
+          "idempotent": false
         }
       },
       {
@@ -4184,6 +4266,7 @@
             }
           ],
           "expiration": 10800,
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -4208,6 +4291,7 @@
             }
           ],
           "expiration": 10800,
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -4232,6 +4316,7 @@
             }
           ],
           "expiration": 10800,
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -4395,7 +4480,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -4417,7 +4503,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -4439,7 +4526,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -4461,7 +4549,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -4487,7 +4576,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -4516,7 +4606,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -4553,7 +4644,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -4576,7 +4668,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -4598,7 +4691,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -4621,7 +4715,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -4646,6 +4741,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -4669,6 +4765,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -4692,6 +4789,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -4873,7 +4971,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -4895,7 +4994,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -4917,7 +5017,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -4939,7 +5040,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -4965,7 +5067,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -4994,7 +5097,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -5031,7 +5135,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -5054,7 +5159,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -5076,7 +5182,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -5099,7 +5206,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -5124,6 +5232,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -5149,6 +5258,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -5172,6 +5282,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -5195,6 +5306,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -5443,7 +5555,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -5464,7 +5577,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -5485,7 +5599,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -5506,7 +5621,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -5531,7 +5647,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -5559,7 +5676,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -5595,7 +5713,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -5617,7 +5736,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -5638,7 +5758,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -5660,6 +5781,7 @@
               "os": "Mac-10.12.6"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -5821,7 +5943,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -5843,7 +5966,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -5865,7 +5989,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -5887,7 +6012,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -5913,7 +6039,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -5942,7 +6069,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -5979,7 +6107,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -6002,7 +6131,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -6024,7 +6154,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -6049,6 +6180,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -6072,6 +6204,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -6242,7 +6375,8 @@
               "os": "Mac-10.13.6",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -6265,7 +6399,8 @@
               "os": "Mac-10.13.6",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -6288,7 +6423,8 @@
               "os": "Mac-10.13.6",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -6311,7 +6447,8 @@
               "os": "Mac-10.13.6",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -6338,7 +6475,8 @@
               "os": "Mac-10.13.6",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -6368,7 +6506,8 @@
               "os": "Mac-10.13.6",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -6406,7 +6545,8 @@
               "os": "Mac-10.13.6",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -6430,7 +6570,8 @@
               "os": "Mac-10.13.6",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -6453,7 +6594,8 @@
               "os": "Mac-10.13.6",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -6479,6 +6621,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -6503,6 +6646,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -6682,7 +6826,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 21600
+          "expiration": 21600,
+          "idempotent": false
         }
       },
       {
@@ -6706,7 +6851,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 21600
+          "expiration": 21600,
+          "idempotent": false
         }
       },
       {
@@ -6730,7 +6876,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 21600
+          "expiration": 21600,
+          "idempotent": false
         }
       },
       {
@@ -6754,7 +6901,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 21600
+          "expiration": 21600,
+          "idempotent": false
         }
       },
       {
@@ -6782,7 +6930,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 21600
+          "expiration": 21600,
+          "idempotent": false
         }
       },
       {
@@ -6813,7 +6962,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 21600
+          "expiration": 21600,
+          "idempotent": false
         }
       },
       {
@@ -6852,7 +7002,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 21600
+          "expiration": 21600,
+          "idempotent": false
         }
       },
       {
@@ -6877,7 +7028,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 21600
+          "expiration": 21600,
+          "idempotent": false
         }
       },
       {
@@ -6901,7 +7053,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 21600
+          "expiration": 21600,
+          "idempotent": false
         }
       },
       {
@@ -6928,6 +7081,7 @@
             }
           ],
           "expiration": 21600,
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -6953,6 +7107,7 @@
             }
           ],
           "expiration": 21600,
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -7178,7 +7333,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -7205,7 +7361,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -7232,7 +7389,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -7259,7 +7417,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -7293,7 +7452,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -7335,7 +7495,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -7363,7 +7524,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -7390,7 +7552,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -7421,6 +7584,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -7450,6 +7614,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -7614,7 +7779,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -7635,7 +7801,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -7656,7 +7823,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -7677,7 +7845,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -7702,7 +7871,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -7730,7 +7900,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -7766,7 +7937,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -7788,7 +7960,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -7809,7 +7982,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -7833,6 +8007,7 @@
               "os": "Mac-10.12.6"
             }
           ],
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -7855,6 +8030,7 @@
               "os": "Mac-10.12.6"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -8018,7 +8194,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8041,7 +8218,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8064,7 +8242,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8087,7 +8266,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8114,7 +8294,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8144,7 +8325,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8182,7 +8364,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8206,7 +8389,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8229,7 +8413,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8253,6 +8438,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -8416,7 +8602,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8439,7 +8626,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8462,7 +8650,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8485,7 +8674,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8512,7 +8702,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8542,7 +8733,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8580,7 +8772,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8604,7 +8797,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8627,7 +8821,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8651,6 +8846,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -8835,7 +9031,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8858,7 +9055,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8881,7 +9079,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8904,7 +9103,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8931,7 +9131,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8961,7 +9162,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -8999,7 +9201,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -9023,7 +9226,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -9046,7 +9250,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -9072,6 +9277,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -9096,6 +9302,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -9280,7 +9487,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -9303,7 +9511,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -9326,7 +9535,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -9349,7 +9559,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -9376,7 +9587,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -9406,7 +9618,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -9444,7 +9657,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -9468,7 +9682,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -9491,7 +9706,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -9517,6 +9733,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -9541,6 +9758,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -9815,7 +10033,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 10800
+          "expiration": 10800,
+          "idempotent": false
         }
       },
       {
@@ -9838,7 +10057,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 10800
+          "expiration": 10800,
+          "idempotent": false
         }
       },
       {
@@ -9861,7 +10081,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 10800
+          "expiration": 10800,
+          "idempotent": false
         }
       },
       {
@@ -9884,7 +10105,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 10800
+          "expiration": 10800,
+          "idempotent": false
         }
       },
       {
@@ -9911,7 +10133,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 10800
+          "expiration": 10800,
+          "idempotent": false
         }
       },
       {
@@ -9941,7 +10164,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 10800
+          "expiration": 10800,
+          "idempotent": false
         }
       },
       {
@@ -9979,7 +10203,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 10800
+          "expiration": 10800,
+          "idempotent": false
         }
       },
       {
@@ -10003,7 +10228,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 10800
+          "expiration": 10800,
+          "idempotent": false
         }
       },
       {
@@ -10026,7 +10252,8 @@
               "pool": "Chrome-GPU"
             }
           ],
-          "expiration": 10800
+          "expiration": 10800,
+          "idempotent": false
         }
       },
       {
@@ -10052,6 +10279,7 @@
             }
           ],
           "expiration": 10800,
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -10076,6 +10304,7 @@
             }
           ],
           "expiration": 10800,
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -10171,7 +10400,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -10196,6 +10426,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -10219,6 +10450,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -10331,7 +10563,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -10356,6 +10589,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -10379,6 +10613,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -10481,7 +10716,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -10505,6 +10741,7 @@
               "os": "Mac-10.12.6"
             }
           ],
+          "idempotent": false,
           "shards": 20
         }
       }
@@ -10619,7 +10856,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -10645,6 +10883,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         }
       }
@@ -10759,7 +10998,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       }
     ]
@@ -10978,7 +11218,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -11003,6 +11244,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -11026,6 +11268,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -11049,6 +11292,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -11072,6 +11316,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -11095,6 +11340,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -11468,7 +11714,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -11558,6 +11805,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         },
         "trigger_script": {
@@ -11590,6 +11838,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         },
         "trigger_script": {
@@ -11622,6 +11871,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         },
         "trigger_script": {
@@ -11654,6 +11904,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         },
         "trigger_script": {
@@ -11686,6 +11937,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         },
         "trigger_script": {
@@ -11998,7 +12250,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -12020,7 +12273,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -12042,7 +12296,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -12064,7 +12319,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -12090,7 +12346,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -12119,7 +12376,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -12156,7 +12414,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -12179,7 +12438,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -12201,7 +12461,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -12224,7 +12485,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -12247,6 +12509,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -12270,6 +12533,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -12293,6 +12557,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -12316,6 +12581,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -12339,6 +12605,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -12621,7 +12888,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -12643,7 +12911,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -12665,7 +12934,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -12687,7 +12957,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -12713,7 +12984,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -12742,7 +13014,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -12779,7 +13052,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -12802,7 +13076,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -12824,7 +13099,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -12847,7 +13123,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -12872,6 +13149,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -12895,6 +13173,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -12918,6 +13197,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -12941,6 +13221,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -12967,7 +13248,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       }
     ]
@@ -13271,7 +13553,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -13293,7 +13576,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -13315,7 +13599,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -13337,7 +13622,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -13363,7 +13649,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -13392,7 +13679,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -13429,7 +13717,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -13452,7 +13741,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -13474,7 +13764,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -13497,7 +13788,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -13522,6 +13814,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -13545,6 +13838,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -13568,6 +13862,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -13591,6 +13886,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -13614,6 +13910,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -13637,6 +13934,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -14140,7 +14438,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -14171,7 +14470,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -14202,7 +14502,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -14233,7 +14534,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -14268,7 +14570,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -14306,7 +14609,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -14380,7 +14684,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -14412,7 +14717,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -14443,7 +14749,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -14503,7 +14810,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -14537,6 +14845,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         },
         "trigger_script": {
@@ -14571,6 +14880,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         },
         "trigger_script": {
@@ -14605,6 +14915,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         },
         "trigger_script": {
@@ -14637,6 +14948,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         },
         "trigger_script": {
@@ -14669,6 +14981,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         },
         "trigger_script": {
@@ -14701,6 +15014,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         },
         "trigger_script": {
@@ -14733,6 +15047,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         },
         "trigger_script": {
@@ -14765,6 +15080,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         },
         "trigger_script": {
@@ -15015,6 +15331,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -15038,6 +15355,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -15319,7 +15637,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -15341,7 +15660,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -15363,7 +15683,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -15385,7 +15706,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -15411,7 +15733,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -15440,7 +15763,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -15477,7 +15801,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -15500,7 +15825,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -15522,7 +15848,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -15545,7 +15872,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -15568,6 +15896,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -15591,6 +15920,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -15614,6 +15944,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -15637,6 +15968,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -15938,7 +16270,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -15960,7 +16293,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -15982,7 +16316,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -16004,7 +16339,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -16030,7 +16366,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -16059,7 +16396,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -16096,7 +16434,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -16119,7 +16458,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -16141,7 +16481,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -16164,7 +16505,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -16189,6 +16531,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -16212,6 +16555,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -16235,6 +16579,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -16258,6 +16603,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -16281,6 +16627,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -16726,7 +17073,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -16757,7 +17105,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -16788,7 +17137,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -16819,7 +17169,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -16854,7 +17205,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -16892,7 +17244,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -16938,7 +17291,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -16970,7 +17324,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -17001,7 +17356,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -17033,7 +17389,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -17067,6 +17424,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         },
         "trigger_script": {
@@ -17099,6 +17457,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         },
         "trigger_script": {
@@ -17131,6 +17490,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         },
         "trigger_script": {
@@ -17163,6 +17523,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         },
         "trigger_script": {
@@ -17195,6 +17556,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         },
         "trigger_script": {
@@ -17227,6 +17589,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         },
         "trigger_script": {
@@ -17579,7 +17942,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -17601,7 +17965,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -17623,7 +17988,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -17645,7 +18011,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -17671,7 +18038,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -17700,7 +18068,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -17737,7 +18106,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -17760,7 +18130,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -17782,7 +18153,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -17805,7 +18177,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -17830,6 +18203,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -17853,6 +18227,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -17876,6 +18251,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -17899,6 +18275,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -17922,6 +18299,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       },
@@ -17945,6 +18323,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
diff --git a/testing/buildbot/chromium.gpu.json b/testing/buildbot/chromium.gpu.json
index 2ebb3ce..df8abf08f 100644
--- a/testing/buildbot/chromium.gpu.json
+++ b/testing/buildbot/chromium.gpu.json
@@ -22,7 +22,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -44,7 +45,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -66,7 +68,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -88,7 +91,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -114,7 +118,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -143,7 +148,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -180,7 +186,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -203,7 +210,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -225,7 +233,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -249,6 +258,7 @@
               "os": "Android"
             }
           ],
+          "idempotent": false,
           "shards": 6
         }
       }
@@ -335,7 +345,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -357,7 +368,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -379,7 +391,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -401,7 +414,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -427,7 +441,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -456,7 +471,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -493,7 +509,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -516,7 +533,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -538,7 +556,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -561,7 +580,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -584,6 +604,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -685,7 +706,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -707,7 +729,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -729,7 +752,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -751,7 +775,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -777,7 +802,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -806,7 +832,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -843,7 +870,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -866,7 +894,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -888,7 +917,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -911,7 +941,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -934,6 +965,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -1008,7 +1040,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1029,7 +1062,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1050,7 +1084,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1071,7 +1106,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1096,7 +1132,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1124,7 +1161,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1160,7 +1198,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1182,7 +1221,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1203,7 +1243,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1225,6 +1266,7 @@
               "os": "Mac-10.12.6"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -1318,7 +1360,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1339,7 +1382,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1360,7 +1404,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1381,7 +1426,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1406,7 +1452,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1434,7 +1481,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1470,7 +1518,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1492,7 +1541,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1513,7 +1563,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1535,6 +1586,7 @@
               "os": "Mac-10.12.6"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -1617,7 +1669,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1640,7 +1693,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1663,7 +1717,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1686,7 +1741,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1713,7 +1769,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1743,7 +1800,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1781,7 +1839,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1805,7 +1864,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1828,7 +1888,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1852,6 +1913,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -1955,7 +2017,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1978,7 +2041,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2001,7 +2065,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2024,7 +2089,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2051,7 +2117,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2081,7 +2148,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2119,7 +2187,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2143,7 +2212,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2166,7 +2236,8 @@
               "os": "Mac-10.13.5",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2190,6 +2261,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -2327,7 +2399,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2349,7 +2422,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2371,7 +2445,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2393,7 +2468,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2419,7 +2495,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2448,7 +2525,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2485,7 +2563,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2508,7 +2587,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2530,7 +2610,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2553,7 +2634,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2576,6 +2658,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -2796,7 +2879,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -2827,7 +2911,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -2858,7 +2943,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -2889,7 +2975,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -2924,7 +3011,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -2962,7 +3050,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -3008,7 +3097,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -3040,7 +3130,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -3071,7 +3162,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -3103,7 +3195,8 @@
               "os": "Windows-10",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         },
         "trigger_script": {
           "args": [
@@ -3135,6 +3228,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         },
         "trigger_script": {
diff --git a/testing/buildbot/chromium.linux.json b/testing/buildbot/chromium.linux.json
index d34c4a3..85c5bb2a 100644
--- a/testing/buildbot/chromium.linux.json
+++ b/testing/buildbot/chromium.linux.json
@@ -1061,7 +1061,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -1242,16 +1242,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -1426,16 +1416,6 @@
         "test": "sync_integration_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_sync_integration_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "sync_integration_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -1460,16 +1440,6 @@
         "test": "unit_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_unit_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "unit_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -1589,7 +1559,8 @@
         "isolate_name": "telemetry_gpu_unittests",
         "name": "telemetry_gpu_unittests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "idempotent": false
         }
       },
       {
@@ -1602,6 +1573,7 @@
         "swarming": {
           "can_use_on_swarming_builders": true,
           "hard_timeout": 960,
+          "idempotent": false,
           "shards": 12
         }
       },
@@ -1613,6 +1585,7 @@
         "name": "telemetry_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "idempotent": false,
           "shards": 4
         }
       },
@@ -1900,7 +1873,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -2081,16 +2054,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -2266,16 +2229,6 @@
         "test": "sync_integration_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_sync_integration_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "sync_integration_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -2300,16 +2253,6 @@
         "test": "unit_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_unit_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "unit_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -2391,7 +2334,8 @@
         "isolate_name": "telemetry_gpu_unittests",
         "name": "telemetry_gpu_unittests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "idempotent": false
         }
       },
       {
@@ -2404,6 +2348,7 @@
         "swarming": {
           "can_use_on_swarming_builders": true,
           "hard_timeout": 960,
+          "idempotent": false,
           "shards": 12
         }
       },
@@ -2415,6 +2360,7 @@
         "name": "telemetry_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "idempotent": false,
           "shards": 4
         }
       },
@@ -2650,7 +2596,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -3068,7 +3014,8 @@
         "isolate_name": "telemetry_gpu_unittests",
         "name": "telemetry_gpu_unittests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "idempotent": false
         }
       },
       {
@@ -3079,6 +3026,7 @@
         "name": "telemetry_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "idempotent": false,
           "shards": 4
         }
       },
@@ -3523,7 +3471,7 @@
               "os": "Ubuntu-16.04"
             }
           ],
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -3829,21 +3777,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
@@ -4163,21 +4096,6 @@
         "test": "sync_integration_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_sync_integration_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "sync_integration_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
@@ -4222,21 +4140,6 @@
         "test": "unit_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_unit_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-16.04"
-            }
-          ]
-        },
-        "test": "unit_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
@@ -4426,7 +4329,8 @@
             {
               "os": "Ubuntu-16.04"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -4444,6 +4348,7 @@
             }
           ],
           "hard_timeout": 960,
+          "idempotent": false,
           "shards": 12
         }
       },
@@ -4460,6 +4365,7 @@
               "os": "Ubuntu-16.04"
             }
           ],
+          "idempotent": false,
           "shards": 4
         }
       },
diff --git a/testing/buildbot/chromium.mac.json b/testing/buildbot/chromium.mac.json
index d3edce23..f570fd46 100644
--- a/testing/buildbot/chromium.mac.json
+++ b/testing/buildbot/chromium.mac.json
@@ -135,7 +135,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -523,7 +523,8 @@
         "isolate_name": "telemetry_gpu_unittests",
         "name": "telemetry_gpu_unittests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "idempotent": false
         }
       },
       {
@@ -535,6 +536,7 @@
         "swarming": {
           "can_use_on_swarming_builders": true,
           "hard_timeout": 960,
+          "idempotent": false,
           "shards": 12
         }
       },
@@ -546,6 +548,7 @@
         "name": "telemetry_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "idempotent": false,
           "shards": 4
         }
       },
@@ -732,7 +735,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -1120,7 +1123,8 @@
         "isolate_name": "telemetry_gpu_unittests",
         "name": "telemetry_gpu_unittests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "idempotent": false
         }
       },
       {
@@ -1132,6 +1136,7 @@
         "swarming": {
           "can_use_on_swarming_builders": true,
           "hard_timeout": 960,
+          "idempotent": false,
           "shards": 12
         }
       },
@@ -1143,6 +1148,7 @@
         "name": "telemetry_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "idempotent": false,
           "shards": 4
         }
       },
@@ -1456,7 +1462,7 @@
               "os": "Mac-10.12.6"
             }
           ],
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -2216,7 +2222,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -2234,6 +2241,7 @@
             }
           ],
           "hard_timeout": 960,
+          "idempotent": false,
           "shards": 12
         }
       },
@@ -2251,6 +2259,7 @@
               "os": "Mac-10.12.6"
             }
           ],
+          "idempotent": false,
           "shards": 4
         }
       },
@@ -2450,7 +2459,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -2838,7 +2847,8 @@
         "isolate_name": "telemetry_gpu_unittests",
         "name": "telemetry_gpu_unittests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "idempotent": false
         }
       },
       {
@@ -2849,6 +2859,7 @@
         "name": "telemetry_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "idempotent": false,
           "shards": 4
         }
       },
@@ -3048,7 +3059,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -3439,7 +3450,8 @@
         "isolate_name": "telemetry_gpu_unittests",
         "name": "telemetry_gpu_unittests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "idempotent": false
         }
       },
       {
@@ -3450,6 +3462,7 @@
         "name": "telemetry_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "idempotent": false,
           "shards": 4
         }
       },
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index e1e6cd2..d055041 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -781,6 +781,98 @@
       },
       {
         "args": [
+          "--enable-surface-synchronization",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "surface_sync_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "surface_sync_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ],
+          "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"
+            }
+          ],
+          "shards": 2
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
+          "--enable-features=VizDisplayCompositor",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "viz_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "viz_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ],
+          "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"
+            }
+          ],
+          "shards": 10
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
@@ -3694,7 +3786,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -3911,16 +4003,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -4095,16 +4177,6 @@
         "test": "sync_integration_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_sync_integration_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "sync_integration_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -4129,16 +4201,6 @@
         "test": "unit_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_unit_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "unit_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -5658,7 +5720,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -5845,16 +5907,6 @@
         "test": "interactive_ui_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_interactive_ui_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "interactive_ui_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -6023,16 +6075,6 @@
         "test": "sync_integration_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_sync_integration_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "sync_integration_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -6057,16 +6099,6 @@
         "test": "unit_tests"
       },
       {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_unit_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "unit_tests"
-      },
-      {
         "swarming": {
           "can_use_on_swarming_builders": true
         },
@@ -6761,7 +6793,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
diff --git a/testing/buildbot/chromium.perf.fyi.json b/testing/buildbot/chromium.perf.fyi.json
index 8b72aef9..36793e8 100644
--- a/testing/buildbot/chromium.perf.fyi.json
+++ b/testing/buildbot/chromium.perf.fyi.json
@@ -141,6 +141,8 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name",
+          "net_perftests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -226,6 +228,8 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name",
+          "views_perftests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -271,6 +275,8 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name",
+          "load_library_perf_tests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
diff --git a/testing/buildbot/chromium.perf.json b/testing/buildbot/chromium.perf.json
index 658f5708..5d0954a 100644
--- a/testing/buildbot/chromium.perf.json
+++ b/testing/buildbot/chromium.perf.json
@@ -11,6 +11,8 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name",
+          "angle_perftests",
           "--non-telemetry=true",
           "--migrated-test=true",
           "--shard-timeout=300"
@@ -55,6 +57,8 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name",
+          "components_perftests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -98,6 +102,8 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name",
+          "gpu_perftests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -188,6 +194,8 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name",
+          "tracing_perftests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -384,6 +392,8 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name",
+          "tracing_perftests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -447,6 +457,8 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name",
+          "angle_perftests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -488,6 +500,8 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name",
+          "load_library_perf_tests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -529,6 +543,8 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name",
+          "media_perftests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -570,6 +586,8 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name",
+          "passthrough_command_buffer_perftests",
           "--non-telemetry=true",
           "--migrated-test=true",
           "--use-cmd-decoder=passthrough",
@@ -658,6 +676,8 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name",
+          "validating_command_buffer_perftests",
           "--non-telemetry=true",
           "--migrated-test=true",
           "--use-cmd-decoder=validating",
@@ -705,6 +725,8 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name",
+          "components_perftests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -746,6 +768,8 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name",
+          "load_library_perf_tests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -787,6 +811,8 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name",
+          "media_perftests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -936,6 +962,8 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name",
+          "angle_perftests",
           "--non-telemetry=true",
           "--migrated-test=true",
           "--shard-timeout=300"
@@ -980,6 +1008,8 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name",
+          "components_perftests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -1023,6 +1053,8 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name",
+          "gpu_perftests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -1066,6 +1098,8 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name",
+          "media_perftests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -1156,6 +1190,8 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name",
+          "tracing_perftests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -1203,6 +1239,8 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name",
+          "load_library_perf_tests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -1244,6 +1282,8 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name",
+          "media_perftests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -1285,6 +1325,8 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name",
+          "net_perftests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -1371,6 +1413,8 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name",
+          "tracing_perftests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -1416,6 +1460,8 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name",
+          "load_library_perf_tests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -1457,6 +1503,8 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name",
+          "performance_browser_tests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -1547,6 +1595,8 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name",
+          "media_perftests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -1588,6 +1638,8 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name",
+          "net_perftests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -1674,6 +1726,8 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name",
+          "views_perftests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -1719,6 +1773,8 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name",
+          "components_perftests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -1760,6 +1816,8 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name",
+          "media_perftests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
@@ -1846,6 +1904,8 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name",
+          "views_perftests",
           "--non-telemetry=true",
           "--migrated-test=true"
         ],
diff --git a/testing/buildbot/chromium.swarm.json b/testing/buildbot/chromium.swarm.json
index b2e0d7a..15a97646 100644
--- a/testing/buildbot/chromium.swarm.json
+++ b/testing/buildbot/chromium.swarm.json
@@ -705,6 +705,7 @@
             }
           ],
           "hard_timeout": 960,
+          "idempotent": false,
           "shards": 12
         }
       },
@@ -724,6 +725,7 @@
               "os": "ChromeOS"
             }
           ],
+          "idempotent": false,
           "shards": 4
         }
       }
diff --git a/testing/buildbot/chromium.win.json b/testing/buildbot/chromium.win.json
index 3466317..e2e1c06 100644
--- a/testing/buildbot/chromium.win.json
+++ b/testing/buildbot/chromium.win.json
@@ -201,7 +201,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -679,7 +679,8 @@
         "isolate_name": "telemetry_gpu_unittests",
         "name": "telemetry_gpu_unittests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "idempotent": false
         }
       },
       {
@@ -688,6 +689,7 @@
         "swarming": {
           "can_use_on_swarming_builders": true,
           "hard_timeout": 960,
+          "idempotent": false,
           "shards": 12
         }
       },
@@ -699,6 +701,7 @@
         "name": "telemetry_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "idempotent": false,
           "shards": 4
         }
       },
@@ -937,7 +940,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -2039,7 +2042,8 @@
         "isolate_name": "telemetry_gpu_unittests",
         "name": "telemetry_gpu_unittests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "idempotent": false
         }
       },
       {
@@ -2050,6 +2054,7 @@
         "name": "telemetry_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "idempotent": false,
           "shards": 4
         }
       },
@@ -2281,7 +2286,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -2744,7 +2749,8 @@
         "isolate_name": "telemetry_gpu_unittests",
         "name": "telemetry_gpu_unittests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "idempotent": false
         }
       },
       {
@@ -2753,6 +2759,7 @@
         "swarming": {
           "can_use_on_swarming_builders": true,
           "hard_timeout": 960,
+          "idempotent": false,
           "shards": 12
         }
       },
@@ -2764,6 +2771,7 @@
         "name": "telemetry_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "idempotent": false,
           "shards": 4
         }
       },
@@ -3016,7 +3024,7 @@
         "name": "viz_content_browsertests",
         "swarming": {
           "can_use_on_swarming_builders": true,
-          "shards": 2
+          "shards": 10
         },
         "test": "content_browsertests"
       },
@@ -3504,7 +3512,8 @@
         "isolate_name": "telemetry_gpu_unittests",
         "name": "telemetry_gpu_unittests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "idempotent": false
         }
       },
       {
@@ -3517,6 +3526,7 @@
         "swarming": {
           "can_use_on_swarming_builders": true,
           "hard_timeout": 960,
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -3528,6 +3538,7 @@
         "name": "telemetry_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "idempotent": false,
           "shards": 4
         }
       },
diff --git a/testing/buildbot/client.v8.chromium.json b/testing/buildbot/client.v8.chromium.json
index 78e1271..cf8537d 100644
--- a/testing/buildbot/client.v8.chromium.json
+++ b/testing/buildbot/client.v8.chromium.json
@@ -208,16 +208,6 @@
           "can_use_on_swarming_builders": true
         },
         "test": "unit_tests"
-      },
-      {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_unit_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "unit_tests"
       }
     ],
     "isolated_scripts": [
@@ -246,7 +236,8 @@
         "isolate_name": "telemetry_gpu_unittests",
         "name": "telemetry_gpu_unittests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "idempotent": false
         }
       },
       {
@@ -258,6 +249,7 @@
         "swarming": {
           "can_use_on_swarming_builders": true,
           "hard_timeout": 960,
+          "idempotent": false,
           "shards": 12
         }
       },
@@ -269,6 +261,7 @@
         "name": "telemetry_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "idempotent": false,
           "shards": 4
         }
       }
@@ -482,16 +475,6 @@
           "can_use_on_swarming_builders": true
         },
         "test": "unit_tests"
-      },
-      {
-        "args": [
-          "--disable-site-isolation-trials"
-        ],
-        "name": "not_site_per_process_unit_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "unit_tests"
       }
     ],
     "isolated_scripts": [
@@ -520,7 +503,8 @@
         "isolate_name": "telemetry_gpu_unittests",
         "name": "telemetry_gpu_unittests",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "idempotent": false
         }
       },
       {
@@ -531,6 +515,7 @@
         "name": "telemetry_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
+          "idempotent": false,
           "shards": 4
         }
       }
diff --git a/testing/buildbot/client.v8.fyi.json b/testing/buildbot/client.v8.fyi.json
index 4789212..d3feddf 100644
--- a/testing/buildbot/client.v8.fyi.json
+++ b/testing/buildbot/client.v8.fyi.json
@@ -22,7 +22,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -44,7 +45,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -66,7 +68,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -88,7 +91,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -117,7 +121,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -154,7 +159,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -177,7 +183,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -199,7 +206,8 @@
               "device_type": "bullhead",
               "os": "Android"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -223,6 +231,7 @@
               "os": "Android"
             }
           ],
+          "idempotent": false,
           "shards": 6
         }
       }
@@ -249,7 +258,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -271,7 +281,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -293,7 +304,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -315,7 +327,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -344,7 +357,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -381,7 +395,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -404,7 +419,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -426,7 +442,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -451,6 +468,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -474,6 +492,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -500,7 +519,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -522,7 +542,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -544,7 +565,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -566,7 +588,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -595,7 +618,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -632,7 +656,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -655,7 +680,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -677,7 +703,8 @@
               "os": "Ubuntu",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -702,6 +729,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -725,6 +753,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -750,7 +779,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -771,7 +801,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -792,7 +823,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -813,7 +845,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -841,7 +874,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -877,7 +911,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -899,7 +934,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -920,7 +956,8 @@
               "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -944,6 +981,7 @@
               "os": "Mac-10.12.6"
             }
           ],
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -966,6 +1004,7 @@
               "os": "Mac-10.12.6"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
@@ -1047,7 +1086,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1069,7 +1109,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1091,7 +1132,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1113,7 +1155,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1142,7 +1185,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1179,7 +1223,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1202,7 +1247,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1224,7 +1270,8 @@
               "os": "Windows-2008ServerR2-SP1",
               "pool": "Chrome-GPU"
             }
-          ]
+          ],
+          "idempotent": false
         }
       },
       {
@@ -1249,6 +1296,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 20
         }
       },
@@ -1272,6 +1320,7 @@
               "pool": "Chrome-GPU"
             }
           ],
+          "idempotent": false,
           "shards": 2
         }
       }
diff --git a/testing/buildbot/filters/chromeos.services_unittests.filter b/testing/buildbot/filters/chromeos.services_unittests.filter
index fc3660e..0b77adc 100644
--- a/testing/buildbot/filters/chromeos.services_unittests.filter
+++ b/testing/buildbot/filters/chromeos.services_unittests.filter
@@ -3,6 +3,7 @@
 
 # TODO(crbug.com/868060): Enable this.
 -GeolocationChromeOsWifiDataProviderTest.*
+-GeolocationServiceUnitTest.GeolocationConfig
 
 # TODO(crbug.com/866114): Enable this.
 -ScreenManagerOzoneInternalTest.*
diff --git a/testing/buildbot/generate_buildbot_json.py b/testing/buildbot/generate_buildbot_json.py
index ea71c1d3..b7694f77 100755
--- a/testing/buildbot/generate_buildbot_json.py
+++ b/testing/buildbot/generate_buildbot_json.py
@@ -576,6 +576,11 @@
     result['isolate_name'] = 'telemetry_gpu_integration_test'
     args = result.get('args', [])
     test_to_run = result.pop('telemetry_test_name', test_name)
+
+    # These tests upload and download results from cloud storage and therefore
+    # aren't idempotent yet. https://crbug.com/549140.
+    result['swarming']['idempotent'] = False
+
     args = [
       test_to_run,
       '--show-stdout',
diff --git a/testing/buildbot/generate_buildbot_json_unittest.py b/testing/buildbot/generate_buildbot_json_unittest.py
index 8ffc29a3..b57ac108 100755
--- a/testing/buildbot/generate_buildbot_json_unittest.py
+++ b/testing/buildbot/generate_buildbot_json_unittest.py
@@ -931,7 +931,8 @@
             {
               "gpu": "10de:1cb3"
             }
-          ]
+          ],
+          "idempotent": false
         }
       }
     ]
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 9ee6346..1b591a26 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -1038,35 +1038,6 @@
       'Linux TSan Tests',  # No capacity, https://crbug.com/852442
     ],
   },
-  'not_site_per_process_interactive_ui_tests': {
-    'remove_from': [
-      # chromium.linux
-      'Linux Tests (dbg)(1)(32)',
-      # chromium.memory
-      'Linux ASan LSan Tests (1)',  # No capacity, https://crbug.com/852442
-      'Linux TSan Tests',  # No capacity, https://crbug.com/852442
-    ],
-  },
-  'not_site_per_process_sync_integration_tests': {
-    'remove_from': [
-      # chromium.linux
-      'Linux Tests (dbg)(1)(32)',
-      # chromium.memory
-      'Linux ASan LSan Tests (1)',  # No capacity, https://crbug.com/852442
-      'Linux TSan Tests',  # No capacity, https://crbug.com/852442
-    ],
-  },
-  'not_site_per_process_unit_tests': {
-    'remove_from': [
-      # chromium.fyi
-      'Site Isolation Android',
-      # chromium.linux
-      'Linux Tests (dbg)(1)(32)',
-      # chromium.memory
-      'Linux ASan LSan Tests (1)',  # No capacity, https://crbug.com/852442
-      'Linux TSan Tests',  # No capacity, https://crbug.com/852442
-    ],
-  },
   'site_per_process_webkit_layout_tests': {
     'remove_from': [
       # chromium.linux
@@ -1107,6 +1078,17 @@
       },
     },
   },
+  'surface_sync_content_browsertests': {
+    'modifications': {
+      # chromium.android
+      'android-kitkat-arm-rel': {
+        'experiment_percentage': 100,
+      },
+      'android-marshmallow-arm64-rel': {
+        'experiment_percentage': 100,
+      },
+    },
+  },
   'sync_integration_tests': {
     'modifications': {
       'Win7 Tests (dbg)(1)': {
@@ -1313,10 +1295,20 @@
   },
   'viz_content_browsertests': {
     'remove_from': [
+      # chromium.android
+      # GL OOM error triggered on KitKat. crbug.com/863049
+      'android-kitkat-arm-rel',
       # Currently flaky timeouts on Windows 10. crbug.com/839824
       'Win10 Tests x64 (dbg)',
     ],
     'modifications': {
+      # chromium.android
+      'android-kitkat-arm-rel': {
+        'experiment_percentage': 100,
+      },
+      'android-marshmallow-arm64-rel': {
+        'experiment_percentage': 100,
+      },
       # chromium.clang
       'ToTLinuxUBSanVptr': {
         'swarming': {
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 3c2f653..b1002d56 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -118,12 +118,12 @@
       },
       'test': 'chrome_public_test_vr_apk',
     },
-    'chrome_public_test_vr_apk-marlin-ddview-nougat-donenabled': {
+    'chrome_public_test_vr_apk-marlin-nougat-dynamicsettings': {
       'args': [
-        '--shared-prefs-file=//chrome/android/shared_preference_files/test/vr_ddview_don_setupcomplete.json',
+        '--shared-prefs-file=//chrome/android/shared_preference_files/test/vr_enable_vr_settings_service.json',
         '--replace-system-package=com.google.vr.vrcore,//third_party/gvr-android-sdk/test-apks/vr_services/vr_services_current.apk',
-        '--annotation=Restriction=DON_Enabled',
-        '--don-enabled',
+        '--annotation=Restriction=VR_Settings_Service',
+        '--vr-settings-service-enabled',
       ],
       'swarming': {
         'dimension_sets': [
@@ -189,13 +189,13 @@
       ],
       'test': 'chrome_public_test_vr_apk',
     },
-    'chrome_public_test_vr_apk (don enabled)': {
+    'chrome_public_test_vr_apk (dynamic settings)': {
       'args': [
-        '--shared-prefs-file=src/chrome/android/shared_preference_files/test/vr_ddview_don_setupcomplete.json',
+        '--shared-prefs-file=src/chrome/android/shared_preference_files/test/vr_enable_vr_settings_service.json',
         '--additional-apk=src/third_party/gvr-android-sdk/test-apks/vr_services/vr_services_current.apk',
         '--additional-apk=src/third_party/gvr-android-sdk/test-apks/daydream_home/daydream_home_current.apk',
-        '--annotation=Restriction=DON_Enabled',
-        '--don-enabled',
+        '--annotation=Restriction=VR_Settings_Service',
+        '--vr-settings-service-enabled',
       ],
       'test': 'chrome_public_test_vr_apk',
     },
@@ -224,12 +224,12 @@
       ],
       'test': 'chrome_public_test_vr_apk',
     },
-    'chrome_public_test_vr_apk-ddready-ddview-donenabled': {
+    'chrome_public_test_vr_apk-ddready-dynamicsettings': {
       'args': [
-        '--shared-prefs-file=//chrome/android/shared_preference_files/test/vr_ddview_don_setupcomplete.json',
+        '--shared-prefs-file=//chrome/android/shared_preference_files/test/vr_enable_vr_settings_service.json',
         '--replace-system-package=com.google.vr.vrcore,//third_party/gvr-android-sdk/test-apks/vr_services/vr_services_current.apk',
-        '--annotation=Restriction=DON_Enabled',
-        '--don-enabled',
+        '--annotation=Restriction=VR_Settings_Service',
+        '--vr-settings-service-enabled',
       ],
       'test': 'chrome_public_test_vr_apk',
     },
@@ -427,6 +427,7 @@
       ],
       'swarming': {
         'hard_timeout': 1200,
+        'idempotent': False,
         'shards': 12,
       },
     },
@@ -444,6 +445,7 @@
       ],
       'swarming': {
         'hard_timeout': 1200,
+        'idempotent': False,
         'shards': 6,
       },
     },
@@ -458,6 +460,7 @@
       ],
       'swarming': {
         'hard_timeout': 960,
+        'idempotent': False,
         'shards': 12,
       },
     },
@@ -468,6 +471,7 @@
         '--jobs=1',
       ],
       'swarming': {
+        'idempotent': False,
         'shards': 4,
       },
     },
@@ -576,6 +580,15 @@
         'shards': 5,
       },
     },
+    'viz_content_browsertests': {
+      'args': [
+        '--enable-features=VizDisplayCompositor',
+      ],
+      'swarming': {
+        'shards': 10,
+      },
+      'test': 'content_browsertests',
+    },
     'viz_unittests': {},
   },
 
@@ -821,12 +834,6 @@
       ],
       'test': 'content_unittests',
     },
-    'not_site_per_process_unit_tests': {
-      'args': [
-        '--disable-site-isolation-trials'
-      ],
-      'test': 'unit_tests',
-    },
     'sync_integration_tests': {},
     'unit_tests': {},
   },
@@ -835,13 +842,18 @@
     'content_shell_crash_test': {},
     'devtools_closure_compile': {},
     'devtools_eslint': {},
-    'telemetry_gpu_unittests': {},
+    'telemetry_gpu_unittests': {
+      'swarming': {
+        'idempotent': False,
+      },
+    },
     'telemetry_perf_unittests': {
       'args': [
         '--xvfb',
       ],
       'swarming': {
         'hard_timeout': 960,
+        'idempotent': False,
         'shards': 12,
       },
     },
@@ -850,6 +862,7 @@
         '--jobs=1',
       ],
       'swarming': {
+        'idempotent': False,
         'shards': 4,
       },
     },
@@ -877,12 +890,17 @@
   'desktop_chromium_isolated_scripts': {
     'content_shell_crash_test': {},
     'metrics_python_tests': {},
-    'telemetry_gpu_unittests': {},
+    'telemetry_gpu_unittests': {
+      'swarming': {
+        'idempotent': False,
+      },
+    },
     'telemetry_unittests': {
       'args': [
         '--jobs=1',
       ],
       'swarming': {
+        'idempotent': False,
         'shards': 4,
       },
     },
@@ -1617,6 +1635,7 @@
       ],
       'swarming': {
         'hard_timeout': 960,
+        'idempotent': False,
         'shards': 12,
       },
     },
@@ -1628,6 +1647,7 @@
       ],
       'swarming': {
         'hard_timeout': 960,
+        'idempotent': False,
         'shards': 12,
       },
     },
@@ -1637,11 +1657,13 @@
         '--extra-browser-args=--enable-features=VizDisplayCompositor',
       ],
       'swarming': {
+        'idempotent': False,
         'shards': 4,
       },
      },
      'telemetry_unittests': {
        'swarming': {
+         'idempotent': False,
          'shards': 4,
        },
      },
@@ -1885,15 +1907,6 @@
       },
       'test': 'browser_tests',
     },
-    'viz_content_browsertests': {
-      'args': [
-        '--enable-features=VizDisplayCompositor',
-      ],
-      'swarming': {
-        'shards': 2,
-      },
-      'test': 'content_browsertests',
-    },
     'viz_content_unittests': {
       'args': [
         '--enable-features=VizDisplayCompositor',
@@ -1952,12 +1965,6 @@
       ],
       'test': 'content_unittests',
     },
-    'not_site_per_process_unit_tests': {
-      'args': [
-        '--disable-site-isolation-trials'
-      ],
-      'test': 'unit_tests',
-    },
   },
 
   'site_isolation_chromium_linux_gtests': {
@@ -1985,18 +1992,6 @@
       ],
       'test': 'extensions_unittests',
     },
-    'not_site_per_process_interactive_ui_tests': {
-      'args': [
-        '--disable-site-isolation-trials'
-      ],
-      'test': 'interactive_ui_tests',
-    },
-    'not_site_per_process_sync_integration_tests': {
-      'args': [
-        '--disable-site-isolation-trials'
-      ],
-      'test': 'sync_integration_tests',
-    },
   },
 
   'site_isolation_android_fyi_specific_gtests': {
@@ -2023,6 +2018,18 @@
     },
   },
 
+  'surface_sync_gtests': {
+     'surface_sync_content_browsertests': {
+      'args': [
+        '--enable-surface-synchronization',
+      ],
+      'swarming': {
+        'shards': 2,
+      },
+      'test': 'content_browsertests',
+    },
+  },
+
   'system_webview_shell_instrumentation_tests': {
     'system_webview_shell_layout_test_apk': {},
   },
@@ -2050,6 +2057,7 @@
     'telemetry_perf_unittests': {
       'swarming': {
         'hard_timeout': 960,
+        'idempotent': False,
         'shards': 12,
       },
     },
@@ -2059,16 +2067,7 @@
     'components_perftests': {},
   },
 
-  'viz_fyi_gtests': {
-    'viz_browser_tests': {
-      'args': [
-        '--enable-features=VizDisplayCompositor',
-      ],
-      'swarming': {
-        'shards': 10,
-      },
-      'test': 'browser_tests',
-    },
+  'viz_gtests': {
     'viz_content_browsertests': {
       'args': [
         '--enable-features=VizDisplayCompositor',
@@ -2078,6 +2077,18 @@
       },
       'test': 'content_browsertests',
     },
+  },
+
+  'viz_non_android_fyi_gtests': {
+    'viz_browser_tests': {
+      'args': [
+        '--enable-features=VizDisplayCompositor',
+      ],
+      'swarming': {
+        'shards': 10,
+      },
+      'test': 'browser_tests',
+    },
     'viz_content_unittests': {
       'args': [
         '--enable-features=VizDisplayCompositor',
@@ -2092,27 +2103,6 @@
      },
   },
 
-  'viz_fyi_android_gtests': {
-     'surface_sync_content_browsertests': {
-      'args': [
-        '--enable-surface-synchronization',
-      ],
-      'swarming': {
-        'shards': 2,
-      },
-      'test': 'content_browsertests',
-    },
-    'viz_content_browsertests': {
-      'args': [
-        '--enable-features=VizDisplayCompositor',
-      ],
-      'swarming': {
-        'shards': 2,
-      },
-      'test': 'content_browsertests',
-    },
-  },
-
   'vr_platform_specific_chromium_gtests': {
     # Only run on platforms that intend to support WebVR in the near
     # future.
@@ -2226,6 +2216,7 @@
     'chromium_gtests',
     'chromium_gtests_for_devices_with_graphical_output',
     'linux_flavor_specific_chromium_gtests',
+    'surface_sync_gtests',
     'vr_platform_specific_chromium_gtests',
   ],
 
@@ -2606,6 +2597,11 @@
 
   # END composition test suites used by the GPU bots
 
+  'linux_viz_gtests': [
+    'viz_gtests',
+    'viz_non_android_fyi_gtests',
+  ],
+
   'marshmallow_isolated_scripts': [
     'components_perftests_isolated_scripts',
     'monochrome_apk_checker_isolated_script',
@@ -2614,20 +2610,23 @@
 
   'mojo_android_gtests': [
     'network_service_android_gtests',
-    'viz_fyi_android_gtests',
+    'surface_sync_gtests',
+    'viz_gtests',
   ],
 
   'mojo_windows_gtests': [
     'mojo_windows_specific_gtests',
     'network_service_gtests',
-    'viz_fyi_gtests',
+    'viz_gtests',
+    'viz_non_android_fyi_gtests',
   ],
 
   'mojo_chromiumos_fyi_gtests': [
     'aura_gtests',
     'mash_chromium_gtests',
     'mojo_chromiumos_specific_gtests',
-    'viz_fyi_gtests',
+    'viz_gtests',
+    'viz_non_android_fyi_gtests',
   ],
 
   'sandboxed_chromium_memory_linux_gtests': [
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index a5143f5..91936841 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -2348,7 +2348,7 @@
           'all',
         ],
         'test_suites': {
-          'gtest_tests': 'viz_fyi_gtests',
+          'gtest_tests': 'linux_viz_gtests',
           'isolated_scripts': 'linux_viz_isolated_scripts',
         },
       },
diff --git a/testing/scripts/run_performance_tests.py b/testing/scripts/run_performance_tests.py
index e7d67082..8ce37cb 100755
--- a/testing/scripts/run_performance_tests.py
+++ b/testing/scripts/run_performance_tests.py
@@ -178,6 +178,10 @@
   parser.add_argument('--xvfb', help='Start xvfb.', action='store_true')
   parser.add_argument('--non-telemetry',
                       help='Type of perf test', type=bool, default=False)
+  parser.add_argument('--gtest-benchmark-name',
+                      help='Name of the gtest benchmark', type=str,
+                      required=False)
+
   parser.add_argument('--benchmarks',
                       help='Comma separated list of benchmark names'
                       ' to run in lieu of indexing into our benchmark bot maps',
@@ -194,8 +198,13 @@
   return_code = 0
 
   if args.non_telemetry:
-    # For non telemetry tests the benchmark name is the name of the executable.
-    benchmark_name = rest_args[0]
+    benchmark_name = args.gtest_benchmark_name
+    # Fallback to use the name of the executable if flag isn't set.
+    # TODO(crbug.com/870899): remove fallback logic and raise parser error if
+    # -non-telemetry is set but --gtest-benchmark-name is not set once pinpoint
+    # is converted to always pass --gtest-benchmark-name flag.
+    if not benchmark_name:
+      benchmark_name = rest_args[0]
     return_code, charts, output_json = run_gtest_perf_test.execute_perf_test(
         args, rest_args)
 
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index e04172f..ed4d737 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -26,12 +26,11 @@
                 "mac",
                 "chromeos",
                 "linux",
-                "ios",
                 "android"
             ],
             "experiments": [
                 {
-                    "name": "LowPriorityAdFrameEnabled",
+                    "name": "LowPriorityForAdFrame",
                     "enable_features": [
                         "BlinkSchedulerLowPriorityForAdFrame"
                     ]
@@ -1662,7 +1661,7 @@
                 {
                     "name": "LowPriorityForHiddenFrameDuringLoadingEnabled",
                     "enable_features": [
-                        "BlinkSchedulerExperimentOnlyWhenLoading",
+                        "BlinkSchedulerFrameExperimentOnlyWhenLoading",
                         "BlinkSchedulerLowPriorityForHiddenFrame"
                     ]
                 }
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index cfbe1d4..19526a3b 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -2203,7 +2203,7 @@
 crbug.com/665577 [ Linux Win ] virtual/threaded/fast/scroll-behavior/smooth-scroll/mousewheel-scroll-interrupted.html [ Pass Failure Timeout ]
 crbug.com/665577 [ Mac ] virtual/threaded/fast/scroll-behavior/smooth-scroll/track-scroll.html [ Pass Failure ]
 
-crbug.com/599670 [ Win ] http/tests/devtools/resource-parameters-ipv6.js [ Pass Failure ]
+crbug.com/599670 [ Win ] http/tests/devtools/resource-parameters-ipv6.js [ Pass Failure Crash ]
 crbug.com/472330 fast/borders/border-image-outset-split-inline-vertical-lr.html [ Failure ]
 crbug.com/472330 fast/writing-mode/box-shadow-vertical-lr.html [ Failure ]
 crbug.com/472330 fast/writing-mode/box-shadow-vertical-rl.html [ Failure ]
@@ -2652,7 +2652,7 @@
 crbug.com/808834 [ Linux Win ] external/wpt/css/css-pseudo/first-letter-001.html [ Failure ]
 
 
-crbug.com/723741 virtual/threaded/http/tests/devtools/tracing/idle-callback.js [ Failure Pass Timeout ]
+crbug.com/723741 virtual/threaded/http/tests/devtools/tracing/idle-callback.js [ Failure Crash Pass Timeout ]
 
 
 # Untriaged failures after https://crrev.com/c/543695/.
diff --git a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json
index 73ec95e..b36e443 100644
--- a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json
+++ b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json
@@ -5995,9 +5995,15 @@
      {}
     ]
    ],
-   "uievents/auxclick/auxclick_event-manual.html": [
+   "uievents/click/auxclick_event-manual.html": [
     [
-     "/uievents/auxclick/auxclick_event-manual.html",
+     "/uievents/click/auxclick_event-manual.html",
+     {}
+    ]
+   ],
+   "uievents/click/click_event_target-manual.html": [
+    [
+     "/uievents/click/click_event_target-manual.html",
      {}
     ]
    ],
@@ -159469,12 +159475,12 @@
      {}
     ]
    ],
-   "interfaces/webrtc-pc.idl": [
+   "interfaces/webrtc-stats.idl": [
     [
      {}
     ]
    ],
-   "interfaces/webrtc-stats.idl": [
+   "interfaces/webrtc.idl": [
     [
      {}
     ]
@@ -169459,7 +169465,7 @@
      {}
     ]
    ],
-   "uievents/auxclick/auxclick_event-manual-expected.txt": [
+   "uievents/click/auxclick_event-manual-expected.txt": [
     [
      {}
     ]
@@ -171629,6 +171635,11 @@
      {}
     ]
    ],
+   "webrtc/idlharness.https.window-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "webrtc/interfaces.https-expected.txt": [
     [
      {}
@@ -256734,9 +256745,9 @@
      {}
     ]
    ],
-   "webrtc/interfaces.https.html": [
+   "webrtc/idlharness.https.window.js": [
     [
-     "/webrtc/interfaces.https.html",
+     "/webrtc/idlharness.https.window.html",
      {}
     ]
    ],
@@ -379609,14 +379620,14 @@
    "5ec433d860f230c3eddddc38bc1f4d40e94d4b23",
    "support"
   ],
-  "interfaces/webrtc-pc.idl": [
-   "a631e2e0ea0f451c64b5d5f74fe7cbeafc231b8e",
-   "support"
-  ],
   "interfaces/webrtc-stats.idl": [
    "a4a2e5152e3c96a5efa26893ed34856ebe5348c0",
    "support"
   ],
+  "interfaces/webrtc.idl": [
+   "d7cd98169078292592ff7443f5754d5496ca2f75",
+   "support"
+  ],
   "interfaces/webusb.idl": [
    "8b6b064f5dc45751da07476ef2c05390a23a2d79",
    "support"
@@ -406629,14 +406640,18 @@
    "bf426dc592940dbabd23db6c2343bcc5d29dc4b8",
    "support"
   ],
-  "uievents/auxclick/auxclick_event-manual-expected.txt": [
+  "uievents/click/auxclick_event-manual-expected.txt": [
    "fb32fe1cf7ab2543974237e66e09e90ec029c58d",
    "support"
   ],
-  "uievents/auxclick/auxclick_event-manual.html": [
+  "uievents/click/auxclick_event-manual.html": [
    "464073435190f51b2725653a0a0589e10f136d2d",
    "manual"
   ],
+  "uievents/click/click_event_target-manual.html": [
+   "9f801897d495eb79b6c01628d2e40cf2520a7b30",
+   "manual"
+  ],
   "uievents/constructors/README.md": [
    "771f659821e2860457b741385606f714acc01e05",
    "support"
@@ -410877,14 +410892,18 @@
    "8286dcf59e5eae0320a569c2097423d9589b0be2",
    "support"
   ],
+  "webrtc/idlharness.https.window-expected.txt": [
+   "fc927276b7994269703a563bc0c90e9976da1b2b",
+   "support"
+  ],
+  "webrtc/idlharness.https.window.js": [
+   "9939f13538b34536848839a42dfd6c92e4404f23",
+   "testharness"
+  ],
   "webrtc/interfaces.https-expected.txt": [
    "a8e17f8faf114cbc75ba49ed9992630c7e918cdf",
    "support"
   ],
-  "webrtc/interfaces.https.html": [
-   "40ccef5265fde51caf21ff0fa35859523ee154f7",
-   "testharness"
-  ],
   "webrtc/no-media-call.html": [
    "e9f056be5a865f1bb73d60ec72c38f8d5f16d747",
    "testharness"
diff --git a/third_party/WebKit/LayoutTests/external/wpt/interfaces/webrtc-pc.idl b/third_party/WebKit/LayoutTests/external/wpt/interfaces/webrtc-pc.idl
deleted file mode 100644
index fde2ba02..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/interfaces/webrtc-pc.idl
+++ /dev/null
@@ -1,681 +0,0 @@
-// GENERATED CONTENT - DO NOT EDIT
-// Content of this file was automatically extracted from the WebRTC spec.
-// See https://w3c.github.io/webrtc-pc/
-
-dictionary RTCConfiguration {
-             sequence<RTCIceServer>   iceServers;
-             RTCIceTransportPolicy    iceTransportPolicy = "all";
-             RTCBundlePolicy          bundlePolicy = "balanced";
-             RTCRtcpMuxPolicy         rtcpMuxPolicy = "require";
-             DOMString                peerIdentity;
-             sequence<RTCCertificate> certificates;
-             [EnforceRange]
-             octet                    iceCandidatePoolSize = 0;
-};
-
-enum RTCIceCredentialType {
-    "password",
-    "oauth"
-};
-
-dictionary RTCOAuthCredential {
-    required DOMString        macKey;
-    required DOMString        accessToken;
-};
-
-dictionary RTCIceServer {
-    required (DOMString or sequence<DOMString>) urls;
-             DOMString                          username;
-             (DOMString or RTCOAuthCredential)     credential;
-             RTCIceCredentialType               credentialType = "password";
-};
-
-enum RTCIceTransportPolicy {
-    "relay",
-    "all"
-};
-
-enum RTCBundlePolicy {
-    "balanced",
-    "max-compat",
-    "max-bundle"
-};
-
-enum RTCRtcpMuxPolicy {
-    // At risk due to lack of implementers' interest.
-    "negotiate",
-    "require"
-};
-
-dictionary RTCOfferAnswerOptions {
-             boolean voiceActivityDetection = true;
-};
-
-dictionary RTCOfferOptions : RTCOfferAnswerOptions {
-             boolean iceRestart = false;
-};
-
-          dictionary RTCAnswerOptions : RTCOfferAnswerOptions {
-};
-
-enum RTCSignalingState {
-    "stable",
-    "have-local-offer",
-    "have-remote-offer",
-    "have-local-pranswer",
-    "have-remote-pranswer",
-    "closed"
-};
-
-enum RTCIceGatheringState {
-    "new",
-    "gathering",
-    "complete"
-};
-
-enum RTCPeerConnectionState {
-    "new",
-    "connecting",
-    "connected",
-    "disconnected",
-    "failed",
-    "closed"
-};
-
-enum RTCIceConnectionState {
-    "new",
-    "checking",
-    "connected",
-    "completed",
-    "disconnected",
-    "failed",
-    "closed"
-};
-
-          [ Constructor (optional RTCConfiguration configuration), Exposed=Window]
-interface RTCPeerConnection : EventTarget  {
-    Promise<RTCSessionDescriptionInit> createOffer (optional RTCOfferOptions options);
-    Promise<RTCSessionDescriptionInit> createAnswer (optional RTCAnswerOptions options);
-    Promise<void>                      setLocalDescription (RTCSessionDescriptionInit description);
-    readonly        attribute RTCSessionDescription?    localDescription;
-    readonly        attribute RTCSessionDescription?    currentLocalDescription;
-    readonly        attribute RTCSessionDescription?    pendingLocalDescription;
-    Promise<void>                      setRemoteDescription (RTCSessionDescriptionInit description);
-    readonly        attribute RTCSessionDescription?    remoteDescription;
-    readonly        attribute RTCSessionDescription?    currentRemoteDescription;
-    readonly        attribute RTCSessionDescription?    pendingRemoteDescription;
-    Promise<void>                      addIceCandidate ((RTCIceCandidateInit or RTCIceCandidate) candidate);
-    readonly        attribute RTCSignalingState         signalingState;
-    readonly        attribute RTCIceGatheringState      iceGatheringState;
-    readonly        attribute RTCIceConnectionState     iceConnectionState;
-    readonly        attribute RTCPeerConnectionState    connectionState;
-    readonly        attribute boolean?                  canTrickleIceCandidates;
-    static sequence<RTCIceServer>      getDefaultIceServers ();
-    RTCConfiguration                   getConfiguration ();
-    void                               setConfiguration (RTCConfiguration configuration);
-    void                               close ();
-                    attribute EventHandler              onnegotiationneeded;
-                    attribute EventHandler              onicecandidate;
-                    attribute EventHandler              onicecandidateerror;
-                    attribute EventHandler              onsignalingstatechange;
-                    attribute EventHandler              oniceconnectionstatechange;
-                    attribute EventHandler              onicegatheringstatechange;
-                    attribute EventHandler              onconnectionstatechange;
-};
-
-partial interface RTCPeerConnection {
-    Promise<void> createOffer (RTCSessionDescriptionCallback successCallback, RTCPeerConnectionErrorCallback failureCallback, optional RTCOfferOptions options);
-    Promise<void> setLocalDescription (RTCSessionDescriptionInit description, VoidFunction successCallback, RTCPeerConnectionErrorCallback failureCallback);
-    Promise<void> createAnswer (RTCSessionDescriptionCallback successCallback, RTCPeerConnectionErrorCallback failureCallback);
-    Promise<void> setRemoteDescription (RTCSessionDescriptionInit description, VoidFunction successCallback, RTCPeerConnectionErrorCallback failureCallback);
-    Promise<void> addIceCandidate ((RTCIceCandidateInit or RTCIceCandidate) candidate, VoidFunction successCallback, RTCPeerConnectionErrorCallback failureCallback);
-};
-
-                callback RTCPeerConnectionErrorCallback = void (DOMException error);
-
-                callback RTCSessionDescriptionCallback = void (RTCSessionDescriptionInit description);
-
-partial dictionary RTCOfferOptions {
-            boolean offerToReceiveAudio;
-            boolean offerToReceiveVideo;
-          };
-
-enum RTCSdpType {
-    "offer",
-    "pranswer",
-    "answer",
-    "rollback"
-};
-
-          [ Constructor (RTCSessionDescriptionInit descriptionInitDict), Exposed=Window]
-interface RTCSessionDescription {
-    readonly        attribute RTCSdpType type;
-    readonly        attribute DOMString  sdp;
-    [Default] object toJSON();
-};
-
-dictionary RTCSessionDescriptionInit {
-    required RTCSdpType type;
-             DOMString  sdp = "";
-};
-
-          [ Constructor (optional RTCIceCandidateInit candidateInitDict), Exposed=Window]
-interface RTCIceCandidate {
-    readonly        attribute DOMString               candidate;
-    readonly        attribute DOMString?              sdpMid;
-    readonly        attribute unsigned short?         sdpMLineIndex;
-    readonly        attribute DOMString?              foundation;
-    readonly        attribute RTCIceComponent?        component;
-    readonly        attribute unsigned long?          priority;
-    readonly        attribute DOMString?              ip;
-    readonly        attribute RTCIceProtocol?         protocol;
-    readonly        attribute unsigned short?         port;
-    readonly        attribute RTCIceCandidateType?    type;
-    readonly        attribute RTCIceTcpCandidateType? tcpType;
-    readonly        attribute DOMString?              relatedAddress;
-    readonly        attribute unsigned short?         relatedPort;
-    readonly        attribute DOMString?              usernameFragment;
-    RTCIceCandidateInit toJSON();
-};
-
-dictionary RTCIceCandidateInit {
-             DOMString       candidate = "";
-             DOMString?      sdpMid = null;
-             unsigned short? sdpMLineIndex = null;
-             DOMString       usernameFragment;
-};
-
-enum RTCIceProtocol {
-    "udp",
-    "tcp"
-};
-
-enum RTCIceTcpCandidateType {
-    "active",
-    "passive",
-    "so"
-};
-
-enum RTCIceCandidateType {
-    "host",
-    "srflx",
-    "prflx",
-    "relay"
-};
-
-          [ Constructor (DOMString type, optional RTCPeerConnectionIceEventInit eventInitDict), Exposed=Window]
-interface RTCPeerConnectionIceEvent : Event {
-    readonly        attribute RTCIceCandidate? candidate;
-    readonly        attribute DOMString?       url;
-};
-
-          dictionary RTCPeerConnectionIceEventInit : EventInit {
-             RTCIceCandidate? candidate;
-             DOMString?       url;
-};
-
-          [ Constructor (DOMString type, RTCPeerConnectionIceErrorEventInit eventInitDict), Exposed=Window]
-interface RTCPeerConnectionIceErrorEvent : Event {
-    readonly        attribute DOMString      hostCandidate;
-    readonly        attribute DOMString      url;
-    readonly        attribute unsigned short errorCode;
-    readonly        attribute USVString      errorText;
-};
-
-          dictionary RTCPeerConnectionIceErrorEventInit : EventInit {
-             DOMString      hostCandidate;
-             DOMString      url;
-             required unsigned short errorCode;
-             USVString      statusText;
-};
-
-enum RTCPriorityType {
-    "very-low",
-    "low",
-    "medium",
-    "high"
-};
-
-partial interface RTCPeerConnection {
-    static Promise<RTCCertificate> generateCertificate (AlgorithmIdentifier keygenAlgorithm);
-};
-
-dictionary RTCCertificateExpiration {
-    [EnforceRange]
-    DOMTimeStamp expires;
-};
-
-[Exposed=Window] interface RTCCertificate {
-    readonly        attribute DOMTimeStamp expires;
-    static sequence<AlgorithmIdentifier> getSupportedAlgorithms();
-    sequence<RTCDtlsFingerprint> getFingerprints ();
-};
-
-partial interface RTCPeerConnection {
-    sequence<RTCRtpSender>      getSenders ();
-    sequence<RTCRtpReceiver>    getReceivers ();
-    sequence<RTCRtpTransceiver> getTransceivers ();
-    RTCRtpSender                addTrack (MediaStreamTrack track, MediaStream... streams);
-    void                        removeTrack (RTCRtpSender sender);
-    RTCRtpTransceiver           addTransceiver ((MediaStreamTrack or DOMString) trackOrKind, optional RTCRtpTransceiverInit init);
-                    attribute EventHandler ontrack;
-};
-
-dictionary RTCRtpTransceiverInit {
-             RTCRtpTransceiverDirection         direction = "sendrecv";
-             sequence<MediaStream>              streams = [];
-             sequence<RTCRtpEncodingParameters> sendEncodings = [];
-};
-
-enum RTCRtpTransceiverDirection {
-    "sendrecv",
-    "sendonly",
-    "recvonly",
-    "inactive"
-};
-
-[Exposed=Window] interface RTCRtpSender {
-    readonly        attribute MediaStreamTrack? track;
-    readonly        attribute RTCDtlsTransport?  transport;
-    readonly        attribute RTCDtlsTransport? rtcpTransport;
-    static RTCRtpCapabilities getCapabilities (DOMString kind);
-    Promise<void>             setParameters (optional RTCRtpParameters parameters);
-    RTCRtpParameters          getParameters ();
-    Promise<void>             replaceTrack (MediaStreamTrack? withTrack);
-    Promise<RTCStatsReport>   getStats();
-};
-
-dictionary RTCRtpParameters {
-             DOMString                                 transactionId;
-             sequence<RTCRtpEncodingParameters>        encodings;
-             sequence<RTCRtpHeaderExtensionParameters> headerExtensions;
-             RTCRtcpParameters                         rtcp;
-             sequence<RTCRtpCodecParameters>           codecs;
-             RTCDegradationPreference                  degradationPreference;
-};
-
-dictionary RTCRtpEncodingParameters {
-             octet               codecPayloadType;
-             RTCDtxStatus        dtx;
-             boolean             active = true;
-             RTCPriorityType     priority = "low";
-             unsigned long       ptime;
-             unsigned long       maxBitrate;
-             double              maxFramerate;
-             DOMString           rid;
-             double              scaleResolutionDownBy;
-};
-
-enum RTCDtxStatus {
-         "disabled",
-         "enabled"
-         };
-
-enum RTCDegradationPreference {
-    "maintain-framerate",
-    "maintain-resolution",
-    "balanced"
-};
-
-dictionary RTCRtcpParameters {
-             DOMString cname;
-             boolean   reducedSize;
-};
-
-dictionary RTCRtpHeaderExtensionParameters {
-             DOMString      uri;
-             unsigned short id;
-             boolean        encrypted;
-};
-
-dictionary RTCRtpCodecParameters {
-             octet          payloadType;
-             DOMString      mimeType;
-             unsigned long  clockRate;
-             unsigned short channels;
-             DOMString      sdpFmtpLine;
-};
-
-dictionary RTCRtpCapabilities {
-             sequence<RTCRtpCodecCapability>           codecs;
-             sequence<RTCRtpHeaderExtensionCapability> headerExtensions;
-};
-
-dictionary RTCRtpCodecCapability {
-             DOMString mimeType;
-             unsigned long  clockRate;
-             unsigned short channels;
-             DOMString      sdpFmtpLine;
-};
-
-dictionary RTCRtpHeaderExtensionCapability {
-             DOMString uri;
-};
-
-[Exposed=Window] interface RTCRtpReceiver {
-    readonly        attribute MediaStreamTrack  track;
-    readonly        attribute RTCDtlsTransport? transport;
-    readonly        attribute RTCDtlsTransport? rtcpTransport;
-    static RTCRtpCapabilities          getCapabilities (DOMString kind);
-    RTCRtpParameters                   getParameters ();
-    sequence<RTCRtpContributingSource>    getContributingSources ();
-    sequence<RTCRtpSynchronizationSource> getSynchronizationSources ();
-    Promise<RTCStatsReport>   getStats();
-};
-
-dictionary RTCRtpContributingSource {
-    required DOMHighResTimeStamp timestamp;
-    required unsigned long       source;
-             double              audioLevel;
-};
-
-dictionary RTCRtpSynchronizationSource : RTCRtpContributingSource {
-    boolean voiceActivityFlag;
-};
-
-[Exposed=Window] interface RTCRtpTransceiver {
-    readonly        attribute DOMString?                  mid;
-    [SameObject]
-    readonly        attribute RTCRtpSender                sender;
-    [SameObject]
-    readonly        attribute RTCRtpReceiver              receiver;
-    readonly        attribute boolean                     stopped;
-                    attribute RTCRtpTransceiverDirection  direction;
-    readonly        attribute RTCRtpTransceiverDirection? currentDirection;
-    void stop ();
-    void setCodecPreferences (sequence<RTCRtpCodecCapability> codecs);
-};
-
-[Exposed=Window] interface RTCDtlsTransport : EventTarget {
-    readonly        attribute RTCIceTransport       transport;
-    readonly        attribute RTCDtlsTransportState state;
-    sequence<ArrayBuffer> getRemoteCertificates ();
-                    attribute EventHandler          onstatechange;
-                    attribute EventHandler          onerror;
-};
-
-enum RTCDtlsTransportState {
-    "new",
-    "connecting",
-    "connected",
-    "closed",
-    "failed"
-};
-
-dictionary RTCDtlsFingerprint {
-             DOMString algorithm;
-             DOMString value;
-};
-
-[Exposed=Window] interface RTCIceTransport : EventTarget {
-    readonly        attribute RTCIceRole           role;
-    readonly        attribute RTCIceComponent      component;
-    readonly        attribute RTCIceTransportState state;
-    readonly        attribute RTCIceGathererState gatheringState;
-    sequence<RTCIceCandidate> getLocalCandidates ();
-    sequence<RTCIceCandidate> getRemoteCandidates ();
-    RTCIceCandidatePair?      getSelectedCandidatePair ();
-    RTCIceParameters?         getLocalParameters ();
-    RTCIceParameters?         getRemoteParameters ();
-                    attribute EventHandler         onstatechange;
-                    attribute EventHandler         ongatheringstatechange;
-                    attribute EventHandler         onselectedcandidatepairchange;
-};
-
-dictionary RTCIceParameters {
-             DOMString usernameFragment;
-             DOMString password;
-};
-
-dictionary RTCIceCandidatePair {
-             RTCIceCandidate local;
-             RTCIceCandidate remote;
-};
-
-enum RTCIceGathererState {
-    "new",
-    "gathering",
-    "complete"
-};
-
-enum RTCIceTransportState {
-    "new",
-    "checking",
-    "connected",
-    "completed",
-    "disconnected",
-    "failed",
-    "closed"
-};
-
-enum RTCIceRole {
-    "controlling",
-    "controlled"
-};
-
-enum RTCIceComponent {
-    "rtp",
-    "rtcp"
-};
-
-        [ Constructor (DOMString type, RTCTrackEventInit eventInitDict), Exposed=Window]
-interface RTCTrackEvent : Event {
-    readonly        attribute RTCRtpReceiver           receiver;
-    readonly        attribute MediaStreamTrack         track;
-    [SameObject]
-    readonly        attribute FrozenArray<MediaStream> streams;
-    readonly        attribute RTCRtpTransceiver        transceiver;
-};
-
-dictionary RTCTrackEventInit : EventInit {
-    required RTCRtpReceiver        receiver;
-    required MediaStreamTrack      track;
-             sequence<MediaStream> streams = [];
-    required RTCRtpTransceiver     transceiver;
-};
-
-partial interface RTCPeerConnection {
-    readonly        attribute RTCSctpTransport? sctp;
-    RTCDataChannel createDataChannel (USVString label, optional RTCDataChannelInit dataChannelDict);
-                    attribute EventHandler      ondatachannel;
-};
-
-[Exposed=Window] interface RTCSctpTransport {
-    readonly        attribute RTCDtlsTransport transport;
-    readonly        attribute RTCSctpTransportState state;
-    readonly        attribute unrestricted double maxMessageSize;
-                    attribute EventHandler     onstatechange;
-};
-
-enum RTCSctpTransportState {
-    "new",
-    "connecting",
-    "connected",
-    "closed"
-};
-
-[Exposed=Window] interface RTCDataChannel : EventTarget {
-    readonly        attribute USVString           label;
-    readonly        attribute boolean             ordered;
-    readonly        attribute unsigned short?     maxPacketLifeTime;
-    readonly        attribute unsigned short?     maxRetransmits;
-    readonly        attribute USVString           protocol;
-    readonly        attribute boolean             negotiated;
-    readonly        attribute unsigned short?     id;
-    readonly        attribute RTCPriorityType     priority;
-    readonly        attribute RTCDataChannelState readyState;
-    readonly        attribute unsigned long       bufferedAmount;
-                    attribute unsigned long       bufferedAmountLowThreshold;
-                    attribute EventHandler        onopen;
-                    attribute EventHandler        onbufferedamountlow;
-                    attribute EventHandler        onerror;
-                    attribute EventHandler        onclose;
-    void close ();
-                    attribute EventHandler        onmessage;
-                    attribute DOMString           binaryType;
-    void send (USVString data);
-    void send (Blob data);
-    void send (ArrayBuffer data);
-    void send (ArrayBufferView data);
-};
-
-dictionary RTCDataChannelInit {
-             boolean         ordered = true;
-             unsigned short  maxPacketLifeTime;
-             unsigned short  maxRetransmits;
-             USVString       protocol = "";
-             boolean         negotiated = false;
-             [EnforceRange]
-             unsigned short  id;
-             RTCPriorityType priority = "low";
-};
-
-enum RTCDataChannelState {
-    "connecting",
-    "open",
-    "closing",
-    "closed"
-};
-
-        [ Constructor (DOMString type, RTCDataChannelEventInit eventInitDict), Exposed=Window]
-interface RTCDataChannelEvent : Event {
-    readonly        attribute RTCDataChannel channel;
-};
-
-dictionary RTCDataChannelEventInit : EventInit {
-             required RTCDataChannel channel;
-};
-
-partial interface RTCRtpSender {
-    readonly        attribute RTCDTMFSender? dtmf;
-};
-
-[Exposed=Window] interface RTCDTMFSender : EventTarget {
-    void insertDTMF (DOMString tones, optional unsigned long duration = 100, optional unsigned long interToneGap = 70);
-                    attribute EventHandler ontonechange;
-    readonly        attribute boolean      canInsertDTMF;
-    readonly        attribute DOMString    toneBuffer;
-};
-
-        [ Constructor (DOMString type, RTCDTMFToneChangeEventInit eventInitDict), Exposed=Window]
-interface RTCDTMFToneChangeEvent : Event {
-    readonly        attribute DOMString tone;
-};
-
-dictionary RTCDTMFToneChangeEventInit : EventInit {
-             required DOMString tone;
-};
-
-partial interface RTCPeerConnection {
-    Promise<RTCStatsReport> getStats (optional MediaStreamTrack? selector = null);
-    attribute EventHandler onstatsended;
-          };
-
-[Exposed=Window] interface RTCStatsReport {
-    readonly maplike<DOMString, object>;
-};
-
-dictionary RTCStats {
-             required DOMHighResTimeStamp timestamp;
-             required RTCStatsType        type;
-             required DOMString           id;
-};
-
-[ Constructor (DOMString type, RTCStatsEventInit
-          eventInitDict), Exposed=Window]
-          interface RTCStatsEvent : Event {
-            readonly attribute RTCStatsReport report;
-          };
-
-[Global, Exposed=RTCIdentityProviderGlobalScope]
-interface RTCIdentityProviderGlobalScope : WorkerGlobalScope {
-    readonly        attribute RTCIdentityProviderRegistrar rtcIdentityProvider;
-};
-
-[Exposed=RTCIdentityProviderGlobalScope]
-interface RTCIdentityProviderRegistrar {
-    void register (RTCIdentityProvider idp);
-};
-
-dictionary RTCIdentityProvider {
-    required GenerateAssertionCallback generateAssertion;
-    required ValidateAssertionCallback validateAssertion;
-};
-
-          callback GenerateAssertionCallback = Promise<RTCIdentityAssertionResult>
-          (DOMString contents, DOMString origin, RTCIdentityProviderOptions options);
-
-          callback ValidateAssertionCallback = Promise<RTCIdentityValidationResult>
-          (DOMString assertion, DOMString origin);
-
-dictionary RTCIdentityAssertionResult {
-    required RTCIdentityProviderDetails idp;
-    required DOMString                  assertion;
-};
-
-dictionary RTCIdentityProviderDetails {
-    required DOMString domain;
-             DOMString protocol = "default";
-};
-
-dictionary RTCIdentityValidationResult {
-    required DOMString identity;
-    required DOMString contents;
-};
-
-partial interface RTCPeerConnection {
-    void               setIdentityProvider (DOMString provider, optional RTCIdentityProviderOptions options);
-    Promise<DOMString> getIdentityAssertion ();
-    readonly        attribute Promise<RTCIdentityAssertion> peerIdentity;
-    readonly        attribute DOMString?                    idpLoginUrl;
-    readonly        attribute DOMString?                    idpErrorInfo;
-};
-
-dictionary RTCIdentityProviderOptions {
-    DOMString protocol = "default";
-    DOMString usernameHint;
-    DOMString peerIdentity;
-};
-
-[Constructor(DOMString idp, DOMString name), Exposed=Window]
-interface RTCIdentityAssertion {
-    attribute DOMString idp;
-    attribute DOMString name;
-};
-
-partial dictionary MediaStreamConstraints {
-             DOMString peerIdentity;
-};
-
-partial interface MediaStreamTrack {
-    readonly        attribute boolean      isolated;
-                    attribute EventHandler onisolationchange;
-};
-
-enum RTCErrorDetailType {
-              "data-channel-failure",
-              "dtls-failure",
-              "fingerprint-failure",
-              "idp-bad-script-failure",
-              "idp-execution-failure",
-              "idp-load-failure",
-              "idp-need-login",
-              "idp-timeout",
-              "idp-tls-failure",
-              "idp-token-expired",
-              "idp-token-invalid",
-              "sctp-failure",
-              "sdp-syntax-error",
-              "hardware-encoder-not-available",
-              "hardware-encoder-error"
-          };
-
-[Exposed=Window,
- Constructor (DOMString type, RTCErrorEventInit eventInitDict)]
-interface RTCErrorEvent : Event {
-    readonly        attribute RTCError? error;
-};
-
-dictionary RTCErrorEventInit : EventInit {
-             RTCError? error = null;
-};
diff --git a/third_party/WebKit/LayoutTests/external/wpt/interfaces/webrtc.idl b/third_party/WebKit/LayoutTests/external/wpt/interfaces/webrtc.idl
new file mode 100644
index 0000000..8d949cabb
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/interfaces/webrtc.idl
@@ -0,0 +1,635 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the
+// "WebRTC 1.0: Real-time Communication Between Browsers" spec.
+// See: https://w3c.github.io/webrtc-pc/
+
+dictionary RTCConfiguration {
+             sequence<RTCIceServer> iceServers;
+             RTCIceTransportPolicy iceTransportPolicy = "all";
+             RTCBundlePolicy bundlePolicy = "balanced";
+             RTCRtcpMuxPolicy rtcpMuxPolicy = "require";
+             DOMString peerIdentity;
+             sequence<RTCCertificate> certificates;
+             [EnforceRange]
+             octet iceCandidatePoolSize = 0;
+};
+
+enum RTCIceCredentialType {
+    "password",
+    "oauth"
+};
+
+dictionary RTCOAuthCredential {
+    required DOMString macKey;
+    required DOMString accessToken;
+};
+
+dictionary RTCIceServer {
+    required (DOMString or sequence<DOMString>) urls;
+             DOMString username;
+             (DOMString or RTCOAuthCredential) credential;
+             RTCIceCredentialType credentialType = "password";
+};
+
+enum RTCIceTransportPolicy {
+    "relay",
+    "all"
+};
+
+enum RTCBundlePolicy {
+    "balanced",
+    "max-compat",
+    "max-bundle"
+};
+
+enum RTCRtcpMuxPolicy {
+    // At risk due to lack of implementers' interest.
+    "negotiate",
+    "require"
+};
+
+dictionary RTCOfferAnswerOptions {
+             boolean voiceActivityDetection = true;
+};
+
+dictionary RTCOfferOptions : RTCOfferAnswerOptions {
+             boolean iceRestart = false;
+};
+
+dictionary RTCAnswerOptions : RTCOfferAnswerOptions {
+};
+
+enum RTCSignalingState {
+    "stable",
+    "have-local-offer",
+    "have-remote-offer",
+    "have-local-pranswer",
+    "have-remote-pranswer",
+    "closed"
+};
+
+enum RTCIceGatheringState {
+    "new",
+    "gathering",
+    "complete"
+};
+
+enum RTCPeerConnectionState {
+    "new",
+    "connecting",
+    "connected",
+    "disconnected",
+    "failed",
+    "closed"
+};
+
+enum RTCIceConnectionState {
+    "new",
+    "checking",
+    "connected",
+    "completed",
+    "disconnected",
+    "failed",
+    "closed"
+};
+
+[ Constructor(optional RTCConfiguration configuration), Exposed=Window]
+interface RTCPeerConnection : EventTarget {
+    Promise<RTCSessionDescriptionInit> createOffer(optional RTCOfferOptions options);
+    Promise<RTCSessionDescriptionInit> createAnswer(optional RTCAnswerOptions options);
+    Promise<void> setLocalDescription(RTCSessionDescriptionInit description);
+    readonly        attribute RTCSessionDescription? localDescription;
+    readonly        attribute RTCSessionDescription? currentLocalDescription;
+    readonly        attribute RTCSessionDescription? pendingLocalDescription;
+    Promise<void> setRemoteDescription(RTCSessionDescriptionInit description);
+    readonly        attribute RTCSessionDescription? remoteDescription;
+    readonly        attribute RTCSessionDescription? currentRemoteDescription;
+    readonly        attribute RTCSessionDescription? pendingRemoteDescription;
+    Promise<void> addIceCandidate((RTCIceCandidateInit or RTCIceCandidate) candidate);
+    readonly        attribute RTCSignalingState signalingState;
+    readonly        attribute RTCIceGatheringState iceGatheringState;
+    readonly        attribute RTCIceConnectionState iceConnectionState;
+    readonly        attribute RTCPeerConnectionState connectionState;
+    readonly        attribute boolean? canTrickleIceCandidates;
+    static sequence<RTCIceServer> getDefaultIceServers();
+    RTCConfiguration getConfiguration();
+    void setConfiguration(RTCConfiguration configuration);
+    void close();
+                    attribute EventHandler onnegotiationneeded;
+                    attribute EventHandler onicecandidate;
+                    attribute EventHandler onicecandidateerror;
+                    attribute EventHandler onsignalingstatechange;
+                    attribute EventHandler oniceconnectionstatechange;
+                    attribute EventHandler onicegatheringstatechange;
+                    attribute EventHandler onconnectionstatechange;
+};
+
+partial interface RTCPeerConnection {
+    Promise<void> createOffer(RTCSessionDescriptionCallback successCallback, RTCPeerConnectionErrorCallback failureCallback, optional RTCOfferOptions options);
+    Promise<void> setLocalDescription(RTCSessionDescriptionInit description, VoidFunction successCallback, RTCPeerConnectionErrorCallback failureCallback);
+    Promise<void> createAnswer(RTCSessionDescriptionCallback successCallback, RTCPeerConnectionErrorCallback failureCallback);
+    Promise<void> setRemoteDescription(RTCSessionDescriptionInit description, VoidFunction successCallback, RTCPeerConnectionErrorCallback failureCallback);
+    Promise<void> addIceCandidate((RTCIceCandidateInit or RTCIceCandidate) candidate, VoidFunction successCallback, RTCPeerConnectionErrorCallback failureCallback);
+};
+
+callback RTCPeerConnectionErrorCallback = void (DOMException error);
+
+callback RTCSessionDescriptionCallback = void (RTCSessionDescriptionInit description);
+
+partial dictionary RTCOfferOptions {
+            boolean offerToReceiveAudio;
+            boolean offerToReceiveVideo;
+          };
+
+enum RTCSdpType {
+    "offer",
+    "pranswer",
+    "answer",
+    "rollback"
+};
+
+[ Constructor(RTCSessionDescriptionInit descriptionInitDict), Exposed=Window]
+interface RTCSessionDescription {
+    readonly        attribute RTCSdpType type;
+    readonly        attribute DOMString sdp;
+    [Default] object toJSON();
+};
+
+dictionary RTCSessionDescriptionInit {
+    required RTCSdpType type;
+             DOMString sdp = "";
+};
+
+[ Constructor(optional RTCIceCandidateInit candidateInitDict), Exposed=Window]
+interface RTCIceCandidate {
+    readonly        attribute DOMString candidate;
+    readonly        attribute DOMString? sdpMid;
+    readonly        attribute unsigned short? sdpMLineIndex;
+    readonly        attribute DOMString? foundation;
+    readonly        attribute RTCIceComponent? component;
+    readonly        attribute unsigned long? priority;
+    readonly        attribute DOMString? ip;
+    readonly        attribute RTCIceProtocol? protocol;
+    readonly        attribute unsigned short? port;
+    readonly        attribute RTCIceCandidateType? type;
+    readonly        attribute RTCIceTcpCandidateType? tcpType;
+    readonly        attribute DOMString? relatedAddress;
+    readonly        attribute unsigned short? relatedPort;
+    readonly        attribute DOMString? usernameFragment;
+    RTCIceCandidateInit toJSON();
+};
+
+dictionary RTCIceCandidateInit {
+             DOMString candidate = "";
+             DOMString? sdpMid = null;
+             unsigned short? sdpMLineIndex = null;
+             DOMString usernameFragment;
+};
+
+enum RTCIceProtocol {
+    "udp",
+    "tcp"
+};
+
+enum RTCIceTcpCandidateType {
+    "active",
+    "passive",
+    "so"
+};
+
+enum RTCIceCandidateType {
+    "host",
+    "srflx",
+    "prflx",
+    "relay"
+};
+
+[ Constructor(DOMString type, optional RTCPeerConnectionIceEventInit eventInitDict), Exposed=Window]
+interface RTCPeerConnectionIceEvent : Event {
+    readonly        attribute RTCIceCandidate? candidate;
+    readonly        attribute DOMString? url;
+};
+
+dictionary RTCPeerConnectionIceEventInit : EventInit {
+             RTCIceCandidate? candidate;
+             DOMString? url;
+};
+
+[ Constructor(DOMString type, RTCPeerConnectionIceErrorEventInit eventInitDict), Exposed=Window]
+interface RTCPeerConnectionIceErrorEvent : Event {
+    readonly        attribute DOMString hostCandidate;
+    readonly        attribute DOMString url;
+    readonly        attribute unsigned short errorCode;
+    readonly        attribute USVString errorText;
+};
+
+dictionary RTCPeerConnectionIceErrorEventInit : EventInit {
+             DOMString hostCandidate;
+             DOMString url;
+             required unsigned short errorCode;
+             USVString statusText;
+};
+
+enum RTCPriorityType {
+    "very-low",
+    "low",
+    "medium",
+    "high"
+};
+
+partial interface RTCPeerConnection {
+    static Promise<RTCCertificate> generateCertificate(AlgorithmIdentifier keygenAlgorithm);
+};
+
+dictionary RTCCertificateExpiration {
+    [EnforceRange]
+    DOMTimeStamp expires;
+};
+
+[Exposed=Window, Serializable] interface RTCCertificate {
+    readonly        attribute DOMTimeStamp expires;
+    static sequence<AlgorithmIdentifier> getSupportedAlgorithms();
+    sequence<RTCDtlsFingerprint> getFingerprints();
+};
+
+partial interface RTCPeerConnection {
+    sequence<RTCRtpSender> getSenders();
+    sequence<RTCRtpReceiver> getReceivers();
+    sequence<RTCRtpTransceiver> getTransceivers();
+    RTCRtpSender addTrack(MediaStreamTrack track, MediaStream... streams);
+    void removeTrack(RTCRtpSender sender);
+    RTCRtpTransceiver addTransceiver((MediaStreamTrack or DOMString) trackOrKind, optional RTCRtpTransceiverInit init);
+                    attribute EventHandler ontrack;
+};
+
+dictionary RTCRtpTransceiverInit {
+             RTCRtpTransceiverDirection direction = "sendrecv";
+             sequence<MediaStream> streams = [];
+             sequence<RTCRtpEncodingParameters> sendEncodings = [];
+};
+
+enum RTCRtpTransceiverDirection {
+    "sendrecv",
+    "sendonly",
+    "recvonly",
+    "inactive"
+};
+
+[Exposed=Window] interface RTCRtpSender {
+    readonly        attribute MediaStreamTrack? track;
+    readonly        attribute RTCDtlsTransport? transport;
+    readonly        attribute RTCDtlsTransport? rtcpTransport;
+    static RTCRtpCapabilities? getCapabilities(DOMString kind);
+    Promise<void> setParameters(RTCRtpSendParameters parameters);
+    RTCRtpSendParameters getParameters();
+    Promise<void> replaceTrack(MediaStreamTrack? withTrack);
+    void setStreams(MediaStream... streams);
+    Promise<RTCStatsReport> getStats();
+};
+
+dictionary RTCRtpParameters {
+             required sequence<RTCRtpHeaderExtensionParameters> headerExtensions;
+             required RTCRtcpParameters rtcp;
+             required sequence<RTCRtpCodecParameters> codecs;
+};
+
+dictionary RTCRtpSendParameters : RTCRtpParameters {
+             required DOMString transactionId;
+             required sequence<RTCRtpEncodingParameters> encodings;
+             RTCDegradationPreference degradationPreference = "balanced";
+};
+
+dictionary RTCRtpReceiveParameters : RTCRtpParameters {
+             required sequence<RTCRtpDecodingParameters> encodings;
+};
+
+dictionary RTCRtpCodingParameters {
+             DOMString rid;
+};
+
+dictionary RTCRtpDecodingParameters : RTCRtpCodingParameters {
+};
+
+dictionary RTCRtpEncodingParameters : RTCRtpCodingParameters {
+             octet codecPayloadType;
+             RTCDtxStatus dtx;
+             boolean active = true;
+             RTCPriorityType priority = "low";
+             unsigned long ptime;
+             unsigned long maxBitrate;
+             double maxFramerate;
+             double scaleResolutionDownBy;
+};
+
+enum RTCDtxStatus {
+         "disabled",
+         "enabled"
+         };
+
+enum RTCDegradationPreference {
+    "maintain-framerate",
+    "maintain-resolution",
+    "balanced"
+};
+
+dictionary RTCRtcpParameters {
+             DOMString cname;
+             boolean reducedSize;
+};
+
+dictionary RTCRtpHeaderExtensionParameters {
+             required DOMString uri;
+             required unsigned short id;
+             boolean encrypted = false;
+};
+
+dictionary RTCRtpCodecParameters {
+             required octet payloadType;
+             required DOMString mimeType;
+             required unsigned long clockRate;
+             unsigned short channels;
+             DOMString sdpFmtpLine;
+};
+
+dictionary RTCRtpCapabilities {
+             required sequence<RTCRtpCodecCapability> codecs;
+             required sequence<RTCRtpHeaderExtensionCapability> headerExtensions;
+};
+
+dictionary RTCRtpCodecCapability {
+             required DOMString mimeType;
+             required unsigned long clockRate;
+             unsigned short channels;
+             DOMString sdpFmtpLine;
+};
+
+dictionary RTCRtpHeaderExtensionCapability {
+             DOMString uri;
+};
+
+[Exposed=Window] interface RTCRtpReceiver {
+    readonly        attribute MediaStreamTrack track;
+    readonly        attribute RTCDtlsTransport? transport;
+    readonly        attribute RTCDtlsTransport? rtcpTransport;
+    static RTCRtpCapabilities? getCapabilities(DOMString kind);
+    RTCRtpReceiveParameters getParameters();
+    sequence<RTCRtpContributingSource> getContributingSources();
+    sequence<RTCRtpSynchronizationSource> getSynchronizationSources();
+    Promise<RTCStatsReport> getStats();
+};
+
+dictionary RTCRtpContributingSource {
+    required DOMHighResTimeStamp timestamp;
+    required unsigned long source;
+             double audioLevel;
+};
+
+dictionary RTCRtpSynchronizationSource : RTCRtpContributingSource {
+    boolean voiceActivityFlag;
+};
+
+[Exposed=Window] interface RTCRtpTransceiver {
+    readonly        attribute DOMString? mid;
+    [SameObject]
+    readonly        attribute RTCRtpSender sender;
+    [SameObject]
+    readonly        attribute RTCRtpReceiver receiver;
+    readonly        attribute boolean stopped;
+                    attribute RTCRtpTransceiverDirection direction;
+    readonly        attribute RTCRtpTransceiverDirection? currentDirection;
+    void stop();
+    void setCodecPreferences(sequence<RTCRtpCodecCapability> codecs);
+};
+
+[Exposed=Window] interface RTCDtlsTransport : EventTarget {
+    readonly        attribute RTCIceTransport transport;
+    readonly        attribute RTCDtlsTransportState state;
+    sequence<ArrayBuffer> getRemoteCertificates();
+                    attribute EventHandler onstatechange;
+                    attribute EventHandler onerror;
+};
+
+enum RTCDtlsTransportState {
+    "new",
+    "connecting",
+    "connected",
+    "closed",
+    "failed"
+};
+
+dictionary RTCDtlsFingerprint {
+             DOMString algorithm;
+             DOMString value;
+};
+
+[Exposed=Window] interface RTCIceTransport : EventTarget {
+    readonly        attribute RTCIceRole role;
+    readonly        attribute RTCIceComponent component;
+    readonly        attribute RTCIceTransportState state;
+    readonly        attribute RTCIceGathererState gatheringState;
+    sequence<RTCIceCandidate> getLocalCandidates();
+    sequence<RTCIceCandidate> getRemoteCandidates();
+    RTCIceCandidatePair? getSelectedCandidatePair();
+    RTCIceParameters? getLocalParameters();
+    RTCIceParameters? getRemoteParameters();
+                    attribute EventHandler onstatechange;
+                    attribute EventHandler ongatheringstatechange;
+                    attribute EventHandler onselectedcandidatepairchange;
+};
+
+dictionary RTCIceParameters {
+             DOMString usernameFragment;
+             DOMString password;
+};
+
+dictionary RTCIceCandidatePair {
+             RTCIceCandidate local;
+             RTCIceCandidate remote;
+};
+
+enum RTCIceGathererState {
+    "new",
+    "gathering",
+    "complete"
+};
+
+enum RTCIceTransportState {
+    "new",
+    "checking",
+    "connected",
+    "completed",
+    "disconnected",
+    "failed",
+    "closed"
+};
+
+enum RTCIceRole {
+    "controlling",
+    "controlled"
+};
+
+enum RTCIceComponent {
+    "rtp",
+    "rtcp"
+};
+
+[ Constructor(DOMString type, RTCTrackEventInit eventInitDict), Exposed=Window]
+interface RTCTrackEvent : Event {
+    readonly        attribute RTCRtpReceiver receiver;
+    readonly        attribute MediaStreamTrack track;
+    [SameObject]
+    readonly        attribute FrozenArray<MediaStream> streams;
+    readonly        attribute RTCRtpTransceiver transceiver;
+};
+
+dictionary RTCTrackEventInit : EventInit {
+    required RTCRtpReceiver receiver;
+    required MediaStreamTrack track;
+             sequence<MediaStream> streams = [];
+    required RTCRtpTransceiver transceiver;
+};
+
+partial interface RTCPeerConnection {
+    readonly        attribute RTCSctpTransport? sctp;
+    RTCDataChannel createDataChannel(USVString label, optional RTCDataChannelInit dataChannelDict);
+                    attribute EventHandler ondatachannel;
+};
+
+[Exposed=Window] interface RTCSctpTransport {
+    readonly        attribute RTCDtlsTransport transport;
+    readonly        attribute RTCSctpTransportState state;
+    readonly        attribute unrestricted double maxMessageSize;
+    readonly        attribute unsigned short? maxChannels;
+                    attribute EventHandler onstatechange;
+};
+
+enum RTCSctpTransportState {
+    "connecting",
+    "connected",
+    "closed"
+};
+
+[Exposed=Window] interface RTCDataChannel : EventTarget {
+    readonly        attribute USVString label;
+    readonly        attribute boolean ordered;
+    readonly        attribute unsigned short? maxPacketLifeTime;
+    readonly        attribute unsigned short? maxRetransmits;
+    readonly        attribute USVString protocol;
+    readonly        attribute boolean negotiated;
+    readonly        attribute unsigned short? id;
+    readonly        attribute RTCPriorityType priority;
+    readonly        attribute RTCDataChannelState readyState;
+    readonly        attribute unsigned long bufferedAmount;
+                    attribute unsigned long bufferedAmountLowThreshold;
+                    attribute EventHandler onopen;
+                    attribute EventHandler onbufferedamountlow;
+                    attribute EventHandler onerror;
+                    attribute EventHandler onclose;
+    void close();
+                    attribute EventHandler onmessage;
+                    attribute DOMString binaryType;
+    void send(USVString data);
+    void send(Blob data);
+    void send(ArrayBuffer data);
+    void send(ArrayBufferView data);
+};
+
+dictionary RTCDataChannelInit {
+             boolean ordered = true;
+             unsigned short maxPacketLifeTime;
+             unsigned short maxRetransmits;
+             USVString protocol = "";
+             boolean negotiated = false;
+             [EnforceRange]
+             unsigned short id;
+             RTCPriorityType priority = "low";
+};
+
+enum RTCDataChannelState {
+    "connecting",
+    "open",
+    "closing",
+    "closed"
+};
+
+[ Constructor(DOMString type, RTCDataChannelEventInit eventInitDict), Exposed=Window]
+interface RTCDataChannelEvent : Event {
+    readonly        attribute RTCDataChannel channel;
+};
+
+dictionary RTCDataChannelEventInit : EventInit {
+             required RTCDataChannel channel;
+};
+
+partial interface RTCRtpSender {
+    readonly        attribute RTCDTMFSender? dtmf;
+};
+
+[Exposed=Window] interface RTCDTMFSender : EventTarget {
+    void insertDTMF(DOMString tones, optional unsigned long duration = 100, optional unsigned long interToneGap = 70);
+                    attribute EventHandler ontonechange;
+    readonly        attribute boolean canInsertDTMF;
+    readonly        attribute DOMString toneBuffer;
+};
+
+[ Constructor(DOMString type, RTCDTMFToneChangeEventInit eventInitDict), Exposed=Window]
+interface RTCDTMFToneChangeEvent : Event {
+    readonly        attribute DOMString tone;
+};
+
+dictionary RTCDTMFToneChangeEventInit : EventInit {
+             required DOMString tone;
+};
+
+partial interface RTCPeerConnection {
+    Promise<RTCStatsReport> getStats(optional MediaStreamTrack? selector = null);
+    attribute EventHandler onstatsended;
+          };
+
+[Exposed=Window] interface RTCStatsReport {
+
+    readonly maplike<DOMString, object>;
+};
+
+dictionary RTCStats {
+             required DOMHighResTimeStamp timestamp;
+             required RTCStatsType type;
+             required DOMString id;
+};
+
+[ Constructor(DOMString type, RTCStatsEventInit eventInitDict), Exposed=Window]
+          interface RTCStatsEvent : Event {
+            readonly attribute RTCStatsReport report;
+          };
+
+dictionary RTCStatsEventInit : EventInit {
+            required RTCStatsReport report;
+          };
+
+enum RTCErrorDetailType {
+              "data-channel-failure",
+              "dtls-failure",
+              "fingerprint-failure",
+              "idp-bad-script-failure",
+              "idp-execution-failure",
+              "idp-load-failure",
+              "idp-need-login",
+              "idp-timeout",
+              "idp-tls-failure",
+              "idp-token-expired",
+              "idp-token-invalid",
+              "sctp-failure",
+              "sdp-syntax-error",
+              "hardware-encoder-not-available",
+              "hardware-encoder-error"
+          };
+
+[Exposed=Window,
+ Constructor(DOMString type, RTCErrorEventInit eventInitDict)]
+interface RTCErrorEvent : Event {
+    readonly        attribute RTCError? error;
+};
+
+dictionary RTCErrorEventInit : EventInit {
+             RTCError? error = null;
+};
diff --git a/third_party/WebKit/LayoutTests/external/wpt/resources/chromium/webxr-test.js b/third_party/WebKit/LayoutTests/external/wpt/resources/chromium/webxr-test.js
index 7ed9332b..d760e7c7 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/resources/chromium/webxr-test.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/resources/chromium/webxr-test.js
@@ -61,7 +61,7 @@
   }
 }
 
-// Implements both VRDisplayHost and VRMagicWindowProvider. Maintains a mock for
+// Implements both XRDeviceImpl and VRMagicWindowProvider. Maintains a mock for
 // XRPresentationProvider.
 class MockDevice {
   constructor(fakeDeviceInit, service) {
@@ -89,14 +89,14 @@
   // Functions for setup.
   // This function calls to the backend to add this device to the list.
   notifyClientOfDisplay() {
-    let displayPtr = new device.mojom.VRDisplayHostPtr();
-    let displayRequest = mojo.makeRequest(displayPtr);
-    let displayBinding =
-        new mojo.Binding(device.mojom.VRDisplayHost, this, displayRequest);
+    let devicePtr = new device.mojom.XRDevicePtr();
+    let deviceRequest = mojo.makeRequest(devicePtr);
+    let deviceBinding =
+        new mojo.Binding(device.mojom.XRDevice, this, deviceRequest);
 
     let clientRequest = mojo.makeRequest(this.displayClient_);
     this.service_.client_.onDisplayConnected(
-        displayPtr, clientRequest, this.displayInfo_);
+        devicePtr, clientRequest, this.displayInfo_);
   }
 
   // Test methods.
@@ -279,7 +279,7 @@
     // do not have any use for this data at present.
   }
 
-  // VRDisplayHost implementation.
+  // XRDeviceImpl implementation.
 
   requestSession(sessionOptions, was_activation) {
     return this.supportsSession(sessionOptions).then((result) => {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/idlharness.https.window-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/webrtc/idlharness.https.window-expected.txt
new file mode 100644
index 0000000..5c5f591
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/idlharness.https.window-expected.txt
@@ -0,0 +1,503 @@
+This is a testharness.js-based test.
+PASS idl_test setup
+PASS Test driver for asyncInitCertificate
+FAIL Test driver for asyncInitTransports assert_unreached: Failed to run asyncInitTransports: ReferenceError: RTCSctpTransport is not defined Reached unreachable code
+PASS Test driver for asyncInitMediaStreamTrack
+PASS Partial interface RTCPeerConnection: original interface defined
+PASS Partial dictionary RTCOfferOptions: original dictionary defined
+PASS Partial interface RTCPeerConnection[2]: original interface defined
+PASS Partial interface RTCPeerConnection[3]: original interface defined
+PASS Partial interface RTCPeerConnection[4]: original interface defined
+PASS Partial interface RTCRtpSender: original interface defined
+PASS Partial interface RTCPeerConnection[5]: original interface defined
+PASS RTCPeerConnection interface: existence and properties of interface object
+PASS RTCPeerConnection interface object length
+PASS RTCPeerConnection interface object name
+PASS RTCPeerConnection interface: existence and properties of interface prototype object
+PASS RTCPeerConnection interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCPeerConnection interface: existence and properties of interface prototype object's @@unscopables property
+PASS RTCPeerConnection interface: operation createOffer(RTCOfferOptions)
+PASS RTCPeerConnection interface: operation createAnswer(RTCAnswerOptions)
+PASS RTCPeerConnection interface: operation setLocalDescription(RTCSessionDescriptionInit)
+PASS RTCPeerConnection interface: attribute localDescription
+FAIL RTCPeerConnection interface: attribute currentLocalDescription assert_true: The prototype object must have a property "currentLocalDescription" expected true got false
+FAIL RTCPeerConnection interface: attribute pendingLocalDescription assert_true: The prototype object must have a property "pendingLocalDescription" expected true got false
+PASS RTCPeerConnection interface: operation setRemoteDescription(RTCSessionDescriptionInit)
+PASS RTCPeerConnection interface: attribute remoteDescription
+FAIL RTCPeerConnection interface: attribute currentRemoteDescription assert_true: The prototype object must have a property "currentRemoteDescription" expected true got false
+FAIL RTCPeerConnection interface: attribute pendingRemoteDescription assert_true: The prototype object must have a property "pendingRemoteDescription" expected true got false
+PASS RTCPeerConnection interface: operation addIceCandidate([object Object],[object Object])
+PASS RTCPeerConnection interface: attribute signalingState
+PASS RTCPeerConnection interface: attribute iceGatheringState
+PASS RTCPeerConnection interface: attribute iceConnectionState
+FAIL RTCPeerConnection interface: attribute connectionState assert_true: The prototype object must have a property "connectionState" expected true got false
+FAIL RTCPeerConnection interface: attribute canTrickleIceCandidates assert_true: The prototype object must have a property "canTrickleIceCandidates" expected true got false
+FAIL RTCPeerConnection interface: operation getDefaultIceServers() assert_own_property: interface object missing static operation expected property "getDefaultIceServers" missing
+FAIL RTCPeerConnection interface: operation getConfiguration() assert_own_property: interface prototype object missing non-static operation expected property "getConfiguration" missing
+PASS RTCPeerConnection interface: operation setConfiguration(RTCConfiguration)
+PASS RTCPeerConnection interface: operation close()
+PASS RTCPeerConnection interface: attribute onnegotiationneeded
+PASS RTCPeerConnection interface: attribute onicecandidate
+FAIL RTCPeerConnection interface: attribute onicecandidateerror assert_true: The prototype object must have a property "onicecandidateerror" expected true got false
+PASS RTCPeerConnection interface: attribute onsignalingstatechange
+PASS RTCPeerConnection interface: attribute oniceconnectionstatechange
+PASS RTCPeerConnection interface: attribute onicegatheringstatechange
+FAIL RTCPeerConnection interface: attribute onconnectionstatechange assert_true: The prototype object must have a property "onconnectionstatechange" expected true got false
+PASS RTCPeerConnection interface: operation createOffer(RTCSessionDescriptionCallback, RTCPeerConnectionErrorCallback, RTCOfferOptions)
+PASS RTCPeerConnection interface: operation setLocalDescription(RTCSessionDescriptionInit, VoidFunction, RTCPeerConnectionErrorCallback)
+PASS RTCPeerConnection interface: operation createAnswer(RTCSessionDescriptionCallback, RTCPeerConnectionErrorCallback)
+PASS RTCPeerConnection interface: operation setRemoteDescription(RTCSessionDescriptionInit, VoidFunction, RTCPeerConnectionErrorCallback)
+PASS RTCPeerConnection interface: operation addIceCandidate([object Object],[object Object], VoidFunction, RTCPeerConnectionErrorCallback)
+PASS RTCPeerConnection interface: operation generateCertificate(AlgorithmIdentifier)
+PASS RTCPeerConnection interface: operation getSenders()
+PASS RTCPeerConnection interface: operation getReceivers()
+PASS RTCPeerConnection interface: operation getTransceivers()
+PASS RTCPeerConnection interface: operation addTrack(MediaStreamTrack, MediaStream)
+PASS RTCPeerConnection interface: operation removeTrack(RTCRtpSender)
+PASS RTCPeerConnection interface: operation addTransceiver([object Object],[object Object], RTCRtpTransceiverInit)
+PASS RTCPeerConnection interface: attribute ontrack
+FAIL RTCPeerConnection interface: attribute sctp assert_true: The prototype object must have a property "sctp" expected true got false
+PASS RTCPeerConnection interface: operation createDataChannel(USVString, RTCDataChannelInit)
+PASS RTCPeerConnection interface: attribute ondatachannel
+PASS RTCPeerConnection interface: operation getStats(MediaStreamTrack)
+FAIL RTCPeerConnection interface: attribute onstatsended assert_true: The prototype object must have a property "onstatsended" expected true got false
+PASS RTCPeerConnection must be primary interface of new RTCPeerConnection()
+PASS Stringification of new RTCPeerConnection()
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "createOffer(RTCOfferOptions)" with the proper type
+PASS RTCPeerConnection interface: calling createOffer(RTCOfferOptions) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "createAnswer(RTCAnswerOptions)" with the proper type
+PASS RTCPeerConnection interface: calling createAnswer(RTCAnswerOptions) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "setLocalDescription(RTCSessionDescriptionInit)" with the proper type
+PASS RTCPeerConnection interface: calling setLocalDescription(RTCSessionDescriptionInit) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "localDescription" with the proper type
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "currentLocalDescription" with the proper type assert_inherits: property "currentLocalDescription" not found in prototype chain
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "pendingLocalDescription" with the proper type assert_inherits: property "pendingLocalDescription" not found in prototype chain
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "setRemoteDescription(RTCSessionDescriptionInit)" with the proper type
+PASS RTCPeerConnection interface: calling setRemoteDescription(RTCSessionDescriptionInit) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "remoteDescription" with the proper type
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "currentRemoteDescription" with the proper type assert_inherits: property "currentRemoteDescription" not found in prototype chain
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "pendingRemoteDescription" with the proper type assert_inherits: property "pendingRemoteDescription" not found in prototype chain
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "addIceCandidate([object Object],[object Object])" with the proper type
+PASS RTCPeerConnection interface: calling addIceCandidate([object Object],[object Object]) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "signalingState" with the proper type
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "iceGatheringState" with the proper type
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "iceConnectionState" with the proper type
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "connectionState" with the proper type assert_inherits: property "connectionState" not found in prototype chain
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "canTrickleIceCandidates" with the proper type assert_inherits: property "canTrickleIceCandidates" not found in prototype chain
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "getDefaultIceServers()" with the proper type
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "getConfiguration()" with the proper type assert_inherits: property "getConfiguration" not found in prototype chain
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "setConfiguration(RTCConfiguration)" with the proper type
+PASS RTCPeerConnection interface: calling setConfiguration(RTCConfiguration) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "close()" with the proper type
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "onnegotiationneeded" with the proper type Unrecognized type EventHandler
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "onicecandidate" with the proper type Unrecognized type EventHandler
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "onicecandidateerror" with the proper type assert_inherits: property "onicecandidateerror" not found in prototype chain
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "onsignalingstatechange" with the proper type Unrecognized type EventHandler
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "oniceconnectionstatechange" with the proper type Unrecognized type EventHandler
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "onicegatheringstatechange" with the proper type Unrecognized type EventHandler
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "onconnectionstatechange" with the proper type assert_inherits: property "onconnectionstatechange" not found in prototype chain
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "createOffer(RTCSessionDescriptionCallback, RTCPeerConnectionErrorCallback, RTCOfferOptions)" with the proper type
+PASS RTCPeerConnection interface: calling createOffer(RTCSessionDescriptionCallback, RTCPeerConnectionErrorCallback, RTCOfferOptions) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "setLocalDescription(RTCSessionDescriptionInit, VoidFunction, RTCPeerConnectionErrorCallback)" with the proper type
+PASS RTCPeerConnection interface: calling setLocalDescription(RTCSessionDescriptionInit, VoidFunction, RTCPeerConnectionErrorCallback) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "createAnswer(RTCSessionDescriptionCallback, RTCPeerConnectionErrorCallback)" with the proper type
+PASS RTCPeerConnection interface: calling createAnswer(RTCSessionDescriptionCallback, RTCPeerConnectionErrorCallback) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "setRemoteDescription(RTCSessionDescriptionInit, VoidFunction, RTCPeerConnectionErrorCallback)" with the proper type
+PASS RTCPeerConnection interface: calling setRemoteDescription(RTCSessionDescriptionInit, VoidFunction, RTCPeerConnectionErrorCallback) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "addIceCandidate([object Object],[object Object], VoidFunction, RTCPeerConnectionErrorCallback)" with the proper type
+PASS RTCPeerConnection interface: calling addIceCandidate([object Object],[object Object], VoidFunction, RTCPeerConnectionErrorCallback) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "generateCertificate(AlgorithmIdentifier)" with the proper type
+PASS RTCPeerConnection interface: calling generateCertificate(AlgorithmIdentifier) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "getSenders()" with the proper type
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "getReceivers()" with the proper type
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "getTransceivers()" with the proper type
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "addTrack(MediaStreamTrack, MediaStream)" with the proper type
+PASS RTCPeerConnection interface: calling addTrack(MediaStreamTrack, MediaStream) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "removeTrack(RTCRtpSender)" with the proper type
+PASS RTCPeerConnection interface: calling removeTrack(RTCRtpSender) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "addTransceiver([object Object],[object Object], RTCRtpTransceiverInit)" with the proper type
+PASS RTCPeerConnection interface: calling addTransceiver([object Object],[object Object], RTCRtpTransceiverInit) on new RTCPeerConnection() with too few arguments must throw TypeError
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "ontrack" with the proper type Unrecognized type EventHandler
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "sctp" with the proper type assert_inherits: property "sctp" not found in prototype chain
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "createDataChannel(USVString, RTCDataChannelInit)" with the proper type
+PASS RTCPeerConnection interface: calling createDataChannel(USVString, RTCDataChannelInit) on new RTCPeerConnection() with too few arguments must throw TypeError
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "ondatachannel" with the proper type Unrecognized type EventHandler
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "getStats(MediaStreamTrack)" with the proper type
+PASS RTCPeerConnection interface: calling getStats(MediaStreamTrack) on new RTCPeerConnection() with too few arguments must throw TypeError
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "onstatsended" with the proper type assert_inherits: property "onstatsended" not found in prototype chain
+PASS RTCSessionDescription interface: existence and properties of interface object
+FAIL RTCSessionDescription interface object length assert_equals: wrong value for RTCSessionDescription.length expected 1 but got 0
+PASS RTCSessionDescription interface object name
+PASS RTCSessionDescription interface: existence and properties of interface prototype object
+PASS RTCSessionDescription interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCSessionDescription interface: existence and properties of interface prototype object's @@unscopables property
+FAIL RTCSessionDescription interface: attribute type assert_equals: setter must be undefined for readonly attributes expected (undefined) undefined but got (function) function "function () { [native code] }"
+FAIL RTCSessionDescription interface: attribute sdp assert_equals: setter must be undefined for readonly attributes expected (undefined) undefined but got (function) function "function () { [native code] }"
+PASS RTCSessionDescription interface: operation toJSON()
+PASS RTCSessionDescription must be primary interface of new RTCSessionDescription({ type: 'offer' })
+PASS Stringification of new RTCSessionDescription({ type: 'offer' })
+PASS RTCSessionDescription interface: new RTCSessionDescription({ type: 'offer' }) must inherit property "type" with the proper type
+FAIL RTCSessionDescription interface: new RTCSessionDescription({ type: 'offer' }) must inherit property "sdp" with the proper type assert_equals: expected "string" but got "object"
+PASS RTCSessionDescription interface: new RTCSessionDescription({ type: 'offer' }) must inherit property "toJSON()" with the proper type
+FAIL Test default toJSON operation of RTCSessionDescription assert_equals: expected "string" but got "object"
+PASS RTCIceCandidate interface: existence and properties of interface object
+FAIL RTCIceCandidate interface object length assert_equals: wrong value for RTCIceCandidate.length expected 0 but got 1
+PASS RTCIceCandidate interface object name
+PASS RTCIceCandidate interface: existence and properties of interface prototype object
+PASS RTCIceCandidate interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCIceCandidate interface: existence and properties of interface prototype object's @@unscopables property
+FAIL RTCIceCandidate interface: attribute candidate assert_equals: setter must be undefined for readonly attributes expected (undefined) undefined but got (function) function "function () { [native code] }"
+FAIL RTCIceCandidate interface: attribute sdpMid assert_equals: setter must be undefined for readonly attributes expected (undefined) undefined but got (function) function "function () { [native code] }"
+FAIL RTCIceCandidate interface: attribute sdpMLineIndex assert_equals: setter must be undefined for readonly attributes expected (undefined) undefined but got (function) function "function () { [native code] }"
+FAIL RTCIceCandidate interface: attribute foundation assert_true: The prototype object must have a property "foundation" expected true got false
+FAIL RTCIceCandidate interface: attribute component assert_true: The prototype object must have a property "component" expected true got false
+FAIL RTCIceCandidate interface: attribute priority assert_true: The prototype object must have a property "priority" expected true got false
+FAIL RTCIceCandidate interface: attribute ip assert_true: The prototype object must have a property "ip" expected true got false
+FAIL RTCIceCandidate interface: attribute protocol assert_true: The prototype object must have a property "protocol" expected true got false
+FAIL RTCIceCandidate interface: attribute port assert_true: The prototype object must have a property "port" expected true got false
+FAIL RTCIceCandidate interface: attribute type assert_true: The prototype object must have a property "type" expected true got false
+FAIL RTCIceCandidate interface: attribute tcpType assert_true: The prototype object must have a property "tcpType" expected true got false
+FAIL RTCIceCandidate interface: attribute relatedAddress assert_true: The prototype object must have a property "relatedAddress" expected true got false
+FAIL RTCIceCandidate interface: attribute relatedPort assert_true: The prototype object must have a property "relatedPort" expected true got false
+FAIL RTCIceCandidate interface: attribute usernameFragment assert_true: The prototype object must have a property "usernameFragment" expected true got false
+PASS RTCIceCandidate interface: operation toJSON()
+FAIL RTCIceCandidate must be primary interface of new RTCIceCandidate({ sdpMid: 1 }) assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL Stringification of new RTCIceCandidate({ sdpMid: 1 }) assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "candidate" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "sdpMid" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "sdpMLineIndex" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "foundation" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "component" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "priority" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "ip" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "protocol" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "port" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "type" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "tcpType" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "relatedAddress" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "relatedPort" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "usernameFragment" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "toJSON()" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL Test toJSON operation of toJSON object Cannot read property 'toJSON' of undefined
+PASS RTCPeerConnectionIceEvent interface: existence and properties of interface object
+PASS RTCPeerConnectionIceEvent interface object length
+PASS RTCPeerConnectionIceEvent interface object name
+PASS RTCPeerConnectionIceEvent interface: existence and properties of interface prototype object
+PASS RTCPeerConnectionIceEvent interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCPeerConnectionIceEvent interface: existence and properties of interface prototype object's @@unscopables property
+PASS RTCPeerConnectionIceEvent interface: attribute candidate
+FAIL RTCPeerConnectionIceEvent interface: attribute url assert_true: The prototype object must have a property "url" expected true got false
+PASS RTCPeerConnectionIceEvent must be primary interface of new RTCPeerConnectionIceEvent('ice')
+PASS Stringification of new RTCPeerConnectionIceEvent('ice')
+PASS RTCPeerConnectionIceEvent interface: new RTCPeerConnectionIceEvent('ice') must inherit property "candidate" with the proper type
+FAIL RTCPeerConnectionIceEvent interface: new RTCPeerConnectionIceEvent('ice') must inherit property "url" with the proper type assert_inherits: property "url" not found in prototype chain
+FAIL RTCPeerConnectionIceErrorEvent interface: existence and properties of interface object assert_own_property: self does not have own property "RTCPeerConnectionIceErrorEvent" expected property "RTCPeerConnectionIceErrorEvent" missing
+FAIL RTCPeerConnectionIceErrorEvent interface object length assert_own_property: self does not have own property "RTCPeerConnectionIceErrorEvent" expected property "RTCPeerConnectionIceErrorEvent" missing
+FAIL RTCPeerConnectionIceErrorEvent interface object name assert_own_property: self does not have own property "RTCPeerConnectionIceErrorEvent" expected property "RTCPeerConnectionIceErrorEvent" missing
+FAIL RTCPeerConnectionIceErrorEvent interface: existence and properties of interface prototype object assert_own_property: self does not have own property "RTCPeerConnectionIceErrorEvent" expected property "RTCPeerConnectionIceErrorEvent" missing
+FAIL RTCPeerConnectionIceErrorEvent interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "RTCPeerConnectionIceErrorEvent" expected property "RTCPeerConnectionIceErrorEvent" missing
+FAIL RTCPeerConnectionIceErrorEvent interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "RTCPeerConnectionIceErrorEvent" expected property "RTCPeerConnectionIceErrorEvent" missing
+FAIL RTCPeerConnectionIceErrorEvent interface: attribute hostCandidate assert_own_property: self does not have own property "RTCPeerConnectionIceErrorEvent" expected property "RTCPeerConnectionIceErrorEvent" missing
+FAIL RTCPeerConnectionIceErrorEvent interface: attribute url assert_own_property: self does not have own property "RTCPeerConnectionIceErrorEvent" expected property "RTCPeerConnectionIceErrorEvent" missing
+FAIL RTCPeerConnectionIceErrorEvent interface: attribute errorCode assert_own_property: self does not have own property "RTCPeerConnectionIceErrorEvent" expected property "RTCPeerConnectionIceErrorEvent" missing
+FAIL RTCPeerConnectionIceErrorEvent interface: attribute errorText assert_own_property: self does not have own property "RTCPeerConnectionIceErrorEvent" expected property "RTCPeerConnectionIceErrorEvent" missing
+FAIL RTCPeerConnectionIceErrorEvent must be primary interface of new RTCPeerConnectionIceErrorEvent('ice-error', { errorCode: 701 }); assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: RTCPeerConnectionIceErrorEvent is not defined"
+FAIL Stringification of new RTCPeerConnectionIceErrorEvent('ice-error', { errorCode: 701 }); assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: RTCPeerConnectionIceErrorEvent is not defined"
+FAIL RTCPeerConnectionIceErrorEvent interface: new RTCPeerConnectionIceErrorEvent('ice-error', { errorCode: 701 }); must inherit property "hostCandidate" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: RTCPeerConnectionIceErrorEvent is not defined"
+FAIL RTCPeerConnectionIceErrorEvent interface: new RTCPeerConnectionIceErrorEvent('ice-error', { errorCode: 701 }); must inherit property "url" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: RTCPeerConnectionIceErrorEvent is not defined"
+FAIL RTCPeerConnectionIceErrorEvent interface: new RTCPeerConnectionIceErrorEvent('ice-error', { errorCode: 701 }); must inherit property "errorCode" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: RTCPeerConnectionIceErrorEvent is not defined"
+FAIL RTCPeerConnectionIceErrorEvent interface: new RTCPeerConnectionIceErrorEvent('ice-error', { errorCode: 701 }); must inherit property "errorText" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: RTCPeerConnectionIceErrorEvent is not defined"
+PASS RTCCertificate interface: existence and properties of interface object
+PASS RTCCertificate interface object length
+PASS RTCCertificate interface object name
+PASS RTCCertificate interface: existence and properties of interface prototype object
+PASS RTCCertificate interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCCertificate interface: existence and properties of interface prototype object's @@unscopables property
+PASS RTCCertificate interface: attribute expires
+FAIL RTCCertificate interface: operation getSupportedAlgorithms() assert_own_property: interface object missing static operation expected property "getSupportedAlgorithms" missing
+PASS RTCCertificate interface: operation getFingerprints()
+PASS RTCCertificate must be primary interface of idlTestObjects.certificate
+PASS Stringification of idlTestObjects.certificate
+PASS RTCCertificate interface: idlTestObjects.certificate must inherit property "expires" with the proper type
+PASS RTCCertificate interface: idlTestObjects.certificate must inherit property "getSupportedAlgorithms()" with the proper type
+PASS RTCCertificate interface: idlTestObjects.certificate must inherit property "getFingerprints()" with the proper type
+PASS RTCRtpSender interface: existence and properties of interface object
+PASS RTCRtpSender interface object length
+PASS RTCRtpSender interface object name
+PASS RTCRtpSender interface: existence and properties of interface prototype object
+PASS RTCRtpSender interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCRtpSender interface: existence and properties of interface prototype object's @@unscopables property
+PASS RTCRtpSender interface: attribute track
+FAIL RTCRtpSender interface: attribute transport assert_true: The prototype object must have a property "transport" expected true got false
+FAIL RTCRtpSender interface: attribute rtcpTransport assert_true: The prototype object must have a property "rtcpTransport" expected true got false
+PASS RTCRtpSender interface: operation getCapabilities(DOMString)
+FAIL RTCRtpSender interface: operation setParameters(RTCRtpSendParameters) assert_equals: property has wrong .length expected 1 but got 0
+PASS RTCRtpSender interface: operation getParameters()
+PASS RTCRtpSender interface: operation replaceTrack(MediaStreamTrack)
+FAIL RTCRtpSender interface: operation setStreams(MediaStream) assert_own_property: interface prototype object missing non-static operation expected property "setStreams" missing
+PASS RTCRtpSender interface: operation getStats()
+PASS RTCRtpSender interface: attribute dtmf
+FAIL RTCRtpSender must be primary interface of new RTCPeerConnection().addTransceiver('audio').sender assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL Stringification of new RTCPeerConnection().addTransceiver('audio').sender assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpSender interface: new RTCPeerConnection().addTransceiver('audio').sender must inherit property "track" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpSender interface: new RTCPeerConnection().addTransceiver('audio').sender must inherit property "transport" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpSender interface: new RTCPeerConnection().addTransceiver('audio').sender must inherit property "rtcpTransport" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpSender interface: new RTCPeerConnection().addTransceiver('audio').sender must inherit property "getCapabilities(DOMString)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpSender interface: calling getCapabilities(DOMString) on new RTCPeerConnection().addTransceiver('audio').sender with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpSender interface: new RTCPeerConnection().addTransceiver('audio').sender must inherit property "setParameters(RTCRtpSendParameters)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpSender interface: calling setParameters(RTCRtpSendParameters) on new RTCPeerConnection().addTransceiver('audio').sender with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpSender interface: new RTCPeerConnection().addTransceiver('audio').sender must inherit property "getParameters()" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpSender interface: new RTCPeerConnection().addTransceiver('audio').sender must inherit property "replaceTrack(MediaStreamTrack)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpSender interface: calling replaceTrack(MediaStreamTrack) on new RTCPeerConnection().addTransceiver('audio').sender with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpSender interface: new RTCPeerConnection().addTransceiver('audio').sender must inherit property "setStreams(MediaStream)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpSender interface: calling setStreams(MediaStream) on new RTCPeerConnection().addTransceiver('audio').sender with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpSender interface: new RTCPeerConnection().addTransceiver('audio').sender must inherit property "getStats()" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpSender interface: new RTCPeerConnection().addTransceiver('audio').sender must inherit property "dtmf" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+PASS RTCRtpReceiver interface: existence and properties of interface object
+PASS RTCRtpReceiver interface object length
+PASS RTCRtpReceiver interface object name
+PASS RTCRtpReceiver interface: existence and properties of interface prototype object
+PASS RTCRtpReceiver interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCRtpReceiver interface: existence and properties of interface prototype object's @@unscopables property
+PASS RTCRtpReceiver interface: attribute track
+FAIL RTCRtpReceiver interface: attribute transport assert_true: The prototype object must have a property "transport" expected true got false
+FAIL RTCRtpReceiver interface: attribute rtcpTransport assert_true: The prototype object must have a property "rtcpTransport" expected true got false
+PASS RTCRtpReceiver interface: operation getCapabilities(DOMString)
+FAIL RTCRtpReceiver interface: operation getParameters() assert_own_property: interface prototype object missing non-static operation expected property "getParameters" missing
+PASS RTCRtpReceiver interface: operation getContributingSources()
+FAIL RTCRtpReceiver interface: operation getSynchronizationSources() assert_own_property: interface prototype object missing non-static operation expected property "getSynchronizationSources" missing
+PASS RTCRtpReceiver interface: operation getStats()
+FAIL RTCRtpReceiver must be primary interface of new RTCPeerConnection().addTransceiver('audio').receiver assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL Stringification of new RTCPeerConnection().addTransceiver('audio').receiver assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpReceiver interface: new RTCPeerConnection().addTransceiver('audio').receiver must inherit property "track" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpReceiver interface: new RTCPeerConnection().addTransceiver('audio').receiver must inherit property "transport" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpReceiver interface: new RTCPeerConnection().addTransceiver('audio').receiver must inherit property "rtcpTransport" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpReceiver interface: new RTCPeerConnection().addTransceiver('audio').receiver must inherit property "getCapabilities(DOMString)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpReceiver interface: calling getCapabilities(DOMString) on new RTCPeerConnection().addTransceiver('audio').receiver with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpReceiver interface: new RTCPeerConnection().addTransceiver('audio').receiver must inherit property "getParameters()" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpReceiver interface: new RTCPeerConnection().addTransceiver('audio').receiver must inherit property "getContributingSources()" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpReceiver interface: new RTCPeerConnection().addTransceiver('audio').receiver must inherit property "getSynchronizationSources()" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpReceiver interface: new RTCPeerConnection().addTransceiver('audio').receiver must inherit property "getStats()" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+PASS RTCRtpTransceiver interface: existence and properties of interface object
+PASS RTCRtpTransceiver interface object length
+PASS RTCRtpTransceiver interface object name
+PASS RTCRtpTransceiver interface: existence and properties of interface prototype object
+PASS RTCRtpTransceiver interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCRtpTransceiver interface: existence and properties of interface prototype object's @@unscopables property
+PASS RTCRtpTransceiver interface: attribute mid
+PASS RTCRtpTransceiver interface: attribute sender
+PASS RTCRtpTransceiver interface: attribute receiver
+PASS RTCRtpTransceiver interface: attribute stopped
+PASS RTCRtpTransceiver interface: attribute direction
+PASS RTCRtpTransceiver interface: attribute currentDirection
+FAIL RTCRtpTransceiver interface: operation stop() assert_own_property: interface prototype object missing non-static operation expected property "stop" missing
+FAIL RTCRtpTransceiver interface: operation setCodecPreferences([object Object]) assert_own_property: interface prototype object missing non-static operation expected property "setCodecPreferences" missing
+FAIL RTCRtpTransceiver must be primary interface of new RTCPeerConnection().addTransceiver('audio') assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL Stringification of new RTCPeerConnection().addTransceiver('audio') assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpTransceiver interface: new RTCPeerConnection().addTransceiver('audio') must inherit property "mid" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpTransceiver interface: new RTCPeerConnection().addTransceiver('audio') must inherit property "sender" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpTransceiver interface: new RTCPeerConnection().addTransceiver('audio') must inherit property "receiver" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpTransceiver interface: new RTCPeerConnection().addTransceiver('audio') must inherit property "stopped" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpTransceiver interface: new RTCPeerConnection().addTransceiver('audio') must inherit property "direction" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpTransceiver interface: new RTCPeerConnection().addTransceiver('audio') must inherit property "currentDirection" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpTransceiver interface: new RTCPeerConnection().addTransceiver('audio') must inherit property "stop()" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpTransceiver interface: new RTCPeerConnection().addTransceiver('audio') must inherit property "setCodecPreferences([object Object])" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCRtpTransceiver interface: calling setCodecPreferences([object Object]) on new RTCPeerConnection().addTransceiver('audio') with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCDtlsTransport interface: existence and properties of interface object assert_own_property: self does not have own property "RTCDtlsTransport" expected property "RTCDtlsTransport" missing
+FAIL RTCDtlsTransport interface object length assert_own_property: self does not have own property "RTCDtlsTransport" expected property "RTCDtlsTransport" missing
+FAIL RTCDtlsTransport interface object name assert_own_property: self does not have own property "RTCDtlsTransport" expected property "RTCDtlsTransport" missing
+FAIL RTCDtlsTransport interface: existence and properties of interface prototype object assert_own_property: self does not have own property "RTCDtlsTransport" expected property "RTCDtlsTransport" missing
+FAIL RTCDtlsTransport interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "RTCDtlsTransport" expected property "RTCDtlsTransport" missing
+FAIL RTCDtlsTransport interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "RTCDtlsTransport" expected property "RTCDtlsTransport" missing
+FAIL RTCDtlsTransport interface: attribute transport assert_own_property: self does not have own property "RTCDtlsTransport" expected property "RTCDtlsTransport" missing
+FAIL RTCDtlsTransport interface: attribute state assert_own_property: self does not have own property "RTCDtlsTransport" expected property "RTCDtlsTransport" missing
+FAIL RTCDtlsTransport interface: operation getRemoteCertificates() assert_own_property: self does not have own property "RTCDtlsTransport" expected property "RTCDtlsTransport" missing
+FAIL RTCDtlsTransport interface: attribute onstatechange assert_own_property: self does not have own property "RTCDtlsTransport" expected property "RTCDtlsTransport" missing
+FAIL RTCDtlsTransport interface: attribute onerror assert_own_property: self does not have own property "RTCDtlsTransport" expected property "RTCDtlsTransport" missing
+FAIL RTCDtlsTransport must be primary interface of idlTestObjects.dtlsTransport assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL Stringification of idlTestObjects.dtlsTransport assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCDtlsTransport interface: idlTestObjects.dtlsTransport must inherit property "transport" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCDtlsTransport interface: idlTestObjects.dtlsTransport must inherit property "state" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCDtlsTransport interface: idlTestObjects.dtlsTransport must inherit property "getRemoteCertificates()" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCDtlsTransport interface: idlTestObjects.dtlsTransport must inherit property "onstatechange" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCDtlsTransport interface: idlTestObjects.dtlsTransport must inherit property "onerror" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: existence and properties of interface object assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface object length assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface object name assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: existence and properties of interface prototype object assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: attribute role assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: attribute component assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: attribute state assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: attribute gatheringState assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: operation getLocalCandidates() assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: operation getRemoteCandidates() assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: operation getSelectedCandidatePair() assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: operation getLocalParameters() assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: operation getRemoteParameters() assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: attribute onstatechange assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: attribute ongatheringstatechange assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: attribute onselectedcandidatepairchange assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport must be primary interface of idlTestObjects.iceTransport assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL Stringification of idlTestObjects.iceTransport assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "role" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "component" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "state" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "gatheringState" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "getLocalCandidates()" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "getRemoteCandidates()" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "getSelectedCandidatePair()" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "getLocalParameters()" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "getRemoteParameters()" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "onstatechange" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "ongatheringstatechange" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "onselectedcandidatepairchange" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+PASS RTCTrackEvent interface: existence and properties of interface object
+PASS RTCTrackEvent interface object length
+PASS RTCTrackEvent interface object name
+PASS RTCTrackEvent interface: existence and properties of interface prototype object
+PASS RTCTrackEvent interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCTrackEvent interface: existence and properties of interface prototype object's @@unscopables property
+PASS RTCTrackEvent interface: attribute receiver
+PASS RTCTrackEvent interface: attribute track
+PASS RTCTrackEvent interface: attribute streams
+PASS RTCTrackEvent interface: attribute transceiver
+FAIL RTCTrackEvent must be primary interface of initTrackEvent() assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL Stringification of initTrackEvent() assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCTrackEvent interface: initTrackEvent() must inherit property "receiver" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCTrackEvent interface: initTrackEvent() must inherit property "track" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCTrackEvent interface: initTrackEvent() must inherit property "streams" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCTrackEvent interface: initTrackEvent() must inherit property "transceiver" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
+FAIL RTCSctpTransport interface: existence and properties of interface object assert_own_property: self does not have own property "RTCSctpTransport" expected property "RTCSctpTransport" missing
+FAIL RTCSctpTransport interface object length assert_own_property: self does not have own property "RTCSctpTransport" expected property "RTCSctpTransport" missing
+FAIL RTCSctpTransport interface object name assert_own_property: self does not have own property "RTCSctpTransport" expected property "RTCSctpTransport" missing
+FAIL RTCSctpTransport interface: existence and properties of interface prototype object assert_own_property: self does not have own property "RTCSctpTransport" expected property "RTCSctpTransport" missing
+FAIL RTCSctpTransport interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "RTCSctpTransport" expected property "RTCSctpTransport" missing
+FAIL RTCSctpTransport interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "RTCSctpTransport" expected property "RTCSctpTransport" missing
+FAIL RTCSctpTransport interface: attribute transport assert_own_property: self does not have own property "RTCSctpTransport" expected property "RTCSctpTransport" missing
+FAIL RTCSctpTransport interface: attribute state assert_own_property: self does not have own property "RTCSctpTransport" expected property "RTCSctpTransport" missing
+FAIL RTCSctpTransport interface: attribute maxMessageSize assert_own_property: self does not have own property "RTCSctpTransport" expected property "RTCSctpTransport" missing
+FAIL RTCSctpTransport interface: attribute maxChannels assert_own_property: self does not have own property "RTCSctpTransport" expected property "RTCSctpTransport" missing
+FAIL RTCSctpTransport interface: attribute onstatechange assert_own_property: self does not have own property "RTCSctpTransport" expected property "RTCSctpTransport" missing
+FAIL RTCSctpTransport must be primary interface of idlTestObjects.sctpTransport assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL Stringification of idlTestObjects.sctpTransport assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCSctpTransport interface: idlTestObjects.sctpTransport must inherit property "transport" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCSctpTransport interface: idlTestObjects.sctpTransport must inherit property "state" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCSctpTransport interface: idlTestObjects.sctpTransport must inherit property "maxMessageSize" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCSctpTransport interface: idlTestObjects.sctpTransport must inherit property "maxChannels" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCSctpTransport interface: idlTestObjects.sctpTransport must inherit property "onstatechange" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+PASS RTCDataChannel interface: existence and properties of interface object
+PASS RTCDataChannel interface object length
+PASS RTCDataChannel interface object name
+PASS RTCDataChannel interface: existence and properties of interface prototype object
+PASS RTCDataChannel interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCDataChannel interface: existence and properties of interface prototype object's @@unscopables property
+PASS RTCDataChannel interface: attribute label
+PASS RTCDataChannel interface: attribute ordered
+FAIL RTCDataChannel interface: attribute maxPacketLifeTime assert_true: The prototype object must have a property "maxPacketLifeTime" expected true got false
+PASS RTCDataChannel interface: attribute maxRetransmits
+PASS RTCDataChannel interface: attribute protocol
+PASS RTCDataChannel interface: attribute negotiated
+PASS RTCDataChannel interface: attribute id
+FAIL RTCDataChannel interface: attribute priority assert_true: The prototype object must have a property "priority" expected true got false
+PASS RTCDataChannel interface: attribute readyState
+PASS RTCDataChannel interface: attribute bufferedAmount
+PASS RTCDataChannel interface: attribute bufferedAmountLowThreshold
+PASS RTCDataChannel interface: attribute onopen
+PASS RTCDataChannel interface: attribute onbufferedamountlow
+PASS RTCDataChannel interface: attribute onerror
+PASS RTCDataChannel interface: attribute onclose
+PASS RTCDataChannel interface: operation close()
+PASS RTCDataChannel interface: attribute onmessage
+PASS RTCDataChannel interface: attribute binaryType
+PASS RTCDataChannel interface: operation send(USVString)
+PASS RTCDataChannel interface: operation send(Blob)
+PASS RTCDataChannel interface: operation send(ArrayBuffer)
+PASS RTCDataChannel interface: operation send(ArrayBufferView)
+PASS RTCDataChannel must be primary interface of new RTCPeerConnection().createDataChannel('')
+PASS Stringification of new RTCPeerConnection().createDataChannel('')
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "label" with the proper type
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "ordered" with the proper type
+FAIL RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "maxPacketLifeTime" with the proper type assert_inherits: property "maxPacketLifeTime" not found in prototype chain
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "maxRetransmits" with the proper type
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "protocol" with the proper type
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "negotiated" with the proper type
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "id" with the proper type
+FAIL RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "priority" with the proper type assert_inherits: property "priority" not found in prototype chain
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "readyState" with the proper type
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "bufferedAmount" with the proper type
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "bufferedAmountLowThreshold" with the proper type
+FAIL RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "onopen" with the proper type Unrecognized type EventHandler
+FAIL RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "onbufferedamountlow" with the proper type Unrecognized type EventHandler
+FAIL RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "onerror" with the proper type Unrecognized type EventHandler
+FAIL RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "onclose" with the proper type Unrecognized type EventHandler
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "close()" with the proper type
+FAIL RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "onmessage" with the proper type Unrecognized type EventHandler
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "binaryType" with the proper type
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "send(USVString)" with the proper type
+PASS RTCDataChannel interface: calling send(USVString) on new RTCPeerConnection().createDataChannel('') with too few arguments must throw TypeError
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "send(Blob)" with the proper type
+PASS RTCDataChannel interface: calling send(Blob) on new RTCPeerConnection().createDataChannel('') with too few arguments must throw TypeError
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "send(ArrayBuffer)" with the proper type
+PASS RTCDataChannel interface: calling send(ArrayBuffer) on new RTCPeerConnection().createDataChannel('') with too few arguments must throw TypeError
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "send(ArrayBufferView)" with the proper type
+PASS RTCDataChannel interface: calling send(ArrayBufferView) on new RTCPeerConnection().createDataChannel('') with too few arguments must throw TypeError
+PASS RTCDataChannelEvent interface: existence and properties of interface object
+PASS RTCDataChannelEvent interface object length
+PASS RTCDataChannelEvent interface object name
+PASS RTCDataChannelEvent interface: existence and properties of interface prototype object
+PASS RTCDataChannelEvent interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCDataChannelEvent interface: existence and properties of interface prototype object's @@unscopables property
+PASS RTCDataChannelEvent interface: attribute channel
+PASS RTCDataChannelEvent must be primary interface of new RTCDataChannelEvent('channel', {
+          channel: new RTCPeerConnection().createDataChannel('')
+        })
+PASS Stringification of new RTCDataChannelEvent('channel', {
+          channel: new RTCPeerConnection().createDataChannel('')
+        })
+PASS RTCDataChannelEvent interface: new RTCDataChannelEvent('channel', {
+          channel: new RTCPeerConnection().createDataChannel('')
+        }) must inherit property "channel" with the proper type
+PASS RTCDTMFSender interface: existence and properties of interface object
+PASS RTCDTMFSender interface object length
+PASS RTCDTMFSender interface object name
+PASS RTCDTMFSender interface: existence and properties of interface prototype object
+PASS RTCDTMFSender interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCDTMFSender interface: existence and properties of interface prototype object's @@unscopables property
+PASS RTCDTMFSender interface: operation insertDTMF(DOMString, unsigned long, unsigned long)
+PASS RTCDTMFSender interface: attribute ontonechange
+PASS RTCDTMFSender interface: attribute canInsertDTMF
+PASS RTCDTMFSender interface: attribute toneBuffer
+PASS RTCDTMFToneChangeEvent interface: existence and properties of interface object
+PASS RTCDTMFToneChangeEvent interface object length
+PASS RTCDTMFToneChangeEvent interface object name
+PASS RTCDTMFToneChangeEvent interface: existence and properties of interface prototype object
+PASS RTCDTMFToneChangeEvent interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCDTMFToneChangeEvent interface: existence and properties of interface prototype object's @@unscopables property
+PASS RTCDTMFToneChangeEvent interface: attribute tone
+PASS RTCStatsReport interface: existence and properties of interface object
+PASS RTCStatsReport interface object length
+PASS RTCStatsReport interface object name
+PASS RTCStatsReport interface: existence and properties of interface prototype object
+PASS RTCStatsReport interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCStatsReport interface: existence and properties of interface prototype object's @@unscopables property
+FAIL RTCStatsEvent interface: existence and properties of interface object assert_own_property: self does not have own property "RTCStatsEvent" expected property "RTCStatsEvent" missing
+FAIL RTCStatsEvent interface object length assert_own_property: self does not have own property "RTCStatsEvent" expected property "RTCStatsEvent" missing
+FAIL RTCStatsEvent interface object name assert_own_property: self does not have own property "RTCStatsEvent" expected property "RTCStatsEvent" missing
+FAIL RTCStatsEvent interface: existence and properties of interface prototype object assert_own_property: self does not have own property "RTCStatsEvent" expected property "RTCStatsEvent" missing
+FAIL RTCStatsEvent interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "RTCStatsEvent" expected property "RTCStatsEvent" missing
+FAIL RTCStatsEvent interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "RTCStatsEvent" expected property "RTCStatsEvent" missing
+FAIL RTCStatsEvent interface: attribute report assert_own_property: self does not have own property "RTCStatsEvent" expected property "RTCStatsEvent" missing
+FAIL RTCErrorEvent interface: existence and properties of interface object assert_own_property: self does not have own property "RTCErrorEvent" expected property "RTCErrorEvent" missing
+FAIL RTCErrorEvent interface object length assert_own_property: self does not have own property "RTCErrorEvent" expected property "RTCErrorEvent" missing
+FAIL RTCErrorEvent interface object name assert_own_property: self does not have own property "RTCErrorEvent" expected property "RTCErrorEvent" missing
+FAIL RTCErrorEvent interface: existence and properties of interface prototype object assert_own_property: self does not have own property "RTCErrorEvent" expected property "RTCErrorEvent" missing
+FAIL RTCErrorEvent interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "RTCErrorEvent" expected property "RTCErrorEvent" missing
+FAIL RTCErrorEvent interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "RTCErrorEvent" expected property "RTCErrorEvent" missing
+FAIL RTCErrorEvent interface: attribute error assert_own_property: self does not have own property "RTCErrorEvent" expected property "RTCErrorEvent" missing
+FAIL RTCErrorEvent must be primary interface of new RTCErrorEvent('error') assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: RTCErrorEvent is not defined"
+FAIL Stringification of new RTCErrorEvent('error') assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: RTCErrorEvent is not defined"
+FAIL RTCErrorEvent interface: new RTCErrorEvent('error') must inherit property "error" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: RTCErrorEvent is not defined"
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/idlharness.https.window.js b/third_party/WebKit/LayoutTests/external/wpt/webrtc/idlharness.https.window.js
new file mode 100644
index 0000000..3c57a022
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/idlharness.https.window.js
@@ -0,0 +1,143 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+// META: script=./RTCPeerConnection-helper.js
+
+'use strict';
+
+// The following helper functions are called from RTCPeerConnection-helper.js:
+//   generateAnswer()
+//   getNoiseStream()
+
+// Put the global IDL test objects under a parent object.
+// This allows easier search for the test cases when
+// viewing the web page
+const idlTestObjects = {};
+
+// Helper function to create RTCTrackEvent object
+function initTrackEvent() {
+  const pc = new RTCPeerConnection();
+  const transceiver = pc.addTransceiver('audio');
+  const { sender, receiver } = transceiver;
+  const { track } = receiver;
+  return new RTCTrackEvent('track', {
+    receiver, track, transceiver
+  });
+}
+
+// List of async test driver functions
+const asyncInitTasks = [
+  asyncInitCertificate,
+  asyncInitTransports,
+  asyncInitMediaStreamTrack,
+];
+
+// Asynchronously generate an RTCCertificate
+function asyncInitCertificate() {
+  return RTCPeerConnection.generateCertificate({
+    name: 'RSASSA-PKCS1-v1_5',
+    modulusLength: 2048,
+    publicExponent: new Uint8Array([1, 0, 1]),
+    hash: 'SHA-256'
+  }).then(cert => {
+    idlTestObjects.certificate = cert;
+  });
+}
+
+// Asynchronously generate instances of
+// RTCSctpTransport, RTCDtlsTransport,
+// and RTCIceTransport
+function asyncInitTransports() {
+  const pc = new RTCPeerConnection();
+  pc.createDataChannel('test');
+
+  // setting answer description initializes pc.sctp
+  return pc.createOffer()
+  .then(offer =>
+    pc.setLocalDescription(offer)
+    .then(() => generateAnswer(offer)))
+  .then(answer => pc.setRemoteDescription(answer))
+  .then(() => {
+    const sctpTransport = pc.sctp;
+    assert_true(sctpTransport instanceof RTCSctpTransport,
+      'Expect pc.sctp to be instance of RTCSctpTransport');
+    idlTestObjects.sctpTransport = sctpTransport;
+
+    const dtlsTransport = sctpTransport.transport;
+    assert_true(dtlsTransport instanceof RTCDtlsTransport,
+      'Expect sctpTransport.transport to be instance of RTCDtlsTransport');
+    idlTestObjects.dtlsTransport = dtlsTransport;
+
+    const iceTransport = dtlsTransport.transport;
+    idlTestObjects.iceTransport = iceTransport;
+  });
+}
+
+// Asynchoronously generate MediaStreamTrack from getUserMedia
+function asyncInitMediaStreamTrack() {
+  return getNoiseStream({ audio: true })
+    .then(mediaStream => {
+      idlTestObjects.mediaStreamTrack = mediaStream.getTracks()[0];
+    });
+}
+
+// Run all async test drivers, report and swallow any error
+// thrown/rejected. Proper test for correct initialization
+// of the objects are done in their respective test files.
+function asyncInit() {
+  return Promise.all(asyncInitTasks.map(
+    task => {
+      const t = async_test(`Test driver for ${task.name}`);
+      let promise;
+      t.step(() => {
+        promise = task().then(
+          t.step_func_done(),
+          t.step_func(err =>
+            assert_unreached(`Failed to run ${task.name}: ${err}`)));
+      });
+      return promise;
+    }));
+}
+
+idl_test(
+  ['webrtc'],
+  ['mediacapture-streams', 'dom'],
+  async idlArray => {
+    idlArray.add_objects({
+      RTCPeerConnection: [`new RTCPeerConnection()`],
+      RTCSessionDescription: [`new RTCSessionDescription({ type: 'offer' })`],
+      RTCIceCandidate: [`new RTCIceCandidate({ sdpMid: 1 })`],
+      RTCDataChannel: [`new RTCPeerConnection().createDataChannel('')`],
+      RTCRtpTransceiver: [`new RTCPeerConnection().addTransceiver('audio')`],
+      RTCRtpSender: [`new RTCPeerConnection().addTransceiver('audio').sender`],
+      RTCRtpReceiver: [`new RTCPeerConnection().addTransceiver('audio').receiver`],
+      RTCPeerConnectionIceEvent: [`new RTCPeerConnectionIceEvent('ice')`],
+      RTCPeerConnectionIceErrorEvent: [
+        `new RTCPeerConnectionIceErrorEvent('ice-error', { errorCode: 701 });`
+      ],
+      RTCTrackEvent: [`initTrackEvent()`],
+      RTCErrorEvent: [`new RTCErrorEvent('error')`],
+      RTCDataChannelEvent: [
+        `new RTCDataChannelEvent('channel', {
+          channel: new RTCPeerConnection().createDataChannel('')
+        })`
+      ],
+      // Async initialized objects below
+      RTCCertificate: ['idlTestObjects.certificate'],
+      RTCSctpTransport: ['idlTestObjects.sctpTransport'],
+      RTCDtlsTransport: ['idlTestObjects.dtlsTransport'],
+      RTCIceTransport: ['idlTestObjects.iceTransport'],
+      MediaStreamTrack: ['idlTestObjects.mediaStreamTrack'],
+    });
+    /*
+      TODO
+        RTCRtpContributingSource
+        RTCRtpSynchronizationSource
+        RTCDTMFSender
+        RTCDTMFToneChangeEvent
+        RTCIdentityProviderRegistrar
+        RTCIdentityAssertion
+    */
+
+    await asyncInit();
+  }
+);
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/interfaces.https.html b/third_party/WebKit/LayoutTests/external/wpt/webrtc/interfaces.https.html
deleted file mode 100644
index a834f89..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/webrtc/interfaces.https.html
+++ /dev/null
@@ -1,173 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<title>WebRTC IDL Tests</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/resources/WebIDLParser.js"></script>
-<script src="/resources/idlharness.js"></script>
-<script src="./RTCPeerConnection-helper.js"></script>
-<script>
-  'use strict';
-
-  // The following helper functions are called from RTCPeerConnection-helper.js:
-  //   generateAnswer()
-  //   getNoiseStream()
-
-  // Put the global IDL test objects under a parent object.
-  // This allows easier search for the test cases when
-  // viewing the web page
-  const idlTestObjects = {};
-
-  // Helper function to create RTCTrackEvent object
-  function initTrackEvent() {
-    const pc = new RTCPeerConnection();
-    const transceiver = pc.addTransceiver('audio');
-    const { sender, receiver } = transceiver;
-    const { track } = receiver;
-    return new RTCTrackEvent('track', {
-      receiver, track, transceiver
-    });
-  }
-
-  // List of async test driver functions
-  const asyncInitTasks = [
-    asyncInitCertificate,
-    asyncInitTransports,
-    asyncInitMediaStreamTrack,
-  ];
-
-  // Asynchronously generate an RTCCertificate
-  function asyncInitCertificate() {
-    return RTCPeerConnection.generateCertificate({
-      name: 'RSASSA-PKCS1-v1_5',
-      modulusLength: 2048,
-      publicExponent: new Uint8Array([1, 0, 1]),
-      hash: 'SHA-256'
-    }).then(cert => {
-      idlTestObjects.certificate = cert;
-    });
-  }
-
-  // Asynchronously generate instances of
-  // RTCSctpTransport, RTCDtlsTransport,
-  // and RTCIceTransport
-  function asyncInitTransports() {
-    const pc = new RTCPeerConnection();
-    pc.createDataChannel('test');
-
-    // setting answer description initializes pc.sctp
-    return pc.createOffer()
-    .then(offer =>
-      pc.setLocalDescription(offer)
-      .then(() => generateAnswer(offer)))
-    .then(answer => pc.setRemoteDescription(answer))
-    .then(() => {
-      const sctpTransport = pc.sctp;
-      assert_true(sctpTransport instanceof RTCSctpTransport,
-        'Expect pc.sctp to be instance of RTCSctpTransport');
-      idlTestObjects.sctpTransport = sctpTransport;
-
-      const dtlsTransport = sctpTransport.transport;
-      assert_true(dtlsTransport instanceof RTCDtlsTransport,
-        'Expect sctpTransport.transport to be instance of RTCDtlsTransport');
-      idlTestObjects.dtlsTransport = dtlsTransport;
-
-      const iceTransport = dtlsTransport.transport;
-      idlTestObjects.iceTransport = iceTransport;
-    });
-  }
-
-  // Asynchoronously generate MediaStreamTrack from getUserMedia
-  function asyncInitMediaStreamTrack() {
-    return getNoiseStream({ audio: true })
-    .then(mediaStream => {
-      idlTestObjects.mediaStreamTrack = mediaStream.getTracks()[0];
-    });
-  }
-
-  // Run all async test drivers, report and swallow any error
-  // thrown/rejected. Proper test for correct initialization
-  // of the objects are done in their respective test files.
-  function asyncInit() {
-    return Promise.all(asyncInitTasks.map(
-      task => {
-        const t = async_test(`Test driver for ${task.name}`);
-        let promise;
-        t.step(() => {
-          promise = task().then(
-            t.step_func_done(),
-            t.step_func(err =>
-              assert_unreached(`Failed to run ${task.name}: ${err}`)));
-        });
-        return promise;
-      }));
-  }
-
-  promise_test(async t => {
-    await asyncInit();
-
-    const idlArray = new IdlArray();
-
-    let webrtcIdl = fetch('/interfaces/webrtc-pc.idl').then(r => r.text());
-    let mediacaptureMainIdl = fetch('/interfaces/mediacapture-streams.idl').then(r => r.text());
-
-    idlArray.add_untested_idls(mediacaptureMainIdl, { only: ['MediaStreamConstraints'] });
-    idlArray.add_idls(webrtcIdl);
-
-    idlArray.add_untested_idls('interface EventHandler {};');
-    idlArray.add_idls('interface EventTarget {};');
-    idlArray.add_idls('interface MediaStreamTrack : EventTarget {};');
-
-    idlArray.add_objects({
-      'RTCPeerConnection': [`new RTCPeerConnection()`],
-
-      'RTCSessionDescription': [`new RTCSessionDescription({ type: 'offer' })`],
-
-      'RTCIceCandidate': [`new RTCIceCandidate({ sdpMid: 1 })`],
-
-      'RTCDataChannel': [`new RTCPeerConnection().createDataChannel('')`],
-
-      'RTCRtpTransceiver': [`new RTCPeerConnection().addTransceiver('audio')`],
-
-      'RTCRtpSender': [`new RTCPeerConnection().addTransceiver('audio').sender`],
-
-      'RTCRtpReceiver': [`new RTCPeerConnection().addTransceiver('audio').receiver`],
-
-      'RTCPeerConnectionIceEvent': [`new RTCPeerConnectionIceEvent('ice')`],
-
-      'RTCPeerConnectionIceErrorEvent':
-        [`new RTCPeerConnectionIceErrorEvent('ice-error', { errorCode: 701 });`],
-
-      'RTCTrackEvent': [`initTrackEvent()`],
-
-      'RTCErrorEvent': [`new RTCErrorEvent('error')`],
-
-      'RTCDataChannelEvent': [`new RTCDataChannelEvent('channel',
-        { channel: new RTCPeerConnection().createDataChannel('') })`],
-
-      // Async initialized objects below
-
-      'RTCCertificate': ['idlTestObjects.certificate'],
-
-      'RTCSctpTransport': ['idlTestObjects.sctpTransport'],
-
-      'RTCDtlsTransport': ['idlTestObjects.dtlsTransport'],
-
-      'RTCIceTransport': ['idlTestObjects.iceTransport'],
-
-      'MediaStreamTrack': ['idlTestObjects.mediaStreamTrack']
-    });
-
-    idlArray.test();
-  }, 'Main test driver');
-
-  /*
-    TODO
-      RTCRtpContributingSource
-      RTCRtpSynchronizationSource
-      RTCDTMFSender
-      RTCDTMFToneChangeEvent
-      RTCIdentityProviderRegistrar
-      RTCIdentityAssertion
-   */
-</script>
diff --git a/third_party/WebKit/LayoutTests/virtual/webrtc-wpt-unified-plan/external/wpt/webrtc/idlharness.https.window-expected.txt b/third_party/WebKit/LayoutTests/virtual/webrtc-wpt-unified-plan/external/wpt/webrtc/idlharness.https.window-expected.txt
new file mode 100644
index 0000000..7311d7db
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/webrtc-wpt-unified-plan/external/wpt/webrtc/idlharness.https.window-expected.txt
@@ -0,0 +1,503 @@
+This is a testharness.js-based test.
+PASS idl_test setup
+PASS Test driver for asyncInitCertificate
+FAIL Test driver for asyncInitTransports assert_unreached: Failed to run asyncInitTransports: ReferenceError: RTCSctpTransport is not defined Reached unreachable code
+PASS Test driver for asyncInitMediaStreamTrack
+PASS Partial interface RTCPeerConnection: original interface defined
+PASS Partial dictionary RTCOfferOptions: original dictionary defined
+PASS Partial interface RTCPeerConnection[2]: original interface defined
+PASS Partial interface RTCPeerConnection[3]: original interface defined
+PASS Partial interface RTCPeerConnection[4]: original interface defined
+PASS Partial interface RTCRtpSender: original interface defined
+PASS Partial interface RTCPeerConnection[5]: original interface defined
+PASS RTCPeerConnection interface: existence and properties of interface object
+PASS RTCPeerConnection interface object length
+PASS RTCPeerConnection interface object name
+PASS RTCPeerConnection interface: existence and properties of interface prototype object
+PASS RTCPeerConnection interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCPeerConnection interface: existence and properties of interface prototype object's @@unscopables property
+PASS RTCPeerConnection interface: operation createOffer(RTCOfferOptions)
+PASS RTCPeerConnection interface: operation createAnswer(RTCAnswerOptions)
+PASS RTCPeerConnection interface: operation setLocalDescription(RTCSessionDescriptionInit)
+PASS RTCPeerConnection interface: attribute localDescription
+FAIL RTCPeerConnection interface: attribute currentLocalDescription assert_true: The prototype object must have a property "currentLocalDescription" expected true got false
+FAIL RTCPeerConnection interface: attribute pendingLocalDescription assert_true: The prototype object must have a property "pendingLocalDescription" expected true got false
+PASS RTCPeerConnection interface: operation setRemoteDescription(RTCSessionDescriptionInit)
+PASS RTCPeerConnection interface: attribute remoteDescription
+FAIL RTCPeerConnection interface: attribute currentRemoteDescription assert_true: The prototype object must have a property "currentRemoteDescription" expected true got false
+FAIL RTCPeerConnection interface: attribute pendingRemoteDescription assert_true: The prototype object must have a property "pendingRemoteDescription" expected true got false
+PASS RTCPeerConnection interface: operation addIceCandidate([object Object],[object Object])
+PASS RTCPeerConnection interface: attribute signalingState
+PASS RTCPeerConnection interface: attribute iceGatheringState
+PASS RTCPeerConnection interface: attribute iceConnectionState
+FAIL RTCPeerConnection interface: attribute connectionState assert_true: The prototype object must have a property "connectionState" expected true got false
+FAIL RTCPeerConnection interface: attribute canTrickleIceCandidates assert_true: The prototype object must have a property "canTrickleIceCandidates" expected true got false
+FAIL RTCPeerConnection interface: operation getDefaultIceServers() assert_own_property: interface object missing static operation expected property "getDefaultIceServers" missing
+FAIL RTCPeerConnection interface: operation getConfiguration() assert_own_property: interface prototype object missing non-static operation expected property "getConfiguration" missing
+PASS RTCPeerConnection interface: operation setConfiguration(RTCConfiguration)
+PASS RTCPeerConnection interface: operation close()
+PASS RTCPeerConnection interface: attribute onnegotiationneeded
+PASS RTCPeerConnection interface: attribute onicecandidate
+FAIL RTCPeerConnection interface: attribute onicecandidateerror assert_true: The prototype object must have a property "onicecandidateerror" expected true got false
+PASS RTCPeerConnection interface: attribute onsignalingstatechange
+PASS RTCPeerConnection interface: attribute oniceconnectionstatechange
+PASS RTCPeerConnection interface: attribute onicegatheringstatechange
+FAIL RTCPeerConnection interface: attribute onconnectionstatechange assert_true: The prototype object must have a property "onconnectionstatechange" expected true got false
+PASS RTCPeerConnection interface: operation createOffer(RTCSessionDescriptionCallback, RTCPeerConnectionErrorCallback, RTCOfferOptions)
+PASS RTCPeerConnection interface: operation setLocalDescription(RTCSessionDescriptionInit, VoidFunction, RTCPeerConnectionErrorCallback)
+PASS RTCPeerConnection interface: operation createAnswer(RTCSessionDescriptionCallback, RTCPeerConnectionErrorCallback)
+PASS RTCPeerConnection interface: operation setRemoteDescription(RTCSessionDescriptionInit, VoidFunction, RTCPeerConnectionErrorCallback)
+PASS RTCPeerConnection interface: operation addIceCandidate([object Object],[object Object], VoidFunction, RTCPeerConnectionErrorCallback)
+PASS RTCPeerConnection interface: operation generateCertificate(AlgorithmIdentifier)
+PASS RTCPeerConnection interface: operation getSenders()
+PASS RTCPeerConnection interface: operation getReceivers()
+PASS RTCPeerConnection interface: operation getTransceivers()
+PASS RTCPeerConnection interface: operation addTrack(MediaStreamTrack, MediaStream)
+PASS RTCPeerConnection interface: operation removeTrack(RTCRtpSender)
+PASS RTCPeerConnection interface: operation addTransceiver([object Object],[object Object], RTCRtpTransceiverInit)
+PASS RTCPeerConnection interface: attribute ontrack
+FAIL RTCPeerConnection interface: attribute sctp assert_true: The prototype object must have a property "sctp" expected true got false
+PASS RTCPeerConnection interface: operation createDataChannel(USVString, RTCDataChannelInit)
+PASS RTCPeerConnection interface: attribute ondatachannel
+PASS RTCPeerConnection interface: operation getStats(MediaStreamTrack)
+FAIL RTCPeerConnection interface: attribute onstatsended assert_true: The prototype object must have a property "onstatsended" expected true got false
+PASS RTCPeerConnection must be primary interface of new RTCPeerConnection()
+PASS Stringification of new RTCPeerConnection()
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "createOffer(RTCOfferOptions)" with the proper type
+PASS RTCPeerConnection interface: calling createOffer(RTCOfferOptions) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "createAnswer(RTCAnswerOptions)" with the proper type
+PASS RTCPeerConnection interface: calling createAnswer(RTCAnswerOptions) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "setLocalDescription(RTCSessionDescriptionInit)" with the proper type
+PASS RTCPeerConnection interface: calling setLocalDescription(RTCSessionDescriptionInit) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "localDescription" with the proper type
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "currentLocalDescription" with the proper type assert_inherits: property "currentLocalDescription" not found in prototype chain
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "pendingLocalDescription" with the proper type assert_inherits: property "pendingLocalDescription" not found in prototype chain
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "setRemoteDescription(RTCSessionDescriptionInit)" with the proper type
+PASS RTCPeerConnection interface: calling setRemoteDescription(RTCSessionDescriptionInit) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "remoteDescription" with the proper type
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "currentRemoteDescription" with the proper type assert_inherits: property "currentRemoteDescription" not found in prototype chain
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "pendingRemoteDescription" with the proper type assert_inherits: property "pendingRemoteDescription" not found in prototype chain
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "addIceCandidate([object Object],[object Object])" with the proper type
+PASS RTCPeerConnection interface: calling addIceCandidate([object Object],[object Object]) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "signalingState" with the proper type
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "iceGatheringState" with the proper type
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "iceConnectionState" with the proper type
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "connectionState" with the proper type assert_inherits: property "connectionState" not found in prototype chain
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "canTrickleIceCandidates" with the proper type assert_inherits: property "canTrickleIceCandidates" not found in prototype chain
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "getDefaultIceServers()" with the proper type
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "getConfiguration()" with the proper type assert_inherits: property "getConfiguration" not found in prototype chain
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "setConfiguration(RTCConfiguration)" with the proper type
+PASS RTCPeerConnection interface: calling setConfiguration(RTCConfiguration) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "close()" with the proper type
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "onnegotiationneeded" with the proper type Unrecognized type EventHandler
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "onicecandidate" with the proper type Unrecognized type EventHandler
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "onicecandidateerror" with the proper type assert_inherits: property "onicecandidateerror" not found in prototype chain
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "onsignalingstatechange" with the proper type Unrecognized type EventHandler
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "oniceconnectionstatechange" with the proper type Unrecognized type EventHandler
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "onicegatheringstatechange" with the proper type Unrecognized type EventHandler
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "onconnectionstatechange" with the proper type assert_inherits: property "onconnectionstatechange" not found in prototype chain
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "createOffer(RTCSessionDescriptionCallback, RTCPeerConnectionErrorCallback, RTCOfferOptions)" with the proper type
+PASS RTCPeerConnection interface: calling createOffer(RTCSessionDescriptionCallback, RTCPeerConnectionErrorCallback, RTCOfferOptions) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "setLocalDescription(RTCSessionDescriptionInit, VoidFunction, RTCPeerConnectionErrorCallback)" with the proper type
+PASS RTCPeerConnection interface: calling setLocalDescription(RTCSessionDescriptionInit, VoidFunction, RTCPeerConnectionErrorCallback) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "createAnswer(RTCSessionDescriptionCallback, RTCPeerConnectionErrorCallback)" with the proper type
+PASS RTCPeerConnection interface: calling createAnswer(RTCSessionDescriptionCallback, RTCPeerConnectionErrorCallback) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "setRemoteDescription(RTCSessionDescriptionInit, VoidFunction, RTCPeerConnectionErrorCallback)" with the proper type
+PASS RTCPeerConnection interface: calling setRemoteDescription(RTCSessionDescriptionInit, VoidFunction, RTCPeerConnectionErrorCallback) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "addIceCandidate([object Object],[object Object], VoidFunction, RTCPeerConnectionErrorCallback)" with the proper type
+PASS RTCPeerConnection interface: calling addIceCandidate([object Object],[object Object], VoidFunction, RTCPeerConnectionErrorCallback) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "generateCertificate(AlgorithmIdentifier)" with the proper type
+PASS RTCPeerConnection interface: calling generateCertificate(AlgorithmIdentifier) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "getSenders()" with the proper type
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "getReceivers()" with the proper type
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "getTransceivers()" with the proper type
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "addTrack(MediaStreamTrack, MediaStream)" with the proper type
+PASS RTCPeerConnection interface: calling addTrack(MediaStreamTrack, MediaStream) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "removeTrack(RTCRtpSender)" with the proper type
+PASS RTCPeerConnection interface: calling removeTrack(RTCRtpSender) on new RTCPeerConnection() with too few arguments must throw TypeError
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "addTransceiver([object Object],[object Object], RTCRtpTransceiverInit)" with the proper type
+PASS RTCPeerConnection interface: calling addTransceiver([object Object],[object Object], RTCRtpTransceiverInit) on new RTCPeerConnection() with too few arguments must throw TypeError
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "ontrack" with the proper type Unrecognized type EventHandler
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "sctp" with the proper type assert_inherits: property "sctp" not found in prototype chain
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "createDataChannel(USVString, RTCDataChannelInit)" with the proper type
+PASS RTCPeerConnection interface: calling createDataChannel(USVString, RTCDataChannelInit) on new RTCPeerConnection() with too few arguments must throw TypeError
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "ondatachannel" with the proper type Unrecognized type EventHandler
+PASS RTCPeerConnection interface: new RTCPeerConnection() must inherit property "getStats(MediaStreamTrack)" with the proper type
+PASS RTCPeerConnection interface: calling getStats(MediaStreamTrack) on new RTCPeerConnection() with too few arguments must throw TypeError
+FAIL RTCPeerConnection interface: new RTCPeerConnection() must inherit property "onstatsended" with the proper type assert_inherits: property "onstatsended" not found in prototype chain
+PASS RTCSessionDescription interface: existence and properties of interface object
+FAIL RTCSessionDescription interface object length assert_equals: wrong value for RTCSessionDescription.length expected 1 but got 0
+PASS RTCSessionDescription interface object name
+PASS RTCSessionDescription interface: existence and properties of interface prototype object
+PASS RTCSessionDescription interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCSessionDescription interface: existence and properties of interface prototype object's @@unscopables property
+FAIL RTCSessionDescription interface: attribute type assert_equals: setter must be undefined for readonly attributes expected (undefined) undefined but got (function) function "function () { [native code] }"
+FAIL RTCSessionDescription interface: attribute sdp assert_equals: setter must be undefined for readonly attributes expected (undefined) undefined but got (function) function "function () { [native code] }"
+PASS RTCSessionDescription interface: operation toJSON()
+PASS RTCSessionDescription must be primary interface of new RTCSessionDescription({ type: 'offer' })
+PASS Stringification of new RTCSessionDescription({ type: 'offer' })
+PASS RTCSessionDescription interface: new RTCSessionDescription({ type: 'offer' }) must inherit property "type" with the proper type
+FAIL RTCSessionDescription interface: new RTCSessionDescription({ type: 'offer' }) must inherit property "sdp" with the proper type assert_equals: expected "string" but got "object"
+PASS RTCSessionDescription interface: new RTCSessionDescription({ type: 'offer' }) must inherit property "toJSON()" with the proper type
+FAIL Test default toJSON operation of RTCSessionDescription assert_equals: expected "string" but got "object"
+PASS RTCIceCandidate interface: existence and properties of interface object
+FAIL RTCIceCandidate interface object length assert_equals: wrong value for RTCIceCandidate.length expected 0 but got 1
+PASS RTCIceCandidate interface object name
+PASS RTCIceCandidate interface: existence and properties of interface prototype object
+PASS RTCIceCandidate interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCIceCandidate interface: existence and properties of interface prototype object's @@unscopables property
+FAIL RTCIceCandidate interface: attribute candidate assert_equals: setter must be undefined for readonly attributes expected (undefined) undefined but got (function) function "function () { [native code] }"
+FAIL RTCIceCandidate interface: attribute sdpMid assert_equals: setter must be undefined for readonly attributes expected (undefined) undefined but got (function) function "function () { [native code] }"
+FAIL RTCIceCandidate interface: attribute sdpMLineIndex assert_equals: setter must be undefined for readonly attributes expected (undefined) undefined but got (function) function "function () { [native code] }"
+FAIL RTCIceCandidate interface: attribute foundation assert_true: The prototype object must have a property "foundation" expected true got false
+FAIL RTCIceCandidate interface: attribute component assert_true: The prototype object must have a property "component" expected true got false
+FAIL RTCIceCandidate interface: attribute priority assert_true: The prototype object must have a property "priority" expected true got false
+FAIL RTCIceCandidate interface: attribute ip assert_true: The prototype object must have a property "ip" expected true got false
+FAIL RTCIceCandidate interface: attribute protocol assert_true: The prototype object must have a property "protocol" expected true got false
+FAIL RTCIceCandidate interface: attribute port assert_true: The prototype object must have a property "port" expected true got false
+FAIL RTCIceCandidate interface: attribute type assert_true: The prototype object must have a property "type" expected true got false
+FAIL RTCIceCandidate interface: attribute tcpType assert_true: The prototype object must have a property "tcpType" expected true got false
+FAIL RTCIceCandidate interface: attribute relatedAddress assert_true: The prototype object must have a property "relatedAddress" expected true got false
+FAIL RTCIceCandidate interface: attribute relatedPort assert_true: The prototype object must have a property "relatedPort" expected true got false
+FAIL RTCIceCandidate interface: attribute usernameFragment assert_true: The prototype object must have a property "usernameFragment" expected true got false
+PASS RTCIceCandidate interface: operation toJSON()
+FAIL RTCIceCandidate must be primary interface of new RTCIceCandidate({ sdpMid: 1 }) assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL Stringification of new RTCIceCandidate({ sdpMid: 1 }) assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "candidate" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "sdpMid" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "sdpMLineIndex" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "foundation" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "component" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "priority" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "ip" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "protocol" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "port" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "type" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "tcpType" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "relatedAddress" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "relatedPort" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "usernameFragment" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "toJSON()" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
+FAIL Test toJSON operation of toJSON object Cannot read property 'toJSON' of undefined
+PASS RTCPeerConnectionIceEvent interface: existence and properties of interface object
+PASS RTCPeerConnectionIceEvent interface object length
+PASS RTCPeerConnectionIceEvent interface object name
+PASS RTCPeerConnectionIceEvent interface: existence and properties of interface prototype object
+PASS RTCPeerConnectionIceEvent interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCPeerConnectionIceEvent interface: existence and properties of interface prototype object's @@unscopables property
+PASS RTCPeerConnectionIceEvent interface: attribute candidate
+FAIL RTCPeerConnectionIceEvent interface: attribute url assert_true: The prototype object must have a property "url" expected true got false
+PASS RTCPeerConnectionIceEvent must be primary interface of new RTCPeerConnectionIceEvent('ice')
+PASS Stringification of new RTCPeerConnectionIceEvent('ice')
+PASS RTCPeerConnectionIceEvent interface: new RTCPeerConnectionIceEvent('ice') must inherit property "candidate" with the proper type
+FAIL RTCPeerConnectionIceEvent interface: new RTCPeerConnectionIceEvent('ice') must inherit property "url" with the proper type assert_inherits: property "url" not found in prototype chain
+FAIL RTCPeerConnectionIceErrorEvent interface: existence and properties of interface object assert_own_property: self does not have own property "RTCPeerConnectionIceErrorEvent" expected property "RTCPeerConnectionIceErrorEvent" missing
+FAIL RTCPeerConnectionIceErrorEvent interface object length assert_own_property: self does not have own property "RTCPeerConnectionIceErrorEvent" expected property "RTCPeerConnectionIceErrorEvent" missing
+FAIL RTCPeerConnectionIceErrorEvent interface object name assert_own_property: self does not have own property "RTCPeerConnectionIceErrorEvent" expected property "RTCPeerConnectionIceErrorEvent" missing
+FAIL RTCPeerConnectionIceErrorEvent interface: existence and properties of interface prototype object assert_own_property: self does not have own property "RTCPeerConnectionIceErrorEvent" expected property "RTCPeerConnectionIceErrorEvent" missing
+FAIL RTCPeerConnectionIceErrorEvent interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "RTCPeerConnectionIceErrorEvent" expected property "RTCPeerConnectionIceErrorEvent" missing
+FAIL RTCPeerConnectionIceErrorEvent interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "RTCPeerConnectionIceErrorEvent" expected property "RTCPeerConnectionIceErrorEvent" missing
+FAIL RTCPeerConnectionIceErrorEvent interface: attribute hostCandidate assert_own_property: self does not have own property "RTCPeerConnectionIceErrorEvent" expected property "RTCPeerConnectionIceErrorEvent" missing
+FAIL RTCPeerConnectionIceErrorEvent interface: attribute url assert_own_property: self does not have own property "RTCPeerConnectionIceErrorEvent" expected property "RTCPeerConnectionIceErrorEvent" missing
+FAIL RTCPeerConnectionIceErrorEvent interface: attribute errorCode assert_own_property: self does not have own property "RTCPeerConnectionIceErrorEvent" expected property "RTCPeerConnectionIceErrorEvent" missing
+FAIL RTCPeerConnectionIceErrorEvent interface: attribute errorText assert_own_property: self does not have own property "RTCPeerConnectionIceErrorEvent" expected property "RTCPeerConnectionIceErrorEvent" missing
+FAIL RTCPeerConnectionIceErrorEvent must be primary interface of new RTCPeerConnectionIceErrorEvent('ice-error', { errorCode: 701 }); assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: RTCPeerConnectionIceErrorEvent is not defined"
+FAIL Stringification of new RTCPeerConnectionIceErrorEvent('ice-error', { errorCode: 701 }); assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: RTCPeerConnectionIceErrorEvent is not defined"
+FAIL RTCPeerConnectionIceErrorEvent interface: new RTCPeerConnectionIceErrorEvent('ice-error', { errorCode: 701 }); must inherit property "hostCandidate" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: RTCPeerConnectionIceErrorEvent is not defined"
+FAIL RTCPeerConnectionIceErrorEvent interface: new RTCPeerConnectionIceErrorEvent('ice-error', { errorCode: 701 }); must inherit property "url" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: RTCPeerConnectionIceErrorEvent is not defined"
+FAIL RTCPeerConnectionIceErrorEvent interface: new RTCPeerConnectionIceErrorEvent('ice-error', { errorCode: 701 }); must inherit property "errorCode" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: RTCPeerConnectionIceErrorEvent is not defined"
+FAIL RTCPeerConnectionIceErrorEvent interface: new RTCPeerConnectionIceErrorEvent('ice-error', { errorCode: 701 }); must inherit property "errorText" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: RTCPeerConnectionIceErrorEvent is not defined"
+PASS RTCCertificate interface: existence and properties of interface object
+PASS RTCCertificate interface object length
+PASS RTCCertificate interface object name
+PASS RTCCertificate interface: existence and properties of interface prototype object
+PASS RTCCertificate interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCCertificate interface: existence and properties of interface prototype object's @@unscopables property
+PASS RTCCertificate interface: attribute expires
+FAIL RTCCertificate interface: operation getSupportedAlgorithms() assert_own_property: interface object missing static operation expected property "getSupportedAlgorithms" missing
+PASS RTCCertificate interface: operation getFingerprints()
+PASS RTCCertificate must be primary interface of idlTestObjects.certificate
+PASS Stringification of idlTestObjects.certificate
+PASS RTCCertificate interface: idlTestObjects.certificate must inherit property "expires" with the proper type
+PASS RTCCertificate interface: idlTestObjects.certificate must inherit property "getSupportedAlgorithms()" with the proper type
+PASS RTCCertificate interface: idlTestObjects.certificate must inherit property "getFingerprints()" with the proper type
+PASS RTCRtpSender interface: existence and properties of interface object
+PASS RTCRtpSender interface object length
+PASS RTCRtpSender interface object name
+PASS RTCRtpSender interface: existence and properties of interface prototype object
+PASS RTCRtpSender interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCRtpSender interface: existence and properties of interface prototype object's @@unscopables property
+PASS RTCRtpSender interface: attribute track
+FAIL RTCRtpSender interface: attribute transport assert_true: The prototype object must have a property "transport" expected true got false
+FAIL RTCRtpSender interface: attribute rtcpTransport assert_true: The prototype object must have a property "rtcpTransport" expected true got false
+PASS RTCRtpSender interface: operation getCapabilities(DOMString)
+FAIL RTCRtpSender interface: operation setParameters(RTCRtpSendParameters) assert_equals: property has wrong .length expected 1 but got 0
+PASS RTCRtpSender interface: operation getParameters()
+PASS RTCRtpSender interface: operation replaceTrack(MediaStreamTrack)
+FAIL RTCRtpSender interface: operation setStreams(MediaStream) assert_own_property: interface prototype object missing non-static operation expected property "setStreams" missing
+PASS RTCRtpSender interface: operation getStats()
+PASS RTCRtpSender interface: attribute dtmf
+PASS RTCRtpSender must be primary interface of new RTCPeerConnection().addTransceiver('audio').sender
+PASS Stringification of new RTCPeerConnection().addTransceiver('audio').sender
+PASS RTCRtpSender interface: new RTCPeerConnection().addTransceiver('audio').sender must inherit property "track" with the proper type
+FAIL RTCRtpSender interface: new RTCPeerConnection().addTransceiver('audio').sender must inherit property "transport" with the proper type assert_inherits: property "transport" not found in prototype chain
+FAIL RTCRtpSender interface: new RTCPeerConnection().addTransceiver('audio').sender must inherit property "rtcpTransport" with the proper type assert_inherits: property "rtcpTransport" not found in prototype chain
+PASS RTCRtpSender interface: new RTCPeerConnection().addTransceiver('audio').sender must inherit property "getCapabilities(DOMString)" with the proper type
+PASS RTCRtpSender interface: calling getCapabilities(DOMString) on new RTCPeerConnection().addTransceiver('audio').sender with too few arguments must throw TypeError
+PASS RTCRtpSender interface: new RTCPeerConnection().addTransceiver('audio').sender must inherit property "setParameters(RTCRtpSendParameters)" with the proper type
+PASS RTCRtpSender interface: calling setParameters(RTCRtpSendParameters) on new RTCPeerConnection().addTransceiver('audio').sender with too few arguments must throw TypeError
+PASS RTCRtpSender interface: new RTCPeerConnection().addTransceiver('audio').sender must inherit property "getParameters()" with the proper type
+PASS RTCRtpSender interface: new RTCPeerConnection().addTransceiver('audio').sender must inherit property "replaceTrack(MediaStreamTrack)" with the proper type
+PASS RTCRtpSender interface: calling replaceTrack(MediaStreamTrack) on new RTCPeerConnection().addTransceiver('audio').sender with too few arguments must throw TypeError
+FAIL RTCRtpSender interface: new RTCPeerConnection().addTransceiver('audio').sender must inherit property "setStreams(MediaStream)" with the proper type assert_inherits: property "setStreams" not found in prototype chain
+FAIL RTCRtpSender interface: calling setStreams(MediaStream) on new RTCPeerConnection().addTransceiver('audio').sender with too few arguments must throw TypeError assert_inherits: property "setStreams" not found in prototype chain
+PASS RTCRtpSender interface: new RTCPeerConnection().addTransceiver('audio').sender must inherit property "getStats()" with the proper type
+PASS RTCRtpSender interface: new RTCPeerConnection().addTransceiver('audio').sender must inherit property "dtmf" with the proper type
+PASS RTCRtpReceiver interface: existence and properties of interface object
+PASS RTCRtpReceiver interface object length
+PASS RTCRtpReceiver interface object name
+PASS RTCRtpReceiver interface: existence and properties of interface prototype object
+PASS RTCRtpReceiver interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCRtpReceiver interface: existence and properties of interface prototype object's @@unscopables property
+PASS RTCRtpReceiver interface: attribute track
+FAIL RTCRtpReceiver interface: attribute transport assert_true: The prototype object must have a property "transport" expected true got false
+FAIL RTCRtpReceiver interface: attribute rtcpTransport assert_true: The prototype object must have a property "rtcpTransport" expected true got false
+PASS RTCRtpReceiver interface: operation getCapabilities(DOMString)
+FAIL RTCRtpReceiver interface: operation getParameters() assert_own_property: interface prototype object missing non-static operation expected property "getParameters" missing
+PASS RTCRtpReceiver interface: operation getContributingSources()
+FAIL RTCRtpReceiver interface: operation getSynchronizationSources() assert_own_property: interface prototype object missing non-static operation expected property "getSynchronizationSources" missing
+PASS RTCRtpReceiver interface: operation getStats()
+PASS RTCRtpReceiver must be primary interface of new RTCPeerConnection().addTransceiver('audio').receiver
+PASS Stringification of new RTCPeerConnection().addTransceiver('audio').receiver
+PASS RTCRtpReceiver interface: new RTCPeerConnection().addTransceiver('audio').receiver must inherit property "track" with the proper type
+FAIL RTCRtpReceiver interface: new RTCPeerConnection().addTransceiver('audio').receiver must inherit property "transport" with the proper type assert_inherits: property "transport" not found in prototype chain
+FAIL RTCRtpReceiver interface: new RTCPeerConnection().addTransceiver('audio').receiver must inherit property "rtcpTransport" with the proper type assert_inherits: property "rtcpTransport" not found in prototype chain
+PASS RTCRtpReceiver interface: new RTCPeerConnection().addTransceiver('audio').receiver must inherit property "getCapabilities(DOMString)" with the proper type
+PASS RTCRtpReceiver interface: calling getCapabilities(DOMString) on new RTCPeerConnection().addTransceiver('audio').receiver with too few arguments must throw TypeError
+FAIL RTCRtpReceiver interface: new RTCPeerConnection().addTransceiver('audio').receiver must inherit property "getParameters()" with the proper type assert_inherits: property "getParameters" not found in prototype chain
+PASS RTCRtpReceiver interface: new RTCPeerConnection().addTransceiver('audio').receiver must inherit property "getContributingSources()" with the proper type
+FAIL RTCRtpReceiver interface: new RTCPeerConnection().addTransceiver('audio').receiver must inherit property "getSynchronizationSources()" with the proper type assert_inherits: property "getSynchronizationSources" not found in prototype chain
+PASS RTCRtpReceiver interface: new RTCPeerConnection().addTransceiver('audio').receiver must inherit property "getStats()" with the proper type
+PASS RTCRtpTransceiver interface: existence and properties of interface object
+PASS RTCRtpTransceiver interface object length
+PASS RTCRtpTransceiver interface object name
+PASS RTCRtpTransceiver interface: existence and properties of interface prototype object
+PASS RTCRtpTransceiver interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCRtpTransceiver interface: existence and properties of interface prototype object's @@unscopables property
+PASS RTCRtpTransceiver interface: attribute mid
+PASS RTCRtpTransceiver interface: attribute sender
+PASS RTCRtpTransceiver interface: attribute receiver
+PASS RTCRtpTransceiver interface: attribute stopped
+PASS RTCRtpTransceiver interface: attribute direction
+PASS RTCRtpTransceiver interface: attribute currentDirection
+FAIL RTCRtpTransceiver interface: operation stop() assert_own_property: interface prototype object missing non-static operation expected property "stop" missing
+FAIL RTCRtpTransceiver interface: operation setCodecPreferences([object Object]) assert_own_property: interface prototype object missing non-static operation expected property "setCodecPreferences" missing
+PASS RTCRtpTransceiver must be primary interface of new RTCPeerConnection().addTransceiver('audio')
+PASS Stringification of new RTCPeerConnection().addTransceiver('audio')
+PASS RTCRtpTransceiver interface: new RTCPeerConnection().addTransceiver('audio') must inherit property "mid" with the proper type
+PASS RTCRtpTransceiver interface: new RTCPeerConnection().addTransceiver('audio') must inherit property "sender" with the proper type
+PASS RTCRtpTransceiver interface: new RTCPeerConnection().addTransceiver('audio') must inherit property "receiver" with the proper type
+PASS RTCRtpTransceiver interface: new RTCPeerConnection().addTransceiver('audio') must inherit property "stopped" with the proper type
+PASS RTCRtpTransceiver interface: new RTCPeerConnection().addTransceiver('audio') must inherit property "direction" with the proper type
+PASS RTCRtpTransceiver interface: new RTCPeerConnection().addTransceiver('audio') must inherit property "currentDirection" with the proper type
+FAIL RTCRtpTransceiver interface: new RTCPeerConnection().addTransceiver('audio') must inherit property "stop()" with the proper type assert_inherits: property "stop" not found in prototype chain
+FAIL RTCRtpTransceiver interface: new RTCPeerConnection().addTransceiver('audio') must inherit property "setCodecPreferences([object Object])" with the proper type assert_inherits: property "setCodecPreferences" not found in prototype chain
+FAIL RTCRtpTransceiver interface: calling setCodecPreferences([object Object]) on new RTCPeerConnection().addTransceiver('audio') with too few arguments must throw TypeError assert_inherits: property "setCodecPreferences" not found in prototype chain
+FAIL RTCDtlsTransport interface: existence and properties of interface object assert_own_property: self does not have own property "RTCDtlsTransport" expected property "RTCDtlsTransport" missing
+FAIL RTCDtlsTransport interface object length assert_own_property: self does not have own property "RTCDtlsTransport" expected property "RTCDtlsTransport" missing
+FAIL RTCDtlsTransport interface object name assert_own_property: self does not have own property "RTCDtlsTransport" expected property "RTCDtlsTransport" missing
+FAIL RTCDtlsTransport interface: existence and properties of interface prototype object assert_own_property: self does not have own property "RTCDtlsTransport" expected property "RTCDtlsTransport" missing
+FAIL RTCDtlsTransport interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "RTCDtlsTransport" expected property "RTCDtlsTransport" missing
+FAIL RTCDtlsTransport interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "RTCDtlsTransport" expected property "RTCDtlsTransport" missing
+FAIL RTCDtlsTransport interface: attribute transport assert_own_property: self does not have own property "RTCDtlsTransport" expected property "RTCDtlsTransport" missing
+FAIL RTCDtlsTransport interface: attribute state assert_own_property: self does not have own property "RTCDtlsTransport" expected property "RTCDtlsTransport" missing
+FAIL RTCDtlsTransport interface: operation getRemoteCertificates() assert_own_property: self does not have own property "RTCDtlsTransport" expected property "RTCDtlsTransport" missing
+FAIL RTCDtlsTransport interface: attribute onstatechange assert_own_property: self does not have own property "RTCDtlsTransport" expected property "RTCDtlsTransport" missing
+FAIL RTCDtlsTransport interface: attribute onerror assert_own_property: self does not have own property "RTCDtlsTransport" expected property "RTCDtlsTransport" missing
+FAIL RTCDtlsTransport must be primary interface of idlTestObjects.dtlsTransport assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL Stringification of idlTestObjects.dtlsTransport assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCDtlsTransport interface: idlTestObjects.dtlsTransport must inherit property "transport" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCDtlsTransport interface: idlTestObjects.dtlsTransport must inherit property "state" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCDtlsTransport interface: idlTestObjects.dtlsTransport must inherit property "getRemoteCertificates()" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCDtlsTransport interface: idlTestObjects.dtlsTransport must inherit property "onstatechange" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCDtlsTransport interface: idlTestObjects.dtlsTransport must inherit property "onerror" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: existence and properties of interface object assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface object length assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface object name assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: existence and properties of interface prototype object assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: attribute role assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: attribute component assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: attribute state assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: attribute gatheringState assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: operation getLocalCandidates() assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: operation getRemoteCandidates() assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: operation getSelectedCandidatePair() assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: operation getLocalParameters() assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: operation getRemoteParameters() assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: attribute onstatechange assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: attribute ongatheringstatechange assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport interface: attribute onselectedcandidatepairchange assert_own_property: self does not have own property "RTCIceTransport" expected property "RTCIceTransport" missing
+FAIL RTCIceTransport must be primary interface of idlTestObjects.iceTransport assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL Stringification of idlTestObjects.iceTransport assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "role" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "component" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "state" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "gatheringState" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "getLocalCandidates()" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "getRemoteCandidates()" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "getSelectedCandidatePair()" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "getLocalParameters()" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "getRemoteParameters()" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "onstatechange" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "ongatheringstatechange" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "onselectedcandidatepairchange" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+PASS RTCTrackEvent interface: existence and properties of interface object
+PASS RTCTrackEvent interface object length
+PASS RTCTrackEvent interface object name
+PASS RTCTrackEvent interface: existence and properties of interface prototype object
+PASS RTCTrackEvent interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCTrackEvent interface: existence and properties of interface prototype object's @@unscopables property
+PASS RTCTrackEvent interface: attribute receiver
+PASS RTCTrackEvent interface: attribute track
+PASS RTCTrackEvent interface: attribute streams
+PASS RTCTrackEvent interface: attribute transceiver
+PASS RTCTrackEvent must be primary interface of initTrackEvent()
+PASS Stringification of initTrackEvent()
+PASS RTCTrackEvent interface: initTrackEvent() must inherit property "receiver" with the proper type
+PASS RTCTrackEvent interface: initTrackEvent() must inherit property "track" with the proper type
+PASS RTCTrackEvent interface: initTrackEvent() must inherit property "streams" with the proper type
+PASS RTCTrackEvent interface: initTrackEvent() must inherit property "transceiver" with the proper type
+FAIL RTCSctpTransport interface: existence and properties of interface object assert_own_property: self does not have own property "RTCSctpTransport" expected property "RTCSctpTransport" missing
+FAIL RTCSctpTransport interface object length assert_own_property: self does not have own property "RTCSctpTransport" expected property "RTCSctpTransport" missing
+FAIL RTCSctpTransport interface object name assert_own_property: self does not have own property "RTCSctpTransport" expected property "RTCSctpTransport" missing
+FAIL RTCSctpTransport interface: existence and properties of interface prototype object assert_own_property: self does not have own property "RTCSctpTransport" expected property "RTCSctpTransport" missing
+FAIL RTCSctpTransport interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "RTCSctpTransport" expected property "RTCSctpTransport" missing
+FAIL RTCSctpTransport interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "RTCSctpTransport" expected property "RTCSctpTransport" missing
+FAIL RTCSctpTransport interface: attribute transport assert_own_property: self does not have own property "RTCSctpTransport" expected property "RTCSctpTransport" missing
+FAIL RTCSctpTransport interface: attribute state assert_own_property: self does not have own property "RTCSctpTransport" expected property "RTCSctpTransport" missing
+FAIL RTCSctpTransport interface: attribute maxMessageSize assert_own_property: self does not have own property "RTCSctpTransport" expected property "RTCSctpTransport" missing
+FAIL RTCSctpTransport interface: attribute maxChannels assert_own_property: self does not have own property "RTCSctpTransport" expected property "RTCSctpTransport" missing
+FAIL RTCSctpTransport interface: attribute onstatechange assert_own_property: self does not have own property "RTCSctpTransport" expected property "RTCSctpTransport" missing
+FAIL RTCSctpTransport must be primary interface of idlTestObjects.sctpTransport assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL Stringification of idlTestObjects.sctpTransport assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCSctpTransport interface: idlTestObjects.sctpTransport must inherit property "transport" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCSctpTransport interface: idlTestObjects.sctpTransport must inherit property "state" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCSctpTransport interface: idlTestObjects.sctpTransport must inherit property "maxMessageSize" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCSctpTransport interface: idlTestObjects.sctpTransport must inherit property "maxChannels" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL RTCSctpTransport interface: idlTestObjects.sctpTransport must inherit property "onstatechange" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+PASS RTCDataChannel interface: existence and properties of interface object
+PASS RTCDataChannel interface object length
+PASS RTCDataChannel interface object name
+PASS RTCDataChannel interface: existence and properties of interface prototype object
+PASS RTCDataChannel interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCDataChannel interface: existence and properties of interface prototype object's @@unscopables property
+PASS RTCDataChannel interface: attribute label
+PASS RTCDataChannel interface: attribute ordered
+FAIL RTCDataChannel interface: attribute maxPacketLifeTime assert_true: The prototype object must have a property "maxPacketLifeTime" expected true got false
+PASS RTCDataChannel interface: attribute maxRetransmits
+PASS RTCDataChannel interface: attribute protocol
+PASS RTCDataChannel interface: attribute negotiated
+PASS RTCDataChannel interface: attribute id
+FAIL RTCDataChannel interface: attribute priority assert_true: The prototype object must have a property "priority" expected true got false
+PASS RTCDataChannel interface: attribute readyState
+PASS RTCDataChannel interface: attribute bufferedAmount
+PASS RTCDataChannel interface: attribute bufferedAmountLowThreshold
+PASS RTCDataChannel interface: attribute onopen
+PASS RTCDataChannel interface: attribute onbufferedamountlow
+PASS RTCDataChannel interface: attribute onerror
+PASS RTCDataChannel interface: attribute onclose
+PASS RTCDataChannel interface: operation close()
+PASS RTCDataChannel interface: attribute onmessage
+PASS RTCDataChannel interface: attribute binaryType
+PASS RTCDataChannel interface: operation send(USVString)
+PASS RTCDataChannel interface: operation send(Blob)
+PASS RTCDataChannel interface: operation send(ArrayBuffer)
+PASS RTCDataChannel interface: operation send(ArrayBufferView)
+PASS RTCDataChannel must be primary interface of new RTCPeerConnection().createDataChannel('')
+PASS Stringification of new RTCPeerConnection().createDataChannel('')
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "label" with the proper type
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "ordered" with the proper type
+FAIL RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "maxPacketLifeTime" with the proper type assert_inherits: property "maxPacketLifeTime" not found in prototype chain
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "maxRetransmits" with the proper type
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "protocol" with the proper type
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "negotiated" with the proper type
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "id" with the proper type
+FAIL RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "priority" with the proper type assert_inherits: property "priority" not found in prototype chain
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "readyState" with the proper type
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "bufferedAmount" with the proper type
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "bufferedAmountLowThreshold" with the proper type
+FAIL RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "onopen" with the proper type Unrecognized type EventHandler
+FAIL RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "onbufferedamountlow" with the proper type Unrecognized type EventHandler
+FAIL RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "onerror" with the proper type Unrecognized type EventHandler
+FAIL RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "onclose" with the proper type Unrecognized type EventHandler
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "close()" with the proper type
+FAIL RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "onmessage" with the proper type Unrecognized type EventHandler
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "binaryType" with the proper type
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "send(USVString)" with the proper type
+PASS RTCDataChannel interface: calling send(USVString) on new RTCPeerConnection().createDataChannel('') with too few arguments must throw TypeError
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "send(Blob)" with the proper type
+PASS RTCDataChannel interface: calling send(Blob) on new RTCPeerConnection().createDataChannel('') with too few arguments must throw TypeError
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "send(ArrayBuffer)" with the proper type
+PASS RTCDataChannel interface: calling send(ArrayBuffer) on new RTCPeerConnection().createDataChannel('') with too few arguments must throw TypeError
+PASS RTCDataChannel interface: new RTCPeerConnection().createDataChannel('') must inherit property "send(ArrayBufferView)" with the proper type
+PASS RTCDataChannel interface: calling send(ArrayBufferView) on new RTCPeerConnection().createDataChannel('') with too few arguments must throw TypeError
+PASS RTCDataChannelEvent interface: existence and properties of interface object
+PASS RTCDataChannelEvent interface object length
+PASS RTCDataChannelEvent interface object name
+PASS RTCDataChannelEvent interface: existence and properties of interface prototype object
+PASS RTCDataChannelEvent interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCDataChannelEvent interface: existence and properties of interface prototype object's @@unscopables property
+PASS RTCDataChannelEvent interface: attribute channel
+PASS RTCDataChannelEvent must be primary interface of new RTCDataChannelEvent('channel', {
+          channel: new RTCPeerConnection().createDataChannel('')
+        })
+PASS Stringification of new RTCDataChannelEvent('channel', {
+          channel: new RTCPeerConnection().createDataChannel('')
+        })
+PASS RTCDataChannelEvent interface: new RTCDataChannelEvent('channel', {
+          channel: new RTCPeerConnection().createDataChannel('')
+        }) must inherit property "channel" with the proper type
+PASS RTCDTMFSender interface: existence and properties of interface object
+PASS RTCDTMFSender interface object length
+PASS RTCDTMFSender interface object name
+PASS RTCDTMFSender interface: existence and properties of interface prototype object
+PASS RTCDTMFSender interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCDTMFSender interface: existence and properties of interface prototype object's @@unscopables property
+PASS RTCDTMFSender interface: operation insertDTMF(DOMString, unsigned long, unsigned long)
+PASS RTCDTMFSender interface: attribute ontonechange
+PASS RTCDTMFSender interface: attribute canInsertDTMF
+PASS RTCDTMFSender interface: attribute toneBuffer
+PASS RTCDTMFToneChangeEvent interface: existence and properties of interface object
+PASS RTCDTMFToneChangeEvent interface object length
+PASS RTCDTMFToneChangeEvent interface object name
+PASS RTCDTMFToneChangeEvent interface: existence and properties of interface prototype object
+PASS RTCDTMFToneChangeEvent interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCDTMFToneChangeEvent interface: existence and properties of interface prototype object's @@unscopables property
+PASS RTCDTMFToneChangeEvent interface: attribute tone
+PASS RTCStatsReport interface: existence and properties of interface object
+PASS RTCStatsReport interface object length
+PASS RTCStatsReport interface object name
+PASS RTCStatsReport interface: existence and properties of interface prototype object
+PASS RTCStatsReport interface: existence and properties of interface prototype object's "constructor" property
+PASS RTCStatsReport interface: existence and properties of interface prototype object's @@unscopables property
+FAIL RTCStatsEvent interface: existence and properties of interface object assert_own_property: self does not have own property "RTCStatsEvent" expected property "RTCStatsEvent" missing
+FAIL RTCStatsEvent interface object length assert_own_property: self does not have own property "RTCStatsEvent" expected property "RTCStatsEvent" missing
+FAIL RTCStatsEvent interface object name assert_own_property: self does not have own property "RTCStatsEvent" expected property "RTCStatsEvent" missing
+FAIL RTCStatsEvent interface: existence and properties of interface prototype object assert_own_property: self does not have own property "RTCStatsEvent" expected property "RTCStatsEvent" missing
+FAIL RTCStatsEvent interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "RTCStatsEvent" expected property "RTCStatsEvent" missing
+FAIL RTCStatsEvent interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "RTCStatsEvent" expected property "RTCStatsEvent" missing
+FAIL RTCStatsEvent interface: attribute report assert_own_property: self does not have own property "RTCStatsEvent" expected property "RTCStatsEvent" missing
+FAIL RTCErrorEvent interface: existence and properties of interface object assert_own_property: self does not have own property "RTCErrorEvent" expected property "RTCErrorEvent" missing
+FAIL RTCErrorEvent interface object length assert_own_property: self does not have own property "RTCErrorEvent" expected property "RTCErrorEvent" missing
+FAIL RTCErrorEvent interface object name assert_own_property: self does not have own property "RTCErrorEvent" expected property "RTCErrorEvent" missing
+FAIL RTCErrorEvent interface: existence and properties of interface prototype object assert_own_property: self does not have own property "RTCErrorEvent" expected property "RTCErrorEvent" missing
+FAIL RTCErrorEvent interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "RTCErrorEvent" expected property "RTCErrorEvent" missing
+FAIL RTCErrorEvent interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "RTCErrorEvent" expected property "RTCErrorEvent" missing
+FAIL RTCErrorEvent interface: attribute error assert_own_property: self does not have own property "RTCErrorEvent" expected property "RTCErrorEvent" missing
+FAIL RTCErrorEvent must be primary interface of new RTCErrorEvent('error') assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: RTCErrorEvent is not defined"
+FAIL Stringification of new RTCErrorEvent('error') assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: RTCErrorEvent is not defined"
+FAIL RTCErrorEvent interface: new RTCErrorEvent('error') must inherit property "error" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: RTCErrorEvent is not defined"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/public/platform/web_runtime_features.h b/third_party/blink/public/platform/web_runtime_features.h
index 18c721d..b06f7c52 100644
--- a/third_party/blink/public/platform/web_runtime_features.h
+++ b/third_party/blink/public/platform/web_runtime_features.h
@@ -166,7 +166,6 @@
   BLINK_PLATFORM_EXPORT static void EnableWebXRGamepadSupport(bool);
   BLINK_PLATFORM_EXPORT static void EnableXSLT(bool);
   BLINK_PLATFORM_EXPORT static void ForceOverlayFullscreenVideo(bool);
-  BLINK_PLATFORM_EXPORT static void EnableAutoplayMutedVideos(bool);
   BLINK_PLATFORM_EXPORT static void EnableTimerThrottlingForBackgroundTabs(
       bool);
   BLINK_PLATFORM_EXPORT static void EnableTimerThrottlingForHiddenFrames(bool);
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc
index 4ad61b99..c33dbdc 100644
--- a/third_party/blink/renderer/core/frame/local_frame.cc
+++ b/third_party/blink/renderer/core/frame/local_frame.cc
@@ -88,6 +88,7 @@
 #include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h"
 #include "third_party/blink/renderer/core/paint/object_painter.h"
 #include "third_party/blink/renderer/core/probe/core_probes.h"
+#include "third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.h"
 #include "third_party/blink/renderer/core/svg/svg_document_extensions.h"
 #include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
@@ -300,6 +301,7 @@
   visitor->Trace(input_method_controller_);
   visitor->Trace(text_suggestion_controller_);
   visitor->Trace(computed_node_mapping_);
+  visitor->Trace(smooth_scroll_sequencer_);
   Frame::Trace(visitor);
   Supplementable<LocalFrame>::Trace(visitor);
 }
@@ -1452,6 +1454,14 @@
           std::move(request), GetDocument());
 }
 
+SmoothScrollSequencer& LocalFrame::GetSmoothScrollSequencer() {
+  if (!IsLocalRoot())
+    return LocalFrameRoot().GetSmoothScrollSequencer();
+  if (!smooth_scroll_sequencer_)
+    smooth_scroll_sequencer_ = new SmoothScrollSequencer();
+  return *smooth_scroll_sequencer_;
+}
+
 ukm::UkmRecorder* LocalFrame::GetUkmRecorder() {
   Document* document = GetDocument();
   if (!document)
diff --git a/third_party/blink/renderer/core/frame/local_frame.h b/third_party/blink/renderer/core/frame/local_frame.h
index 45a580b..034267c1 100644
--- a/third_party/blink/renderer/core/frame/local_frame.h
+++ b/third_party/blink/renderer/core/frame/local_frame.h
@@ -96,6 +96,7 @@
 class PluginData;
 class ScriptController;
 class SharedBuffer;
+class SmoothScrollSequencer;
 class SpellChecker;
 class TextSuggestionController;
 class WebComputedAXTree;
@@ -366,6 +367,8 @@
   void BindPreviewsResourceLoadingHintsRequest(
       blink::mojom::blink::PreviewsResourceLoadingHintsReceiverRequest request);
 
+  SmoothScrollSequencer& GetSmoothScrollSequencer();
+
  private:
   friend class FrameNavigationDisabler;
 
@@ -449,6 +452,9 @@
   Member<AdTracker> ad_tracker_;
   Member<IdlenessDetector> idleness_detector_;
   Member<InspectorTraceEvents> inspector_trace_events_;
+  // SmoothScrollSequencer is only populated for local roots; all local frames
+  // use the instance owned by their local root.
+  Member<SmoothScrollSequencer> smooth_scroll_sequencer_;
 
   InterfaceRegistry* const interface_registry_;
 
diff --git a/third_party/blink/renderer/core/html/media/autoplay_policy.cc b/third_party/blink/renderer/core/html/media/autoplay_policy.cc
index 70d1323..33afa8e 100644
--- a/third_party/blink/renderer/core/html/media/autoplay_policy.cc
+++ b/third_party/blink/renderer/core/html/media/autoplay_policy.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/core/html/media/autoplay_policy.h"
 
+#include "build/build_config.h"
 #include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom-blink.h"
 #include "third_party/blink/public/platform/autoplay.mojom-blink.h"
 #include "third_party/blink/public/platform/web_media_player.h"
@@ -134,6 +135,13 @@
          mojom::blink::kAutoplayFlagForceAllow;
 }
 
+// static
+bool AutoplayPolicy::DocumentShouldAutoplayMutedVideos(
+    const Document& document) {
+  return GetAutoplayPolicyForDocument(document) !=
+         AutoplayPolicy::Type::kNoUserGestureRequired;
+}
+
 AutoplayPolicy::AutoplayPolicy(HTMLMediaElement* element)
     : locked_pending_user_gesture_(false),
       locked_pending_user_gesture_if_cross_origin_experiment_enabled_(true),
@@ -169,7 +177,7 @@
 
 bool AutoplayPolicy::IsEligibleForAutoplayMuted() const {
   return element_->IsHTMLVideoElement() && element_->muted() &&
-         RuntimeEnabledFeatures::AutoplayMutedVideosEnabled();
+         DocumentShouldAutoplayMutedVideos(element_->GetDocument());
 }
 
 void AutoplayPolicy::StartAutoplayMutedWhenVisible() {
@@ -288,7 +296,7 @@
 
 bool AutoplayPolicy::IsOrWillBeAutoplayingMutedInternal(bool muted) const {
   if (!element_->IsHTMLVideoElement() ||
-      !RuntimeEnabledFeatures::AutoplayMutedVideosEnabled()) {
+      !DocumentShouldAutoplayMutedVideos(element_->GetDocument())) {
     return false;
   }
 
@@ -349,7 +357,7 @@
   // - Preload was not disabled (low end devices);
   // - Autoplay is enabled in settings;
   if (element_->IsHTMLVideoElement() && element_->muted() &&
-      RuntimeEnabledFeatures::AutoplayMutedVideosEnabled() &&
+      DocumentShouldAutoplayMutedVideos(element_->GetDocument()) &&
       !(element_->GetDocument().GetSettings() &&
         GetNetworkStateNotifier().SaveDataEnabled() &&
         !element_->GetDocument()
diff --git a/third_party/blink/renderer/core/html/media/autoplay_policy.h b/third_party/blink/renderer/core/html/media/autoplay_policy.h
index c3ece86..d6c6026 100644
--- a/third_party/blink/renderer/core/html/media/autoplay_policy.h
+++ b/third_party/blink/renderer/core/html/media/autoplay_policy.h
@@ -49,6 +49,9 @@
   // Returns true if the given |document| should force allow autoplay.
   static bool DocumentHasForceAllowFlag(const Document&);
 
+  // Returns true if the given |document| should autoplay muted videos.
+  static bool DocumentShouldAutoplayMutedVideos(const Document&);
+
   explicit AutoplayPolicy(HTMLMediaElement*);
 
   void VideoWillBeDrawnToCanvas() const;
diff --git a/third_party/blink/renderer/core/html/media/autoplay_uma_helper.cc b/third_party/blink/renderer/core/html/media/autoplay_uma_helper.cc
index 5ba7fbd..c3e033ed 100644
--- a/third_party/blink/renderer/core/html/media/autoplay_uma_helper.cc
+++ b/third_party/blink/renderer/core/html/media/autoplay_uma_helper.cc
@@ -168,7 +168,8 @@
 
   // Record if it will be blocked by Data Saver or Autoplay setting.
   if (element_->IsHTMLVideoElement() && element_->muted() &&
-      RuntimeEnabledFeatures::AutoplayMutedVideosEnabled()) {
+      AutoplayPolicy::DocumentShouldAutoplayMutedVideos(
+          element_->GetDocument())) {
     bool data_saver_enabled_for_autoplay =
         GetNetworkStateNotifier().SaveDataEnabled() &&
         element_->GetDocument().GetSettings() &&
diff --git a/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc b/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc
index 2e07a2a0..4843946ec 100644
--- a/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc
@@ -23,22 +23,9 @@
 #include "third_party/blink/renderer/platform/wtf/time.h"
 
 namespace blink {
-
 using protocol::Maybe;
 using protocol::Response;
 
-namespace EmulationAgentState {
-static const char kVirtualTimeBudget[] = "virtualTimeBudget";
-static const char kVirtualTimeBudgetInitalOffset[] =
-    "virtualTimeBudgetInitalOffset";
-static const char kInitialVirtualTime[] = "initialVirtualTime";
-static const char kVirtualTimeOffset[] = "virtualTimeOffset";
-static const char kVirtualTimePolicy[] = "virtualTimePolicy";
-static const char kVirtualTimeTaskStarvationCount[] =
-    "virtualTimeTaskStarvationCount";
-static const char kWaitForNavigation[] = "waitForNavigation";
-}  // namespace EmulationAgentState
-
 InspectorEmulationAgent::InspectorEmulationAgent(
     WebLocalFrameImpl* web_local_frame_impl)
     : web_local_frame_(web_local_frame_impl),
@@ -54,7 +41,14 @@
                                    /*default_value=*/WTF::String()),
       user_agent_override_(&agent_state_, /*default_value=*/WTF::String()),
       accept_language_override_(&agent_state_,
-                                /*default_value=*/WTF::String()) {}
+                                /*default_value=*/WTF::String()),
+      virtual_time_budget_(&agent_state_, /*default_value*/ 0.0),
+      virtual_time_budget_initial_offset_(&agent_state_, /*default_value=*/0.0),
+      initial_virtual_time_(&agent_state_, /*default_value=*/0.0),
+      virtual_time_offset_(&agent_state_, /*default_value=*/0.0),
+      virtual_time_policy_(&agent_state_, /*default_value=*/WTF::String()),
+      virtual_time_task_starvation_count_(&agent_state_, /*default_value=*/0),
+      wait_for_navigation_(&agent_state_, /*default_value=*/false) {}
 
 InspectorEmulationAgent::~InspectorEmulationAgent() = default;
 
@@ -92,60 +86,40 @@
     }
   }
 
-  String virtual_time_policy;
-  if (state_->getString(EmulationAgentState::kVirtualTimePolicy,
-                        &virtual_time_policy)) {
-    // Tell the scheduler about the saved virtual time progress to ensure that
-    // virtual time monotonically advances despite the cross origin navigation.
-    // This should be done regardless of the virtual time mode.
-    double offset = 0;
-    state_->getDouble(EmulationAgentState::kVirtualTimeOffset, &offset);
-    web_local_frame_->View()->Scheduler()->SetInitialVirtualTimeOffset(
-        base::TimeDelta::FromMillisecondsD(offset));
+  if (virtual_time_policy_.Get().IsNull())
+    return;
+  // Tell the scheduler about the saved virtual time progress to ensure that
+  // virtual time monotonically advances despite the cross origin navigation.
+  // This should be done regardless of the virtual time mode.
+  web_local_frame_->View()->Scheduler()->SetInitialVirtualTimeOffset(
+      base::TimeDelta::FromMillisecondsD(virtual_time_offset_.Get()));
 
-    // Set initial time baselines for all modes.
-    double initial_virtual_time = 0;
-    bool has_initial_time = state_->getDouble(
-        EmulationAgentState::kInitialVirtualTime, &initial_virtual_time);
-    Maybe<double> initial_time = has_initial_time
-                                     ? Maybe<double>()
-                                     : Maybe<double>(initial_virtual_time);
+  // Preserve wait for navigation in all modes.
+  bool wait_for_navigation = wait_for_navigation_.Get();
 
-    // Preserve wait for navigation in all modes.
-    bool wait_for_navigation = false;
-    state_->getBoolean(EmulationAgentState::kWaitForNavigation,
-                       &wait_for_navigation);
+  // Reinstate the stored policy.
+  double virtual_time_ticks_base_ms;
 
-    // Reinstate the stored policy.
-    double virtual_time_ticks_base_ms;
-
-    // For Pause, do not pass budget or starvation count.
-    if (virtual_time_policy ==
-        protocol::Emulation::VirtualTimePolicyEnum::Pause) {
-      setVirtualTimePolicy(protocol::Emulation::VirtualTimePolicyEnum::Pause,
-                           Maybe<double>(), Maybe<int>(), wait_for_navigation,
-                           std::move(initial_time),
-                           &virtual_time_ticks_base_ms);
-      return;
-    }
-
-    // Calculate remaining budget for the advancing modes.
-    double budget = 0;
-    state_->getDouble(EmulationAgentState::kVirtualTimeBudget, &budget);
-    double inital_offset = 0;
-    state_->getDouble(EmulationAgentState::kVirtualTimeBudgetInitalOffset,
-                      &inital_offset);
-    double budget_remaining = budget + inital_offset - offset;
-    DCHECK_GE(budget_remaining, 0);
-
-    int starvation_count = 0;
-    state_->getInteger(EmulationAgentState::kVirtualTimeTaskStarvationCount,
-                       &starvation_count);
-
-    setVirtualTimePolicy(virtual_time_policy, budget_remaining,
-                         starvation_count, wait_for_navigation,
-                         std::move(initial_time), &virtual_time_ticks_base_ms);
+  // For Pause, do not pass budget or starvation count.
+  if (virtual_time_policy_.Get() ==
+      protocol::Emulation::VirtualTimePolicyEnum::Pause) {
+    setVirtualTimePolicy(protocol::Emulation::VirtualTimePolicyEnum::Pause,
+                         Maybe<double>(), Maybe<int>(), wait_for_navigation,
+                         initial_virtual_time_.Get(),
+                         &virtual_time_ticks_base_ms);
+    return;
   }
+
+  // Calculate remaining budget for the advancing modes.
+  double budget_remaining = virtual_time_budget_.Get() +
+                            virtual_time_budget_initial_offset_.Get() -
+                            virtual_time_offset_.Get();
+  DCHECK_GE(budget_remaining, 0);
+
+  setVirtualTimePolicy(virtual_time_policy_.Get(), budget_remaining,
+                       virtual_time_task_starvation_count_.Get(),
+                       wait_for_navigation, initial_virtual_time_.Get(),
+                       &virtual_time_ticks_base_ms);
 }
 
 Response InspectorEmulationAgent::disable() {
@@ -263,7 +237,7 @@
   Response response = AssertPage();
   if (!response.isSuccess())
     return response;
-  state_->setString(EmulationAgentState::kVirtualTimePolicy, policy);
+  virtual_time_policy_.Set(policy);
 
   PendingVirtualTimePolicy new_policy;
   new_policy.policy = PageScheduler::VirtualTimePolicy::kPause;
@@ -290,24 +264,21 @@
 
   if (virtual_time_budget_ms.isJust()) {
     new_policy.virtual_time_budget_ms = virtual_time_budget_ms.fromJust();
-    state_->setDouble(EmulationAgentState::kVirtualTimeBudget,
-                      *new_policy.virtual_time_budget_ms);
+    virtual_time_budget_.Set(*new_policy.virtual_time_budget_ms);
     // Record the current virtual time offset so Restore can compute how much
     // budget is left.
-    state_->setDouble(
-        EmulationAgentState::kVirtualTimeBudgetInitalOffset,
-        state_->doubleProperty(EmulationAgentState::kVirtualTimeOffset, 0.0));
+    virtual_time_budget_initial_offset_.Set(virtual_time_offset_.Get());
   } else {
-    state_->remove(EmulationAgentState::kVirtualTimeBudget);
+    virtual_time_budget_.Clear();
   }
 
   if (max_virtual_time_task_starvation_count.isJust()) {
     new_policy.max_virtual_time_task_starvation_count =
         max_virtual_time_task_starvation_count.fromJust();
-    state_->setDouble(EmulationAgentState::kVirtualTimeTaskStarvationCount,
-                      *new_policy.max_virtual_time_task_starvation_count);
+    virtual_time_task_starvation_count_.Set(
+        *new_policy.max_virtual_time_task_starvation_count);
   } else {
-    state_->remove(EmulationAgentState::kVirtualTimeTaskStarvationCount);
+    virtual_time_task_starvation_count_.Clear();
   }
 
   InnerEnable();
@@ -318,14 +289,13 @@
 
   // This needs to happen before we apply virtual time.
   if (initial_virtual_time.isJust()) {
-    state_->setDouble(EmulationAgentState::kInitialVirtualTime,
-                      initial_virtual_time.fromJust());
+    initial_virtual_time_.Set(initial_virtual_time.fromJust());
     web_local_frame_->View()->Scheduler()->SetInitialVirtualTime(
         base::Time::FromDoubleT(initial_virtual_time.fromJust()));
   }
 
   if (wait_for_navigation.fromMaybe(false)) {
-    state_->setBoolean(EmulationAgentState::kWaitForNavigation, true);
+    wait_for_navigation_.Set(true);
     pending_virtual_time_policy_ = std::move(new_policy);
   } else {
     ApplyVirtualTimePolicy(new_policy);
@@ -366,7 +336,7 @@
 
 void InspectorEmulationAgent::FrameStartedLoading(LocalFrame*) {
   if (pending_virtual_time_policy_) {
-    state_->setBoolean(EmulationAgentState::kWaitForNavigation, false);
+    wait_for_navigation_.Set(false);
     ApplyVirtualTimePolicy(*pending_virtual_time_policy_);
     pending_virtual_time_policy_ = base::nullopt;
   }
@@ -405,22 +375,19 @@
   DCHECK(web_local_frame_);
   web_local_frame_->View()->Scheduler()->SetVirtualTimePolicy(
       PageScheduler::VirtualTimePolicy::kPause);
-  state_->setString(EmulationAgentState::kVirtualTimePolicy,
-                    protocol::Emulation::VirtualTimePolicyEnum::Pause);
+  virtual_time_policy_.Set(protocol::Emulation::VirtualTimePolicyEnum::Pause);
   GetFrontend()->virtualTimeBudgetExpired();
 }
 
 void InspectorEmulationAgent::OnVirtualTimeAdvanced(
     WTF::TimeDelta virtual_time_offset) {
-  state_->setDouble(EmulationAgentState::kVirtualTimeOffset,
-                    virtual_time_offset.InMillisecondsF());
+  virtual_time_offset_.Set(virtual_time_offset.InMillisecondsF());
   GetFrontend()->virtualTimeAdvanced(virtual_time_offset.InMillisecondsF());
 }
 
 void InspectorEmulationAgent::OnVirtualTimePaused(
     WTF::TimeDelta virtual_time_offset) {
-  state_->setDouble(EmulationAgentState::kVirtualTimeOffset,
-                    virtual_time_offset.InMillisecondsF());
+  virtual_time_offset_.Set(virtual_time_offset.InMillisecondsF());
   GetFrontend()->virtualTimePaused(virtual_time_offset.InMillisecondsF());
 }
 
diff --git a/third_party/blink/renderer/core/inspector/inspector_emulation_agent.h b/third_party/blink/renderer/core/inspector/inspector_emulation_agent.h
index 0148ba8..256d89af 100644
--- a/third_party/blink/renderer/core/inspector/inspector_emulation_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_emulation_agent.h
@@ -132,6 +132,13 @@
   InspectorAgentState::String navigator_platform_override_;
   InspectorAgentState::String user_agent_override_;
   InspectorAgentState::String accept_language_override_;
+  InspectorAgentState::Double virtual_time_budget_;
+  InspectorAgentState::Double virtual_time_budget_initial_offset_;
+  InspectorAgentState::Double initial_virtual_time_;
+  InspectorAgentState::Double virtual_time_offset_;
+  InspectorAgentState::String virtual_time_policy_;
+  InspectorAgentState::Integer virtual_time_task_starvation_count_;
+  InspectorAgentState::Boolean wait_for_navigation_;
   DISALLOW_COPY_AND_ASSIGN(InspectorEmulationAgent);
 };
 
diff --git a/third_party/blink/renderer/core/inspector/inspector_network_agent.cc b/third_party/blink/renderer/core/inspector/inspector_network_agent.cc
index ef14ac0..3bda497 100644
--- a/third_party/blink/renderer/core/inspector/inspector_network_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_network_agent.cc
@@ -1322,15 +1322,15 @@
 
 Response InspectorNetworkAgent::disable() {
   DCHECK(!pending_request_);
-  enabled_.Set(false);
   instrumenting_agents_->removeInspectorNetworkAgent(this);
+  agent_state_.ClearAllFields();
   resources_data_->Clear();
   return Response::OK();
 }
 
 Response InspectorNetworkAgent::setExtraHTTPHeaders(
     std::unique_ptr<protocol::Network::Headers> headers) {
-  extra_request_headers_.ClearAll();
+  extra_request_headers_.Clear();
   std::unique_ptr<protocol::DictionaryValue> in = headers->toValue();
   for (size_t i = 0; i < in->size(); ++i) {
     const auto& entry = in->at(i);
@@ -1389,7 +1389,7 @@
 
 Response InspectorNetworkAgent::setBlockedURLs(
     std::unique_ptr<protocol::Array<String>> urls) {
-  blocked_urls_.ClearAll();
+  blocked_urls_.Clear();
   for (size_t i = 0; i < urls->length(); i++)
     blocked_urls_.Set(urls->get(i), true);
   return Response::OK();
diff --git a/third_party/blink/renderer/core/inspector/inspector_page_agent.cc b/third_party/blink/renderer/core/inspector/inspector_page_agent.cc
index f743e58..961914b 100644
--- a/third_party/blink/renderer/core/inspector/inspector_page_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_page_agent.cc
@@ -527,8 +527,7 @@
 }
 
 Response InspectorPageAgent::disable() {
-  enabled_.Set(false);
-  scripts_to_evaluate_on_load_.ClearAll();
+  agent_state_.ClearAllFields();
   script_to_evaluate_on_load_once_ = String();
   pending_script_to_evaluate_on_load_once_ = String();
   instrumenting_agents_->removeInspectorPageAgent(this);
diff --git a/third_party/blink/renderer/core/inspector/inspector_worker_agent.cc b/third_party/blink/renderer/core/inspector/inspector_worker_agent.cc
index 9cb95fcb..1bc6721b 100644
--- a/third_party/blink/renderer/core/inspector/inspector_worker_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_worker_agent.cc
@@ -63,7 +63,7 @@
   instrumenting_agents_->addInspectorWorkerAgent(this);
   for (const WTF::String& session_id : attached_session_ids_.Keys())
     GetFrontend()->detachedFromTarget(session_id);
-  attached_session_ids_.ClearAll();
+  attached_session_ids_.Clear();
   ConnectToAllProxies();
 }
 
@@ -72,9 +72,7 @@
     DisconnectFromAllProxies(false);
     instrumenting_agents_->removeInspectorWorkerAgent(this);
   }
-  auto_attach_.Clear();
-  wait_for_debugger_on_start_.Clear();
-  attached_session_ids_.ClearAll();
+  agent_state_.ClearAllFields();
   return Response::OK();
 }
 
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index 6be5a8e..2da14298 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -699,13 +699,13 @@
   if (!enclosing_box)
     return rect;
 
-  GetDocument().GetPage()->GetSmoothScrollSequencer()->AbortAnimations();
+  GetDocument().GetFrame()->GetSmoothScrollSequencer().AbortAnimations();
   WebScrollIntoViewParams new_params(params);
   new_params.is_for_scroll_sequence |=
       params.GetScrollType() == kProgrammaticScroll;
   LayoutRect new_location =
       enclosing_box->ScrollRectToVisibleRecursive(rect, new_params);
-  GetDocument().GetPage()->GetSmoothScrollSequencer()->RunQueuedAnimations();
+  GetDocument().GetFrame()->GetSmoothScrollSequencer().RunQueuedAnimations();
 
   return new_location;
 }
diff --git a/third_party/blink/renderer/core/page/page.cc b/third_party/blink/renderer/core/page/page.cc
index e870e98..2a285aab 100644
--- a/third_party/blink/renderer/core/page/page.cc
+++ b/third_party/blink/renderer/core/page/page.cc
@@ -217,13 +217,6 @@
   return *page_scale_constraints_set_;
 }
 
-SmoothScrollSequencer* Page::GetSmoothScrollSequencer() {
-  if (!smooth_scroll_sequencer_)
-    smooth_scroll_sequencer_ = new SmoothScrollSequencer();
-
-  return smooth_scroll_sequencer_.Get();
-}
-
 const PageScaleConstraintsSet& Page::GetPageScaleConstraintsSet() const {
   return *page_scale_constraints_set_;
 }
@@ -726,7 +719,6 @@
   visitor->Trace(context_menu_controller_);
   visitor->Trace(pointer_lock_controller_);
   visitor->Trace(scrolling_coordinator_);
-  visitor->Trace(smooth_scroll_sequencer_);
   visitor->Trace(browser_controls_);
   visitor->Trace(console_message_storage_);
   visitor->Trace(global_root_scroller_controller_);
diff --git a/third_party/blink/renderer/core/page/page.h b/third_party/blink/renderer/core/page/page.h
index 41f555af..ce8c409 100644
--- a/third_party/blink/renderer/core/page/page.h
+++ b/third_party/blink/renderer/core/page/page.h
@@ -69,7 +69,6 @@
 class ScopedPagePauser;
 class ScrollingCoordinator;
 class ScrollbarTheme;
-class SmoothScrollSequencer;
 class Settings;
 class ConsoleMessageStorage;
 class TopDocumentRootScrollerController;
@@ -187,8 +186,6 @@
 
   ScrollingCoordinator* GetScrollingCoordinator();
 
-  SmoothScrollSequencer* GetSmoothScrollSequencer();
-
   DOMRectList* NonFastScrollableRectsForTesting(const LocalFrame*);
 
   Settings& GetSettings() const { return *settings_; }
@@ -357,7 +354,6 @@
   const std::unique_ptr<PageScaleConstraintsSet> page_scale_constraints_set_;
   const Member<PointerLockController> pointer_lock_controller_;
   Member<ScrollingCoordinator> scrolling_coordinator_;
-  Member<SmoothScrollSequencer> smooth_scroll_sequencer_;
   const Member<BrowserControls> browser_controls_;
   const Member<ConsoleMessageStorage> console_message_storage_;
   const Member<TopDocumentRootScrollerController>
diff --git a/third_party/blink/renderer/core/paint/DEPS b/third_party/blink/renderer/core/paint/DEPS
index 6ea8ed75..bc21653 100644
--- a/third_party/blink/renderer/core/paint/DEPS
+++ b/third_party/blink/renderer/core/paint/DEPS
@@ -2,6 +2,8 @@
     # This goes away after slimming paint v2. For now it violates strict onion
     # soup guidelines.
     "+cc/layers/picture_layer.h",
+    # For DCHECK.
+    "+base/logging.h",
 ]
 
 specific_include_rules = {
diff --git a/third_party/blink/renderer/core/paint/paint_layer_painting_info.h b/third_party/blink/renderer/core/paint/paint_layer_painting_info.h
index b57ccd362..74a46e5 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_painting_info.h
+++ b/third_party/blink/renderer/core/paint/paint_layer_painting_info.h
@@ -45,10 +45,16 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_PAINT_LAYER_PAINTING_INFO_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_PAINT_LAYER_PAINTING_INFO_H_
 
+#include "base/logging.h"
 #include "third_party/blink/renderer/core/paint/paint_phase.h"
 #include "third_party/blink/renderer/platform/geometry/layout_rect.h"
 #include "third_party/blink/renderer/platform/wtf/allocator.h"
 
+#if DCHECK_IS_ON()
+#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+#endif
+
 namespace blink {
 
 class PaintLayer;
@@ -103,6 +109,62 @@
   const GlobalPaintFlags global_paint_flags_;
 };
 
+#if DCHECK_IS_ON()
+inline String PaintLayerFlagsToDebugString(PaintLayerFlags flags) {
+  if (flags == 0)
+    return "(kPaintLayerNoFlag)";
+
+  StringBuilder builder;
+  builder.Append("(");
+  bool need_separator = false;
+  auto append = [&builder, &need_separator](const char* str) {
+    if (need_separator)
+      builder.Append("|");
+    builder.Append(str);
+    need_separator = true;
+  };
+
+  if (flags & kPaintLayerPaintingCompositingAllPhases) {
+    append("kPaintLayerPaintingCompositingAllPhases");
+  } else {
+    if (flags & kPaintLayerPaintingCompositingBackgroundPhase)
+      append("kPaintLayerPaintingCompositingBackgroundPhase");
+    if (flags & kPaintLayerPaintingCompositingForegroundPhase)
+      append("kPaintLayerPaintingCompositingForegroundPhase");
+    if (flags & kPaintLayerPaintingCompositingMaskPhase)
+      append("kPaintLayerPaintingCompositingMaskPhase");
+    if (flags & kPaintLayerPaintingCompositingDecorationPhase)
+      append("kPaintLayerPaintingCompositingDecorationPhase");
+  }
+
+  if (flags & kPaintLayerHaveTransparency)
+    append("kPaintLayerHaveTransparency");
+  if (flags & kPaintLayerAppliedTransform)
+    append("kPaintLayerAppliedTransform");
+  if (flags & kPaintLayerUncachedClipRects)
+    append("kPaintLayerUncachedClipRects");
+  if (flags & kPaintLayerPaintingOverlayScrollbars)
+    append("kPaintLayerPaintingOverlayScrollbars");
+  if (flags & kPaintLayerPaintingCompositingScrollingPhase)
+    append("kPaintLayerPaintingCompositingScrollingPhase");
+  if (flags & kPaintLayerPaintingOverflowContents)
+    append("kPaintLayerPaintingOverflowContents");
+  if (flags & kPaintLayerPaintingSkipRootBackground)
+    append("kPaintLayerPaintingSkipRootBackground");
+  if (flags & kPaintLayerPaintingChildClippingMaskPhase)
+    append("kPaintLayerPaintingChildClippingMaskPhase");
+  if (flags & kPaintLayerPaintingAncestorClippingMaskPhase)
+    append("kPaintLayerPaintingAncestorClippingMaskPhase");
+  if (flags & kPaintLayerPaintingRenderingClipPathAsMask)
+    append("kPaintLayerPaintingRenderingClipPathAsMask");
+  if (flags & kPaintLayerPaintingRenderingResourceSubtree)
+    append("kPaintLayerPaintingRenderingResourceSubtree");
+
+  builder.Append(")");
+  return builder.ToString();
+}
+#endif  // DCHECK_IS_ON()
+
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_PAINT_LAYER_PAINTING_INFO_H_
diff --git a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
index 117b3e7..ca1d5c4 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
@@ -250,9 +250,8 @@
     const {
   if (HasBeenDisposed())
     return nullptr;
-  if (Page* page = GetLayoutBox()->GetFrame()->GetPage())
-    return page->GetSmoothScrollSequencer();
-  return nullptr;
+
+  return &GetLayoutBox()->GetFrame()->GetSmoothScrollSequencer();
 }
 
 GraphicsLayer* PaintLayerScrollableArea::LayerForScrolling() const {
diff --git a/third_party/blink/renderer/devtools/front_end/object_ui/ObjectPropertiesSection.js b/third_party/blink/renderer/devtools/front_end/object_ui/ObjectPropertiesSection.js
index 44f94ef..c1935ec 100644
--- a/third_party/blink/renderer/devtools/front_end/object_ui/ObjectPropertiesSection.js
+++ b/third_party/blink/renderer/devtools/front_end/object_ui/ObjectPropertiesSection.js
@@ -35,12 +35,14 @@
    * @param {?string=} emptyPlaceholder
    * @param {boolean=} ignoreHasOwnProperty
    * @param {!Array.<!SDK.RemoteObjectProperty>=} extraProperties
+   * @param {boolean=} showOverflow
    */
-  constructor(object, title, linkifier, emptyPlaceholder, ignoreHasOwnProperty, extraProperties) {
+  constructor(object, title, linkifier, emptyPlaceholder, ignoreHasOwnProperty, extraProperties, showOverflow) {
     super();
     this._object = object;
     this._editable = true;
-    this.hideOverflow();
+    if (!showOverflow)
+      this.hideOverflow();
     this.setFocusable(false);
     this._objectTreeElement = new ObjectUI.ObjectPropertiesSection.RootElement(
         object, linkifier, emptyPlaceholder, ignoreHasOwnProperty, extraProperties);
@@ -792,7 +794,7 @@
       this.expandedValueElement = this._createExpandedValueElement(this.property.value);
 
     this.listItemElement.removeChildren();
-    this._rowContainer = UI.html`<span>${this.nameElement}: ${this.valueElement}</span>`;
+    this._rowContainer = UI.html`<span class='name-and-value'>${this.nameElement}: ${this.valueElement}</span>`;
     this.listItemElement.appendChild(this._rowContainer);
   }
 
@@ -864,6 +866,7 @@
 
     const proxyElement =
         this._prompt.attachAndStartEditing(this._editableDiv, this._editingCommitted.bind(this, originalContent));
+    proxyElement.classList.add('property-prompt');
     this.listItemElement.getComponentSelection().selectAllChildren(this._editableDiv);
     proxyElement.addEventListener('keydown', this._promptKeyDown.bind(this, originalContent), false);
   }
diff --git a/third_party/blink/renderer/devtools/front_end/object_ui/objectPropertiesSection.css b/third_party/blink/renderer/devtools/front_end/object_ui/objectPropertiesSection.css
index 6b4df5f..fe10ae09 100644
--- a/third_party/blink/renderer/devtools/front_end/object_ui/objectPropertiesSection.css
+++ b/third_party/blink/renderer/devtools/front_end/object_ui/objectPropertiesSection.css
@@ -50,3 +50,17 @@
 .object-properties-section .editable-div {
     overflow: hidden;
 }
+
+.name-and-value {
+    overflow-x: hidden;
+    text-overflow: ellipsis;
+}
+
+.editing-sub-part .name-and-value {
+    overflow: visible;
+    display: inline-flex;
+}
+
+.property-prompt {
+    margin-left: 4px;
+}
diff --git a/third_party/blink/renderer/devtools/front_end/source_frame/JSONView.js b/third_party/blink/renderer/devtools/front_end/source_frame/JSONView.js
index e493566..7199d3e 100644
--- a/third_party/blink/renderer/devtools/front_end/source_frame/JSONView.js
+++ b/third_party/blink/renderer/devtools/front_end/source_frame/JSONView.js
@@ -166,7 +166,8 @@
 
     const obj = SDK.RemoteObject.fromLocalObject(this._parsedJSON.data);
     const title = this._parsedJSON.prefix + obj.description + this._parsedJSON.suffix;
-    this._treeOutline = new ObjectUI.ObjectPropertiesSection(obj, title);
+    this._treeOutline = new ObjectUI.ObjectPropertiesSection(
+        obj, title, undefined, undefined, undefined, undefined, true /* showOverflow */);
     this._treeOutline.enableContextMenu();
     this._treeOutline.setEditable(false);
     this._treeOutline.expand();
diff --git a/third_party/blink/renderer/devtools/front_end/workspace/FileManager.js b/third_party/blink/renderer/devtools/front_end/workspace/FileManager.js
index 16063e83..740a6ec 100644
--- a/third_party/blink/renderer/devtools/front_end/workspace/FileManager.js
+++ b/third_party/blink/renderer/devtools/front_end/workspace/FileManager.js
@@ -51,8 +51,9 @@
    */
   save(url, content, forceSaveAs) {
     // Remove this url from the saved URLs while it is being saved.
+    const result = new Promise(resolve => this._saveCallbacks.set(url, resolve));
     InspectorFrontendHost.save(url, content, forceSaveAs);
-    return new Promise(resolve => this._saveCallbacks.set(url, resolve));
+    return result;
   }
 
   /**
@@ -89,7 +90,7 @@
    * @param {string} url
    */
   close(url) {
-    // Currently a no-op.
+    InspectorFrontendHost.close(url);
   }
 
   /**
diff --git a/third_party/blink/renderer/modules/device_orientation/device_orientation_inspector_agent.cc b/third_party/blink/renderer/modules/device_orientation/device_orientation_inspector_agent.cc
index c9144c4..5e777aa 100644
--- a/third_party/blink/renderer/modules/device_orientation/device_orientation_inspector_agent.cc
+++ b/third_party/blink/renderer/modules/device_orientation/device_orientation_inspector_agent.cc
@@ -15,19 +15,16 @@
 
 using protocol::Response;
 
-namespace DeviceOrientationInspectorAgentState {
-static const char kAlpha[] = "alpha";
-static const char kBeta[] = "beta";
-static const char kGamma[] = "gamma";
-static const char kOverrideEnabled[] = "overrideEnabled";
-}
-
 DeviceOrientationInspectorAgent::~DeviceOrientationInspectorAgent() = default;
 
 DeviceOrientationInspectorAgent::DeviceOrientationInspectorAgent(
     InspectedFrames* inspected_frames)
     : inspected_frames_(inspected_frames),
-      sensor_agent_(new SensorInspectorAgent(inspected_frames->Root())) {}
+      sensor_agent_(new SensorInspectorAgent(inspected_frames->Root())),
+      enabled_(&agent_state_, /*default_value=*/false),
+      alpha_(&agent_state_, /*default_value=*/0.0),
+      beta_(&agent_state_, /*default_value=*/0.0),
+      gamma_(&agent_state_, /*default_value=*/0.0) {}
 
 void DeviceOrientationInspectorAgent::Trace(blink::Visitor* visitor) {
   visitor->Trace(inspected_frames_);
@@ -44,11 +41,10 @@
     double alpha,
     double beta,
     double gamma) {
-  state_->setBoolean(DeviceOrientationInspectorAgentState::kOverrideEnabled,
-                     true);
-  state_->setDouble(DeviceOrientationInspectorAgentState::kAlpha, alpha);
-  state_->setDouble(DeviceOrientationInspectorAgentState::kBeta, beta);
-  state_->setDouble(DeviceOrientationInspectorAgentState::kGamma, gamma);
+  enabled_.Set(true);
+  alpha_.Set(alpha);
+  beta_.Set(beta);
+  gamma_.Set(gamma);
   if (Controller()) {
     Controller()->SetOverride(
         DeviceOrientationData::Create(alpha, beta, gamma, false));
@@ -58,17 +54,11 @@
 }
 
 Response DeviceOrientationInspectorAgent::clearDeviceOrientationOverride() {
-  state_->setBoolean(DeviceOrientationInspectorAgentState::kOverrideEnabled,
-                     false);
-  if (Controller())
-    Controller()->ClearOverride();
-  sensor_agent_->Disable();
-  return Response::OK();
+  return disable();
 }
 
 Response DeviceOrientationInspectorAgent::disable() {
-  state_->setBoolean(DeviceOrientationInspectorAgentState::kOverrideEnabled,
-                     false);
+  agent_state_.ClearAllFields();
   if (Controller())
     Controller()->ClearOverride();
   sensor_agent_->Disable();
@@ -76,20 +66,12 @@
 }
 
 void DeviceOrientationInspectorAgent::Restore() {
-  if (!Controller())
+  if (!Controller() || !enabled_.Get())
     return;
-  if (state_->booleanProperty(
-          DeviceOrientationInspectorAgentState::kOverrideEnabled, false)) {
-    double alpha = 0;
-    state_->getDouble(DeviceOrientationInspectorAgentState::kAlpha, &alpha);
-    double beta = 0;
-    state_->getDouble(DeviceOrientationInspectorAgentState::kBeta, &beta);
-    double gamma = 0;
-    state_->getDouble(DeviceOrientationInspectorAgentState::kGamma, &gamma);
-    Controller()->SetOverride(
-        DeviceOrientationData::Create(alpha, beta, gamma, false));
-    sensor_agent_->SetOrientationSensorOverride(alpha, beta, gamma);
-  }
+  Controller()->SetOverride(DeviceOrientationData::Create(
+      alpha_.Get(), beta_.Get(), gamma_.Get(), false));
+  sensor_agent_->SetOrientationSensorOverride(alpha_.Get(), beta_.Get(),
+                                              gamma_.Get());
 }
 
 void DeviceOrientationInspectorAgent::DidCommitLoadForLocalFrame(
diff --git a/third_party/blink/renderer/modules/device_orientation/device_orientation_inspector_agent.h b/third_party/blink/renderer/modules/device_orientation/device_orientation_inspector_agent.h
index 67cdd984..e9d4bb27 100644
--- a/third_party/blink/renderer/modules/device_orientation/device_orientation_inspector_agent.h
+++ b/third_party/blink/renderer/modules/device_orientation/device_orientation_inspector_agent.h
@@ -38,7 +38,10 @@
 
   Member<InspectedFrames> inspected_frames_;
   Member<SensorInspectorAgent> sensor_agent_;
-
+  InspectorAgentState::Boolean enabled_;
+  InspectorAgentState::Double alpha_;
+  InspectorAgentState::Double beta_;
+  InspectorAgentState::Double gamma_;
   DISALLOW_COPY_AND_ASSIGN(DeviceOrientationInspectorAgent);
 };
 
diff --git a/third_party/blink/renderer/modules/vr/vr_controller.cc b/third_party/blink/renderer/modules/vr/vr_controller.cc
index 6805adad..f505d19 100644
--- a/third_party/blink/renderer/modules/vr/vr_controller.cc
+++ b/third_party/blink/renderer/modules/vr/vr_controller.cc
@@ -60,11 +60,11 @@
 // here. Upon calling SetClient in the constructor we should receive one call
 // for each VRDisplay that was already connected at the time.
 void VRController::OnDisplayConnected(
-    device::mojom::blink::VRDisplayHostPtr display,
+    device::mojom::blink::XRDevicePtr device,
     device::mojom::blink::VRDisplayClientRequest request,
     device::mojom::blink::VRDisplayInfoPtr display_info) {
   VRDisplay* vr_display =
-      new VRDisplay(navigator_vr_, std::move(display), std::move(request));
+      new VRDisplay(navigator_vr_, std::move(device), std::move(request));
   vr_display->Update(display_info);
   vr_display->OnConnected();
   vr_display->FocusChanged();
diff --git a/third_party/blink/renderer/modules/vr/vr_controller.h b/third_party/blink/renderer/modules/vr/vr_controller.h
index b91a0e5..d09ceb8 100644
--- a/third_party/blink/renderer/modules/vr/vr_controller.h
+++ b/third_party/blink/renderer/modules/vr/vr_controller.h
@@ -34,7 +34,7 @@
   void GetDisplays(ScriptPromiseResolver*);
   void SetListeningForActivate(bool);
 
-  void OnDisplayConnected(device::mojom::blink::VRDisplayHostPtr,
+  void OnDisplayConnected(device::mojom::blink::XRDevicePtr,
                           device::mojom::blink::VRDisplayClientRequest,
                           device::mojom::blink::VRDisplayInfoPtr) override;
 
diff --git a/third_party/blink/renderer/modules/vr/vr_display.cc b/third_party/blink/renderer/modules/vr/vr_display.cc
index d49ebfc..d6513fa 100644
--- a/third_party/blink/renderer/modules/vr/vr_display.cc
+++ b/third_party/blink/renderer/modules/vr/vr_display.cc
@@ -93,14 +93,13 @@
 
 }  // namespace
 
-VRDisplay::VRDisplay(
-    NavigatorVR* navigator_vr,
-    device::mojom::blink::VRDisplayHostPtr display,
-    device::mojom::blink::VRDisplayClientRequest request)
+VRDisplay::VRDisplay(NavigatorVR* navigator_vr,
+                     device::mojom::blink::XRDevicePtr device,
+                     device::mojom::blink::VRDisplayClientRequest request)
     : PausableObject(navigator_vr->GetDocument()),
       navigator_vr_(navigator_vr),
       capabilities_(new VRDisplayCapabilities()),
-      display_(std::move(display)),
+      device_ptr_(std::move(device)),
       display_client_binding_(this, std::move(request)) {
   PauseIfNeeded();  // Initialize SuspendabaleObject.
 
@@ -111,9 +110,10 @@
   // Set in_on_display_activate to true, this will prevent the request present
   // from being logged.
   // TODO(offenwanger): clean up the logging when refactors are complete.
-  display_->RequestSession(std::move(options), true,
-                           WTF::Bind(&VRDisplay::OnMagicWindowRequestReturned,
-                                     WrapPersistent(this)));
+  device_ptr_->RequestSession(
+      std::move(options), true,
+      WTF::Bind(&VRDisplay::OnMagicWindowRequestReturned,
+                WrapPersistent(this)));
 }
 
 VRDisplay::~VRDisplay() = default;
@@ -231,7 +231,7 @@
   if (!pending_vrdisplay_raf_)
     return;
   Document* doc = navigator_vr_->GetDocument();
-  if (!doc || !display_)
+  if (!doc || !device_ptr_)
     return;
   if (display_blurred_)
     return;
@@ -464,7 +464,7 @@
     // original request returns.
     pending_present_resolvers_.push_back(resolver);
   } else if (first_present) {
-    if (!display_) {
+    if (!device_ptr_) {
       ForceExitPresent();
       DOMException* exception =
           DOMException::Create(DOMExceptionCode::kInvalidStateError,
@@ -482,7 +482,7 @@
     options->immersive = true;
     options->use_legacy_webvr_render_path = true;
 
-    display_->RequestSession(
+    device_ptr_->RequestSession(
         std::move(options), in_display_activate_,
         WTF::Bind(&VRDisplay::OnRequestSessionReturned, WrapPersistent(this)));
     pending_present_request_ = true;
@@ -559,13 +559,13 @@
     return promise;
   }
 
-  if (!display_) {
+  if (!device_ptr_) {
     DOMException* exception = DOMException::Create(
         DOMExceptionCode::kInvalidStateError, "VRService is not available.");
     resolver->Reject(exception);
     return promise;
   }
-  display_->ExitPresent();
+  device_ptr_->ExitPresent();
 
   resolver->Resolve();
 
@@ -645,14 +645,14 @@
 
 // Need to close service if exists and then free rendering context.
 void VRDisplay::ForceExitPresent() {
-  if (display_) {
-    display_->ExitPresent();
+  if (device_ptr_) {
+    device_ptr_->ExitPresent();
   }
   StopPresenting();
 }
 
 void VRDisplay::UpdateLayerBounds() {
-  if (!display_)
+  if (!device_ptr_)
     return;
 
   // Left eye defaults
@@ -711,7 +711,7 @@
 void VRDisplay::submitFrame() {
   DVLOG(2) << __FUNCTION__;
 
-  if (!display_)
+  if (!device_ptr_)
     return;
   TRACE_EVENT1("gpu", "submitFrame", "frame", vr_frame_id_);
 
diff --git a/third_party/blink/renderer/modules/vr/vr_display.h b/third_party/blink/renderer/modules/vr/vr_display.h
index 4c6da38..462d066 100644
--- a/third_party/blink/renderer/modules/vr/vr_display.h
+++ b/third_party/blink/renderer/modules/vr/vr_display.h
@@ -108,7 +108,7 @@
   friend class VRController;
 
   VRDisplay(NavigatorVR*,
-            device::mojom::blink::VRDisplayHostPtr,
+            device::mojom::blink::XRDevicePtr,
             device::mojom::blink::VRDisplayClientRequest);
 
   void Update(const device::mojom::blink::VRDisplayInfoPtr&);
@@ -219,7 +219,7 @@
 
   device::mojom::blink::XRFrameDataProviderPtr magic_window_provider_;
 
-  device::mojom::blink::VRDisplayHostPtr display_;
+  device::mojom::blink::XRDevicePtr device_ptr_;
 
   bool present_image_needs_copy_ = false;
 
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.idl b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.idl
index 7755123..3196519 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.idl
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.idl
@@ -708,5 +708,5 @@
     [RuntimeEnabled=OffscreenCanvasCommit] void commit();
 
     // WebXR Device API support
-    [OriginTrialEnabled=WebXR, SecureContext, CallWith=ScriptState] Promise setCompatibleXRDevice(XRDevice device);
+    [OriginTrialEnabled=WebXR, SecureContext, CallWith=ScriptState] Promise<void> setCompatibleXRDevice(XRDevice device);
 };
diff --git a/third_party/blink/renderer/modules/xr/xr.cc b/third_party/blink/renderer/modules/xr/xr.cc
index 58c4a8a..55850ad 100644
--- a/third_party/blink/renderer/modules/xr/xr.cc
+++ b/third_party/blink/renderer/modules/xr/xr.cc
@@ -127,11 +127,11 @@
 // here. Upon calling SetClient in the constructor we should receive one call
 // for each XRDevice that was already connected at the time.
 void XR::OnDisplayConnected(
-    device::mojom::blink::VRDisplayHostPtr display,
+    device::mojom::blink::XRDevicePtr device,
     device::mojom::blink::VRDisplayClientRequest client_request,
     device::mojom::blink::VRDisplayInfoPtr display_info) {
   XRDevice* xr_device =
-      new XRDevice(this, std::move(display), std::move(client_request),
+      new XRDevice(this, std::move(device), std::move(client_request),
                    std::move(display_info));
 
   devices_.push_back(xr_device);
diff --git a/third_party/blink/renderer/modules/xr/xr.h b/third_party/blink/renderer/modules/xr/xr.h
index 4c6efa8c..8462d46 100644
--- a/third_party/blink/renderer/modules/xr/xr.h
+++ b/third_party/blink/renderer/modules/xr/xr.h
@@ -37,7 +37,7 @@
   ScriptPromise requestDevice(ScriptState*);
 
   // XRServiceClient overrides.
-  void OnDisplayConnected(device::mojom::blink::VRDisplayHostPtr,
+  void OnDisplayConnected(device::mojom::blink::XRDevicePtr,
                           device::mojom::blink::VRDisplayClientRequest,
                           device::mojom::blink::VRDisplayInfoPtr) override;
 
diff --git a/third_party/blink/renderer/modules/xr/xr.idl b/third_party/blink/renderer/modules/xr/xr.idl
index e17562fc..49fdec21 100644
--- a/third_party/blink/renderer/modules/xr/xr.idl
+++ b/third_party/blink/renderer/modules/xr/xr.idl
@@ -8,5 +8,5 @@
     OriginTrialEnabled=WebXR
 ] interface XR : EventTarget {
   attribute EventHandler ondevicechange;
-  [CallWith=ScriptState, MeasureAs=XRRequestDevice] Promise requestDevice();
+  [CallWith=ScriptState, MeasureAs=XRRequestDevice] Promise<XRDevice?> requestDevice();
 };
diff --git a/third_party/blink/renderer/modules/xr/xr_device.cc b/third_party/blink/renderer/modules/xr/xr_device.cc
index b77c9e5..25dc3832 100644
--- a/third_party/blink/renderer/modules/xr/xr_device.cc
+++ b/third_party/blink/renderer/modules/xr/xr_device.cc
@@ -39,13 +39,12 @@
 
 }  // namespace
 
-XRDevice::XRDevice(
-    XR* xr,
-    device::mojom::blink::VRDisplayHostPtr display,
-    device::mojom::blink::VRDisplayClientRequest client_request,
-    device::mojom::blink::VRDisplayInfoPtr display_info)
+XRDevice::XRDevice(XR* xr,
+                   device::mojom::blink::XRDevicePtr device,
+                   device::mojom::blink::VRDisplayClientRequest client_request,
+                   device::mojom::blink::VRDisplayInfoPtr display_info)
     : xr_(xr),
-      display_(std::move(display)),
+      device_ptr_(std::move(device)),
       display_client_binding_(this, std::move(client_request)) {
   SetXRDisplayInfo(std::move(display_info));
 }
@@ -95,7 +94,7 @@
       device::mojom::blink::XRSessionOptions::New();
   session_options->immersive = options.immersive();
 
-  display_->SupportsSession(
+  device_ptr_->SupportsSession(
       std::move(session_options),
       WTF::Bind(&XRDevice::OnSupportsSessionReturned, WrapPersistent(this),
                 WrapPersistent(resolver)));
@@ -185,7 +184,7 @@
   // TODO(offenwanger): Once device activation is sorted out for WebXR, either
   // pass in the value for metrics, or remove the value as soon as legacy API
   // has been removed.
-  display_->RequestSession(
+  device_ptr_->RequestSession(
       std::move(session_options), false /* triggered by display activate */,
       WTF::Bind(&XRDevice::OnRequestSessionReturned, WrapWeakPersistent(this),
                 WrapPersistent(resolver), WrapPersistent(output_context),
diff --git a/third_party/blink/renderer/modules/xr/xr_device.h b/third_party/blink/renderer/modules/xr/xr_device.h
index 4e7d08cb..a6508ad 100644
--- a/third_party/blink/renderer/modules/xr/xr_device.h
+++ b/third_party/blink/renderer/modules/xr/xr_device.h
@@ -28,7 +28,7 @@
 
  public:
   XRDevice(XR*,
-           device::mojom::blink::VRDisplayHostPtr,
+           device::mojom::blink::XRDevicePtr,
            device::mojom::blink::VRDisplayClientRequest,
            device::mojom::blink::VRDisplayInfoPtr);
   XR* xr() const { return xr_; }
@@ -51,8 +51,8 @@
 
   void Dispose();
 
-  const device::mojom::blink::VRDisplayHostPtr& xrDisplayHostPtr() const {
-    return display_;
+  const device::mojom::blink::XRDevicePtr& xrDevicePtr() const {
+    return device_ptr_;
   }
   const device::mojom::blink::XRFrameDataProviderPtr& xrMagicWindowProviderPtr()
       const {
@@ -115,7 +115,7 @@
 
   device::mojom::blink::XRFrameDataProviderPtr magic_window_provider_;
   device::mojom::blink::XREnviromentIntegrationProviderPtr enviroment_provider_;
-  device::mojom::blink::VRDisplayHostPtr display_;
+  device::mojom::blink::XRDevicePtr device_ptr_;
   device::mojom::blink::VRDisplayInfoPtr display_info_;
   unsigned int display_info_id_ = 0;
 
diff --git a/third_party/blink/renderer/modules/xr/xr_frame_provider.cc b/third_party/blink/renderer/modules/xr/xr_frame_provider.cc
index e8cb578..3ec12a2 100644
--- a/third_party/blink/renderer/modules/xr/xr_frame_provider.cc
+++ b/third_party/blink/renderer/modules/xr/xr_frame_provider.cc
@@ -147,7 +147,7 @@
   if (!immersive_session_)
     return;
 
-  device_->xrDisplayHostPtr()->ExitPresent();
+  device_->xrDevicePtr()->ExitPresent();
 
   immersive_session_ = nullptr;
   pending_immersive_vsync_ = false;
diff --git a/third_party/blink/renderer/platform/exported/web_runtime_features.cc b/third_party/blink/renderer/platform/exported/web_runtime_features.cc
index cc329f2..9f404be 100644
--- a/third_party/blink/renderer/platform/exported/web_runtime_features.cc
+++ b/third_party/blink/renderer/platform/exported/web_runtime_features.cc
@@ -444,10 +444,6 @@
   RuntimeEnabledFeatures::SetServiceWorkerScriptFullCodeCacheEnabled(enable);
 }
 
-void WebRuntimeFeatures::EnableAutoplayMutedVideos(bool enable) {
-  RuntimeEnabledFeatures::SetAutoplayMutedVideosEnabled(enable);
-}
-
 void WebRuntimeFeatures::EnableTimerThrottlingForBackgroundTabs(bool enable) {
   RuntimeEnabledFeatures::SetTimerThrottlingForBackgroundTabsEnabled(enable);
 }
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc b/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc
index 0133cc0..f75d8d1 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc
@@ -131,6 +131,7 @@
     base::TimeTicks commit_start_time,
     const SkIRect& damage_rect,
     bool needs_vertical_flip) {
+  TRACE_EVENT0("blink", "CanvasResourceDispatcher::DispatchFrameSync");
   viz::CompositorFrame frame;
   if (!PrepareFrame(std::move(canvas_resource), commit_start_time, damage_rect,
                     needs_vertical_flip, &frame)) {
@@ -150,6 +151,7 @@
     base::TimeTicks commit_start_time,
     const SkIRect& damage_rect,
     bool needs_vertical_flip) {
+  TRACE_EVENT0("blink", "CanvasResourceDispatcher::DispatchFrame");
   viz::CompositorFrame frame;
   if (!PrepareFrame(std::move(canvas_resource), commit_start_time, damage_rect,
                     needs_vertical_flip, &frame)) {
@@ -168,6 +170,7 @@
     const SkIRect& damage_rect,
     bool needs_vertical_flip,
     viz::CompositorFrame* frame) {
+  TRACE_EVENT0("blink", "CanvasResourceDispatcher::PrepareFrame");
   if (!canvas_resource || !VerifyImageSize(canvas_resource->Size()))
     return false;
 
diff --git a/third_party/blink/renderer/platform/graphics/graphics_layer.cc b/third_party/blink/renderer/platform/graphics/graphics_layer.cc
index 517b8e8..4ca1c6e4 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_layer.cc
+++ b/third_party/blink/renderer/platform/graphics/graphics_layer.cc
@@ -876,7 +876,8 @@
     }
     image_layer_->SetImage(std::move(paint_image), matrix,
                            image_orientation.UsesWidthAsHeight());
-    image_layer_->SetContentsOpaque(image->CurrentFrameKnownToBeOpaque());
+    // Image layers can not be marked as opaque due to crbug.com/870857.
+    image_layer_->SetContentsOpaque(false);
     UpdateContentsRect();
   } else if (image_layer_) {
     UnregisterContentsLayer(image_layer_.get());
diff --git a/third_party/blink/renderer/platform/graphics/image_layer_chromium_test.cc b/third_party/blink/renderer/platform/graphics/image_layer_chromium_test.cc
index 25de4618..bd11e25 100644
--- a/third_party/blink/renderer/platform/graphics/image_layer_chromium_test.cc
+++ b/third_party/blink/renderer/platform/graphics/image_layer_chromium_test.cc
@@ -131,7 +131,9 @@
 
   graphics_layer->SetContentsToImage(opaque_image.get(),
                                      Image::kUnspecifiedDecode);
-  ASSERT_TRUE(graphics_layer->ContentsLayer()->contents_opaque());
+  // This would normally have contents_opaque set but due to crbug.com/870857,
+  // we cannot set image layers as having contents_opaque.
+  ASSERT_FALSE(graphics_layer->ContentsLayer()->contents_opaque());
 
   graphics_layer->SetContentsToImage(non_opaque_image.get(),
                                      Image::kUnspecifiedDecode);
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 0a05df4..324273ee 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -117,10 +117,6 @@
       settable_from_internals: true,
     },
     {
-      name: "AutoplayMutedVideos",
-      settable_from_internals: true,
-    },
-    {
       name: "BackgroundFetch",
       status: "experimental",
     },
diff --git a/third_party/blink/renderer/platform/scheduler/child/features.h b/third_party/blink/renderer/platform/scheduler/child/features.h
index 35ae3b2..65aad512 100644
--- a/third_party/blink/renderer/platform/scheduler/child/features.h
+++ b/third_party/blink/renderer/platform/scheduler/child/features.h
@@ -87,6 +87,14 @@
     "BlinkSchedulerLowPriorityForHiddenFrame",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Used along with |kLowPriorityForHiddenFrame|,
+// |kLowPriorityForSubFrameThrottleableTask|, |kLowPriorityForThrottleableTask|,
+// |kLowPriorityForSubFrame| to enable one of these experiments only during the
+// load use case.
+const base::Feature kFrameExperimentOnlyWhenLoading{
+    "BlinkSchedulerFrameExperimentOnlyWhenLoading",
+    base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables setting the priority of an ad frame to low priority.
 const base::Feature kLowPriorityForAdFrame{
     "BlinkSchedulerLowPriorityForAdFrame", base::FEATURE_DISABLED_BY_DEFAULT};
@@ -96,20 +104,34 @@
     "BlinkSchedulerBestEffortPriorityForAdFrame",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Used along with |kLowPriorityForAdFrame| or |kBestEffortPriorityForAdFrame|
+// to enable one of these experiments only during the load use case.
+const base::Feature kAdFrameExperimentOnlyWhenLoading{
+    "BlinkSchedulerAdFrameExperimentOnlyWhenLoading",
+    base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables using a resource's fetch priority to determine the priority of the
 // resource's loading tasks posted to blink's scheduler.
 const base::Feature kUseResourceFetchPriority{
     "BlinkSchedulerResourceFetchPriority", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enables using a resource's fetch priority to determine the priority of the
+// resource's loading tasks posted to blink's scheduler only for resources
+// requested during the loading phase.
+const base::Feature kUseResourceFetchPriorityOnlyWhenLoading{
+    "BlinkSchedulerResourceFetchPriorityOnlyWhenLoading",
+    base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables setting the priority of cross-origin task queues to
 // low priority.
 const base::Feature kLowPriorityForCrossOrigin{
     "BlinkSchedulerLowPriorityForCrossOrigin",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
-// Enables a chosen experiments only during the load use case.
-const base::Feature kExperimentOnlyWhenLoading{
-    "BlinkSchedulerExperimentOnlyWhenLoading",
+// Enables setting the priority of cross-origin task queues to
+// low priority during loading only.
+const base::Feature kLowPriorityForCrossOriginOnlyWhenLoading{
+    "BlinkSchedulerLowPriorityForCrossOriginOnlyWhenLoading",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
 // Enable setting throttleable and freezable task types from field trial
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc
index b3663fa..936e0ea 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc
@@ -508,7 +508,10 @@
 std::unique_ptr<ResourceLoadingTaskRunnerHandleImpl>
 FrameSchedulerImpl::CreateResourceLoadingTaskRunnerHandleImpl() {
   if (main_thread_scheduler_->scheduling_settings()
-          .use_resource_fetch_priority) {
+          .use_resource_fetch_priority ||
+      (parent_page_scheduler_->IsLoading() &&
+       main_thread_scheduler_->scheduling_settings()
+           .use_resource_priorities_only_during_loading)) {
     scoped_refptr<MainThreadTaskQueue> task_queue =
         frame_task_queue_controller_->NewResourceLoadingTaskQueue();
     resource_loading_task_queue_priorities_.insert(
@@ -796,7 +799,7 @@
   // all times.
   if (parent_page_scheduler_->IsLoading() ||
       !main_thread_scheduler_->scheduling_settings()
-           .experiment_only_when_loading) {
+           .use_frame_priorities_only_during_loading) {
     // Low priority feature enabled for hidden frame.
     if (main_thread_scheduler_->scheduling_settings()
             .low_priority_hidden_frame &&
@@ -824,20 +827,28 @@
             .low_priority_throttleable &&
         is_throttleable_task_queue)
       return TaskQueue::QueuePriority::kLowPriority;
+  }
 
-    if (main_thread_scheduler_->scheduling_settings().low_priority_ad_frame &&
-        IsAdFrame()) {
+  // Ad frame experiment.
+  if (IsAdFrame() && (parent_page_scheduler_->IsLoading() ||
+                      !main_thread_scheduler_->scheduling_settings()
+                           .use_adframe_priorities_only_during_loading)) {
+    if (main_thread_scheduler_->scheduling_settings().low_priority_ad_frame) {
       return TaskQueue::QueuePriority::kLowPriority;
     }
 
-    if (main_thread_scheduler_->scheduling_settings().best_effort_ad_frame &&
-        IsAdFrame()) {
+    if (main_thread_scheduler_->scheduling_settings().best_effort_ad_frame) {
       return TaskQueue::QueuePriority::kBestEffortPriority;
     }
+  }
 
+  // Frame origin type experiment.
+  if (IsCrossOrigin()) {
     if (main_thread_scheduler_->scheduling_settings()
-            .low_priority_cross_origin &&
-        IsCrossOrigin()) {
+            .low_priority_cross_origin ||
+        (main_thread_scheduler_->scheduling_settings()
+             .low_priority_cross_origin_only_during_loading &&
+         parent_page_scheduler_->IsLoading())) {
       return TaskQueue::QueuePriority::kLowPriority;
     }
   }
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
index 14657d4..8457836 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
@@ -785,7 +785,7 @@
  public:
   LowPriorityHiddenFrameExperimentTest()
       : FrameSchedulerImplTest({kLowPriorityForHiddenFrame},
-                               {kExperimentOnlyWhenLoading}) {}
+                               {kFrameExperimentOnlyWhenLoading}) {}
 };
 
 TEST_F(LowPriorityHiddenFrameExperimentTest, FrameQueuesPriorities) {
@@ -825,7 +825,7 @@
  public:
   LowPriorityHiddenFrameDuringLoadingExperimentTest()
       : FrameSchedulerImplTest(
-            {kLowPriorityForHiddenFrame, kExperimentOnlyWhenLoading},
+            {kLowPriorityForHiddenFrame, kFrameExperimentOnlyWhenLoading},
             {}) {}
 };
 
@@ -872,7 +872,7 @@
  public:
   LowPrioritySubFrameExperimentTest()
       : FrameSchedulerImplTest({kLowPriorityForSubFrame},
-                               {kExperimentOnlyWhenLoading}) {}
+                               {kFrameExperimentOnlyWhenLoading}) {}
 };
 
 TEST_F(LowPrioritySubFrameExperimentTest, FrameQueuesPriorities) {
@@ -914,7 +914,7 @@
  public:
   LowPrioritySubFrameDuringLoadingExperimentTest()
       : FrameSchedulerImplTest(
-            {kLowPriorityForSubFrame, kExperimentOnlyWhenLoading},
+            {kLowPriorityForSubFrame, kFrameExperimentOnlyWhenLoading},
             {}) {}
 };
 
@@ -961,7 +961,7 @@
  public:
   LowPrioritySubFrameThrottleableTaskExperimentTest()
       : FrameSchedulerImplTest({kLowPriorityForSubFrameThrottleableTask},
-                               {kExperimentOnlyWhenLoading}) {}
+                               {kFrameExperimentOnlyWhenLoading}) {}
 };
 
 TEST_F(LowPrioritySubFrameThrottleableTaskExperimentTest,
@@ -1004,7 +1004,7 @@
  public:
   LowPrioritySubFrameThrottleableTaskDuringLoadingExperimentTest()
       : FrameSchedulerImplTest({kLowPriorityForSubFrameThrottleableTask,
-                                kExperimentOnlyWhenLoading},
+                                kFrameExperimentOnlyWhenLoading},
                                {}) {}
 };
 
@@ -1052,7 +1052,7 @@
  public:
   LowPriorityThrottleableTaskExperimentTest()
       : FrameSchedulerImplTest({kLowPriorityForThrottleableTask},
-                               {kExperimentOnlyWhenLoading}) {}
+                               {kFrameExperimentOnlyWhenLoading}) {}
 };
 
 TEST_F(LowPriorityThrottleableTaskExperimentTest, FrameQueuesPriorities) {
@@ -1094,7 +1094,7 @@
  public:
   LowPriorityThrottleableTaskDuringLoadingExperimentTest()
       : FrameSchedulerImplTest(
-            {kLowPriorityForThrottleableTask, kExperimentOnlyWhenLoading},
+            {kLowPriorityForThrottleableTask, kFrameExperimentOnlyWhenLoading},
             {}) {}
 };
 
@@ -1181,7 +1181,8 @@
 class LowPriorityAdFrameExperimentTest : public FrameSchedulerImplTest {
  public:
   LowPriorityAdFrameExperimentTest()
-      : FrameSchedulerImplTest({kLowPriorityForAdFrame}, {}) {}
+      : FrameSchedulerImplTest({kLowPriorityForAdFrame},
+                               {kAdFrameExperimentOnlyWhenLoading}) {}
 };
 
 TEST_F(LowPriorityAdFrameExperimentTest, FrameQueuesPriorities) {
@@ -1223,7 +1224,7 @@
  public:
   LowPriorityAdFrameDuringLoadingExperimentTest()
       : FrameSchedulerImplTest(
-            {kLowPriorityForAdFrame, kExperimentOnlyWhenLoading},
+            {kLowPriorityForAdFrame, kAdFrameExperimentOnlyWhenLoading},
             {}) {}
 };
 
@@ -1271,7 +1272,8 @@
 class BestEffortPriorityAdFrameExperimentTest : public FrameSchedulerImplTest {
  public:
   BestEffortPriorityAdFrameExperimentTest()
-      : FrameSchedulerImplTest({kBestEffortPriorityForAdFrame}, {}) {}
+      : FrameSchedulerImplTest({kBestEffortPriorityForAdFrame},
+                               {kAdFrameExperimentOnlyWhenLoading}) {}
 };
 
 TEST_F(BestEffortPriorityAdFrameExperimentTest, FrameQueuesPriorities) {
@@ -1313,7 +1315,7 @@
  public:
   BestEffortPriorityAdFrameDuringLoadingExperimentTest()
       : FrameSchedulerImplTest(
-            {kBestEffortPriorityForAdFrame, kExperimentOnlyWhenLoading},
+            {kBestEffortPriorityForAdFrame, kAdFrameExperimentOnlyWhenLoading},
             {}) {}
 };
 
@@ -1367,8 +1369,8 @@
         {"HIGHEST", "HIGH"}, {"MEDIUM", "NORMAL"}, {"LOW", "NORMAL"},
         {"LOWEST", "LOW"},   {"IDLE", "LOW"},      {"THROTTLED", "LOW"}};
 
-    const char kStudyName[] = "BlinkSchedulerResourceFetchPriority";
-    const char kGroupName[] = "GroupName";
+    const char kStudyName[] = "ResourceFetchPriorityExperiment";
+    const char kGroupName[] = "GroupName1";
 
     field_trial_list_ = std::make_unique<base::FieldTrialList>(nullptr);
     base::AssociateFieldTrialParams(kStudyName, kGroupName, params);
@@ -1393,6 +1395,52 @@
             TaskQueue::QueuePriority::kHighPriority);
 }
 
+class ResourceFetchPriorityExperimentOnlyWhenLoadingTest
+    : public FrameSchedulerImplTest {
+ public:
+  ResourceFetchPriorityExperimentOnlyWhenLoadingTest()
+      : FrameSchedulerImplTest({kUseResourceFetchPriorityOnlyWhenLoading}, {}) {
+    std::map<std::string, std::string> params{
+        {"HIGHEST", "HIGH"}, {"MEDIUM", "NORMAL"}, {"LOW", "NORMAL"},
+        {"LOWEST", "LOW"},   {"IDLE", "LOW"},      {"THROTTLED", "LOW"}};
+
+    const char kStudyName[] = "ResourceFetchPriorityExperiment";
+    const char kGroupName[] = "GroupName2";
+
+    field_trial_list_ = std::make_unique<base::FieldTrialList>(nullptr);
+    base::AssociateFieldTrialParams(kStudyName, kGroupName, params);
+    base::FieldTrialList::CreateFieldTrial(kStudyName, kGroupName);
+  }
+};
+
+TEST_F(ResourceFetchPriorityExperimentOnlyWhenLoadingTest, DidChangePriority) {
+  std::unique_ptr<ResourceLoadingTaskRunnerHandleImpl> handle =
+      GetResourceLoadingTaskRunnerHandleImpl();
+  scoped_refptr<MainThreadTaskQueue> task_queue = handle->task_queue();
+
+  TaskQueue::QueuePriority priority = task_queue->GetQueuePriority();
+  EXPECT_EQ(priority, TaskQueue::QueuePriority::kNormalPriority);
+
+  // Experiment is only enabled during the loading phase.
+  DidChangeResourceLoadingPriority(task_queue, net::RequestPriority::LOWEST);
+  EXPECT_EQ(task_queue->GetQueuePriority(), priority);
+
+  // Main thread scheduler is in the loading use case.
+  scheduler_->DidStartProvisionalLoad(true);
+  EXPECT_TRUE(page_scheduler_->IsLoading());
+
+  handle = GetResourceLoadingTaskRunnerHandleImpl();
+  task_queue = handle->task_queue();
+
+  DidChangeResourceLoadingPriority(task_queue, net::RequestPriority::LOWEST);
+  EXPECT_EQ(task_queue->GetQueuePriority(),
+            TaskQueue::QueuePriority::kLowPriority);
+
+  DidChangeResourceLoadingPriority(task_queue, net::RequestPriority::HIGHEST);
+  EXPECT_EQ(task_queue->GetQueuePriority(),
+            TaskQueue::QueuePriority::kHighPriority);
+}
+
 TEST_F(
     FrameSchedulerImplTest,
     DidChangeResourceLoadingPriority_ResourceFecthPriorityExperimentDisabled) {
@@ -1415,8 +1463,7 @@
 class LowPriorityCrossOriginTaskExperimentTest : public FrameSchedulerImplTest {
  public:
   LowPriorityCrossOriginTaskExperimentTest()
-      : FrameSchedulerImplTest({kLowPriorityForCrossOrigin},
-                               {kExperimentOnlyWhenLoading}) {}
+      : FrameSchedulerImplTest({kLowPriorityForCrossOrigin}, {}) {}
 };
 
 TEST_F(LowPriorityCrossOriginTaskExperimentTest, FrameQueuesPriorities) {
@@ -1457,9 +1504,8 @@
     : public FrameSchedulerImplTest {
  public:
   LowPriorityCrossOriginTaskDuringLoadingExperimentTest()
-      : FrameSchedulerImplTest(
-            {kLowPriorityForCrossOrigin, kExperimentOnlyWhenLoading},
-            {}) {}
+      : FrameSchedulerImplTest({kLowPriorityForCrossOriginOnlyWhenLoading},
+                               {}) {}
 };
 
 TEST_F(LowPriorityCrossOriginTaskDuringLoadingExperimentTest,
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
index bc9926f..7b171164 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
@@ -76,7 +76,7 @@
 // Name of the finch study that enables using resource fetch priorities to
 // schedule tasks on Blink.
 constexpr const char kResourceFetchPriorityExperiment[] =
-    "BlinkSchedulerResourceFetchPriority";
+    "ResourceFetchPriorityExperiment";
 
 base::TimeDelta GetWakeUpDuration() {
   int duration_ms;
@@ -575,18 +575,27 @@
       base::FeatureList::IsEnabled(kLowPriorityForThrottleableTask);
   low_priority_subframe_throttleable =
       base::FeatureList::IsEnabled(kLowPriorityForSubFrameThrottleableTask);
+  use_frame_priorities_only_during_loading =
+      base::FeatureList::IsEnabled(kFrameExperimentOnlyWhenLoading);
 
   low_priority_ad_frame = base::FeatureList::IsEnabled(kLowPriorityForAdFrame);
   best_effort_ad_frame =
       base::FeatureList::IsEnabled(kBestEffortPriorityForAdFrame);
+  use_adframe_priorities_only_during_loading =
+      base::FeatureList::IsEnabled(kAdFrameExperimentOnlyWhenLoading);
 
   low_priority_cross_origin =
       base::FeatureList::IsEnabled(kLowPriorityForCrossOrigin);
+  low_priority_cross_origin_only_during_loading =
+      base::FeatureList::IsEnabled(kLowPriorityForCrossOriginOnlyWhenLoading);
 
   use_resource_fetch_priority =
       base::FeatureList::IsEnabled(kUseResourceFetchPriority);
+  use_resource_priorities_only_during_loading =
+      base::FeatureList::IsEnabled(kUseResourceFetchPriorityOnlyWhenLoading);
 
-  if (use_resource_fetch_priority) {
+  if (use_resource_fetch_priority ||
+      use_resource_priorities_only_during_loading) {
     std::map<std::string, std::string> params;
     base::GetFieldTrialParams(kResourceFetchPriorityExperiment, &params);
     for (size_t net_priority = 0;
@@ -603,9 +612,6 @@
     }
   }
 
-  experiment_only_when_loading =
-      base::FeatureList::IsEnabled(kExperimentOnlyWhenLoading);
-
   FrameSchedulerImpl::InitializeTaskTypeQueueTraitsMap(
       frame_task_types_to_queue_traits);
 }
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
index d6a88f6f..8bce663 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
@@ -100,16 +100,24 @@
     bool low_priority_subframe_throttleable;
     bool low_priority_hidden_frame;
 
+    // Used along with |low_priority_subframe|, |low_priority_throttleable|,
+    // |low_priority_subframe_throttleable|, |low_priority_hidden_frame|
+    // to enable one of these experiments during the loading phase only.
+    bool use_frame_priorities_only_during_loading;
+
     // Ads priority experiment (crbug.com/856150).
     bool low_priority_ad_frame;
     bool best_effort_ad_frame;
+    bool use_adframe_priorities_only_during_loading;
 
     // Origin type priority experiment (crbug.com/856158).
     bool low_priority_cross_origin;
+    bool low_priority_cross_origin_only_during_loading;
 
     // Use resource fetch priority for resource loading tasks
     // (crbug.com/860545).
     bool use_resource_fetch_priority;
+    bool use_resource_priorities_only_during_loading;
 
     // Contains a mapping from net::RequestPriority to TaskQueue::QueuePriority
     // when use_resource_fetch_priority is enabled.
diff --git a/third_party/libvpx/README.chromium b/third_party/libvpx/README.chromium
index 266f0493..fee52d7 100644
--- a/third_party/libvpx/README.chromium
+++ b/third_party/libvpx/README.chromium
@@ -5,9 +5,9 @@
 License File: source/libvpx/LICENSE
 Security Critical: yes
 
-Date: Saturday July 28 2018
+Date: Friday August 03 2018
 Branch: master
-Commit: 2d79df49408873dd62a1b26cf8ed0e067c26dc6d
+Commit: 6fd9d0244c7d8941ce0004bcd2efce5d6676bef5
 
 Description:
 Contains the sources used to compile libvpx binaries used by Google Chrome and
diff --git a/third_party/libvpx/source/config/ios/arm-neon/vpx_config.asm b/third_party/libvpx/source/config/ios/arm-neon/vpx_config.asm
index aeaea99..3a2f5e2 100644
--- a/third_party/libvpx/source/config/ios/arm-neon/vpx_config.asm
+++ b/third_party/libvpx/source/config/ios/arm-neon/vpx_config.asm
@@ -4,6 +4,7 @@
 	.set WIDE_REFERENCE, 0
 	.set ARCHITECTURE, 5
 	.set DO1STROUNDING, 0
+	.syntax unified
 .set ARCH_ARM ,  1
 .set ARCH_MIPS ,  0
 .set ARCH_X86 ,  0
diff --git a/third_party/libvpx/source/config/ios/arm64/vpx_config.asm b/third_party/libvpx/source/config/ios/arm64/vpx_config.asm
index 296266de..42f5b41 100644
--- a/third_party/libvpx/source/config/ios/arm64/vpx_config.asm
+++ b/third_party/libvpx/source/config/ios/arm64/vpx_config.asm
@@ -4,6 +4,7 @@
 	.set WIDE_REFERENCE, 0
 	.set ARCHITECTURE, 5
 	.set DO1STROUNDING, 0
+	.syntax unified
 .set ARCH_ARM ,  1
 .set ARCH_MIPS ,  0
 .set ARCH_X86 ,  0
diff --git a/third_party/libvpx/source/config/linux/arm-neon-cpu-detect/vpx_config.asm b/third_party/libvpx/source/config/linux/arm-neon-cpu-detect/vpx_config.asm
index e6fa07b..3e38effd 100644
--- a/third_party/libvpx/source/config/linux/arm-neon-cpu-detect/vpx_config.asm
+++ b/third_party/libvpx/source/config/linux/arm-neon-cpu-detect/vpx_config.asm
@@ -1,6 +1,7 @@
 @ This file was created from a .asm file
 @  using the ads2gas.pl script.
 	.equ DO1STROUNDING, 0
+	.syntax unified
 .equ ARCH_ARM ,  1
 .equ ARCH_MIPS ,  0
 .equ ARCH_X86 ,  0
diff --git a/third_party/libvpx/source/config/linux/arm-neon/vpx_config.asm b/third_party/libvpx/source/config/linux/arm-neon/vpx_config.asm
index 1137b0f..3c20871 100644
--- a/third_party/libvpx/source/config/linux/arm-neon/vpx_config.asm
+++ b/third_party/libvpx/source/config/linux/arm-neon/vpx_config.asm
@@ -1,6 +1,7 @@
 @ This file was created from a .asm file
 @  using the ads2gas.pl script.
 	.equ DO1STROUNDING, 0
+	.syntax unified
 .equ ARCH_ARM ,  1
 .equ ARCH_MIPS ,  0
 .equ ARCH_X86 ,  0
diff --git a/third_party/libvpx/source/config/linux/arm/vpx_config.asm b/third_party/libvpx/source/config/linux/arm/vpx_config.asm
index 51d4f390..3d626673 100644
--- a/third_party/libvpx/source/config/linux/arm/vpx_config.asm
+++ b/third_party/libvpx/source/config/linux/arm/vpx_config.asm
@@ -1,6 +1,7 @@
 @ This file was created from a .asm file
 @  using the ads2gas.pl script.
 	.equ DO1STROUNDING, 0
+	.syntax unified
 .equ ARCH_ARM ,  1
 .equ ARCH_MIPS ,  0
 .equ ARCH_X86 ,  0
diff --git a/third_party/libvpx/source/config/linux/arm64/vpx_config.asm b/third_party/libvpx/source/config/linux/arm64/vpx_config.asm
index 54efd55c5..02e9e46 100644
--- a/third_party/libvpx/source/config/linux/arm64/vpx_config.asm
+++ b/third_party/libvpx/source/config/linux/arm64/vpx_config.asm
@@ -1,6 +1,7 @@
 @ This file was created from a .asm file
 @  using the ads2gas.pl script.
 	.equ DO1STROUNDING, 0
+	.syntax unified
 .equ ARCH_ARM ,  1
 .equ ARCH_MIPS ,  0
 .equ ARCH_X86 ,  0
diff --git a/third_party/libvpx/source/config/linux/generic/vpx_config.asm b/third_party/libvpx/source/config/linux/generic/vpx_config.asm
index 650636a..d24146a 100644
--- a/third_party/libvpx/source/config/linux/generic/vpx_config.asm
+++ b/third_party/libvpx/source/config/linux/generic/vpx_config.asm
@@ -1,6 +1,7 @@
 @ This file was created from a .asm file
 @  using the ads2gas.pl script.
 	.equ DO1STROUNDING, 0
+	.syntax unified
 .equ ARCH_ARM ,  0
 .equ ARCH_MIPS ,  0
 .equ ARCH_X86 ,  0
diff --git a/third_party/libvpx/source/config/vpx_version.h b/third_party/libvpx/source/config/vpx_version.h
index 1c8c474..aaa96d5 100644
--- a/third_party/libvpx/source/config/vpx_version.h
+++ b/third_party/libvpx/source/config/vpx_version.h
@@ -2,7 +2,7 @@
 #define VERSION_MAJOR  1
 #define VERSION_MINOR  7
 #define VERSION_PATCH  0
-#define VERSION_EXTRA  "731-g2d79df494"
+#define VERSION_EXTRA  "778-g6fd9d0244"
 #define VERSION_PACKED ((VERSION_MAJOR<<16)|(VERSION_MINOR<<8)|(VERSION_PATCH))
-#define VERSION_STRING_NOSP "v1.7.0-731-g2d79df494"
-#define VERSION_STRING      " v1.7.0-731-g2d79df494"
+#define VERSION_STRING_NOSP "v1.7.0-778-g6fd9d0244"
+#define VERSION_STRING      " v1.7.0-778-g6fd9d0244"
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index a050ccd0..61d2c1c 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -16449,6 +16449,15 @@
   </description>
 </action>
 
+<action name="Signin_Impression_FromSaveCardBubble">
+  <owner>manasverma@google.com</owner>
+  <owner>jsaul@chromium.org</owner>
+  <owner>sebsg@chromium.org</owner>
+  <description>
+    Recorded when showing sign in entry in the save card bubble.
+  </description>
+</action>
+
 <action name="Signin_Impression_FromSettings">
   <owner>bzanotti@chromium.org</owner>
   <owner>gogerald@chromium.org</owner>
@@ -16540,6 +16549,17 @@
   </description>
 </action>
 
+<action name="Signin_ImpressionWithAccount_FromSaveCardBubble">
+  <owner>manasverma@google.com</owner>
+  <owner>jsaul@chromium.org</owner>
+  <owner>sebsg@chromium.org</owner>
+  <description>
+    Recorded when the personalized sign-in promo is shown with no account in the
+    save card bubble.
+    (signin_metrics::AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE).
+  </description>
+</action>
+
 <action name="Signin_ImpressionWithAccount_FromSettings">
   <owner>jlebel@chromium.org</owner>
   <description>
@@ -16619,6 +16639,17 @@
   </description>
 </action>
 
+<action name="Signin_ImpressionWithNoAccount_FromSaveCardBubble">
+  <owner>manasverma@google.com</owner>
+  <owner>jsaul@chromium.org</owner>
+  <owner>sebsg@chromium.org</owner>
+  <description>
+    Recorded when the personalized sign-in promo is shown with no account in the
+    save card bubble.
+    (signin_metrics::AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE).
+  </description>
+</action>
+
 <action name="Signin_ImpressionWithNoAccount_FromSettings">
   <owner>jlebel@chromium.org</owner>
   <description>
@@ -16801,6 +16832,16 @@
   </description>
 </action>
 
+<action name="Signin_Signin_FromSaveCardBubble">
+  <owner>manasverma@google.com</owner>
+  <owner>jsaul@chromium.org</owner>
+  <owner>sebsg@chromium.org</owner>
+  <description>
+    Recorded on sign in start from access point
+    signin_metrics::AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE.
+  </description>
+</action>
+
 <action name="Signin_Signin_FromSettings">
   <owner>gogerald@chromium.org</owner>
   <description>
@@ -16949,6 +16990,17 @@
   </description>
 </action>
 
+<action name="Signin_SigninNewAccount_FromSaveCardBubble">
+  <owner>manasverma@google.com</owner>
+  <owner>jsaul@chromium.org</owner>
+  <owner>sebsg@chromium.org</owner>
+  <description>
+    Recorded on sign in start from access point
+    signin_metrics::AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE, with a new
+    account.
+  </description>
+</action>
+
 <action name="Signin_SigninNewAccount_FromSettings">
   <owner>jlebel@chromium.org</owner>
   <description>
@@ -17028,6 +17080,17 @@
   </description>
 </action>
 
+<action name="Signin_SigninNotDefault_FromSaveCardBubble">
+  <owner>manasverma@google.com</owner>
+  <owner>jsaul@chromium.org</owner>
+  <owner>sebsg@chromium.org</owner>
+  <description>
+    Recorded on sign in start from access point
+    signin_metrics::AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE, using another
+    account than the default one.
+  </description>
+</action>
+
 <action name="Signin_SigninNotDefault_FromSettings">
   <owner>jlebel@chromium.org</owner>
   <description>
@@ -17119,6 +17182,17 @@
   </description>
 </action>
 
+<action name="Signin_SigninWithDefault_FromSaveCardBubble">
+  <owner>manasverma@google.com</owner>
+  <owner>jsaul@chromium.org</owner>
+  <owner>sebsg@chromium.org</owner>
+  <description>
+    Recorded on sign in start from access point
+    signin_metrics::AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE, using the
+    default account.
+  </description>
+</action>
+
 <action name="Signin_SigninWithDefault_FromSettings">
   <owner>jlebel@chromium.org</owner>
   <description>
@@ -18225,6 +18299,9 @@
 </action>
 
 <action name="Suggestions.Contextual.Carousel.Scrolled">
+  <obsolete>
+    Deprecated with new contextual suggestions implementation.
+  </obsolete>
   <owner>dgn@chromium.org</owner>
   <owner>galinap@google.com</owner>
   <description>
@@ -18234,6 +18311,9 @@
 </action>
 
 <action name="Suggestions.Contextual.Carousel.Shown">
+  <obsolete>
+    Deprecated with new contextual suggestions implementation.
+  </obsolete>
   <owner>dgn@chromium.org</owner>
   <owner>galinap@google.com</owner>
   <description>
@@ -18243,6 +18323,9 @@
 </action>
 
 <action name="Suggestions.ContextualSuggestion.Open">
+  <obsolete>
+    Deprecated with new contextual suggestions implementation.
+  </obsolete>
   <owner>dgn@chromium.org</owner>
   <owner>galinap@google.com</owner>
   <description>
@@ -18300,6 +18383,7 @@
 </action>
 
 <action name="Suggestions.SurfaceFullyVisible">
+  <obsolete>Deprecated with Chrome Home ramp down.</obsolete>
   <owner>finkm@chromium.org</owner>
   <owner>dgn@chromium.org</owner>
   <description>
@@ -18310,6 +18394,7 @@
 </action>
 
 <action name="Suggestions.SurfaceHalfVisible">
+  <obsolete>Deprecated with Chrome Home ramp down.</obsolete>
   <owner>finkm@chromium.org</owner>
   <owner>dgn@chromium.org</owner>
   <description>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index a865e8e3..3847654d 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -3366,6 +3366,8 @@
   <int value="202" label="PERMISSION_SERVICE_BAD_PERMISSION_DESCRIPTOR"/>
   <int value="203" label="RFH_BLOB_URL_TOKEN_FOR_NON_BLOB_URL"/>
   <int value="204" label="RFPH_BLOB_URL_TOKEN_FOR_NON_BLOB_URL"/>
+  <int value="205" label="RFH_ERROR_PROCESS_NON_ERROR_COMMIT"/>
+  <int value="206" label="RFH_ERROR_PROCESS_NON_UNIQUE_ORIGIN_COMMIT"/>
 </enum>
 
 <enum name="BadMessageReasonExtensions">
@@ -27785,6 +27787,8 @@
   <int value="-1714128884" label="disable-launcher-search-provider-api"/>
   <int value="-1713564656" label="ProtectSyncCredentialOnReauth:enabled"/>
   <int value="-1711751318" label="enable-data-reduction-proxy-lo-fi-preview"/>
+  <int value="-1711283256"
+      label="AutofillSaveCardSignInAfterLocalSave:enabled"/>
   <int value="-1710772665" label="disable-my-files-navigation"/>
   <int value="-1703709912" label="enable-new-ntp"/>
   <int value="-1703308540" label="disable-webaudio"/>
@@ -29604,6 +29608,8 @@
   <int value="1995322219" label="EmojiHandwritingVoiceInput:enabled"/>
   <int value="1996125159" label="AutoplayMutedVideos:enabled"/>
   <int value="1997047666" label="NTPSnippetsIncreasedVisibility:disabled"/>
+  <int value="1999081349"
+      label="AutofillSaveCardSignInAfterLocalSave:disabled"/>
   <int value="2000091128" label="enable-touch-hover"/>
   <int value="2001562962"
       label="enable-manual-fallback-for-password-saving:enabled"/>
@@ -40869,6 +40875,9 @@
   <int value="46" label="WorkerThreadTaskQueueDefault"/>
   <int value="47" label="WorkerThreadTaskQueueV8"/>
   <int value="48" label="WorkerThreadTaskQueueCompositor"/>
+  <int value="49" label="CompositorThreadTaskQueueInput"/>
+  <int value="50" label="NetworkingWithURLLoaderAnnotation"/>
+  <int value="51" label="WorkerAnimation"/>
 </enum>
 
 <enum name="RendererSchedulerTaskUseCase">
@@ -42568,6 +42577,12 @@
   <int value="4" label="Received Answer - Too Large"/>
 </enum>
 
+<enum name="SearchBoxActivationSource">
+  <int value="0" label="Mouse Press"/>
+  <int value="1" label="Key Press"/>
+  <int value="2" label="Gesture Tap"/>
+</enum>
+
 <enum name="SearchEngine">
   <obsolete>
     Deprecated 8/2013. No longer generated.
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 6ad0b111..5728704f 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -2963,6 +2963,17 @@
   <summary>The state of a Play Store app search request.</summary>
 </histogram>
 
+<histogram name="Apps.AppListSearchBoxActivated"
+    enum="SearchBoxActivationSource">
+  <owner>newcomer@chromium.org</owner>
+  <summary>
+    The number of times the applist searchbox has been activated. This is split
+    by keystroke, mouse press, and gesture tap activations. This is gathered
+    each time the searchbox goes from inactive to active, not necessarily empty
+    to non-empty.
+  </summary>
+</histogram>
+
 <histogram name="Apps.AppListSearchCommenced" units="searches">
   <owner>tapted@chromium.org</owner>
   <summary>
diff --git a/tools/perf/core/perf_data_generator.py b/tools/perf/core/perf_data_generator.py
index 1d844dc0..24d1247 100755
--- a/tools/perf/core/perf_data_generator.py
+++ b/tools/perf/core/perf_data_generator.py
@@ -902,23 +902,21 @@
 
   return test_args
 
-def generate_non_telemetry_args():
+def generate_non_telemetry_args(test_name):
+  # --gtest-benchmark-name so the benchmark name is consistent with the test
+  # step's name. This is not always the same as the test binary's name (see
+  # crbug.com/870692).
   # --non-telemetry tells run_performance_tests.py that this test needs
   #   to be executed differently
   # --migrated-test tells run_performance_test_wrapper that this has
   #   non-telemetry test has been migrated to the new recipe.
   return [
+    '--gtest-benchmark-name', test_name,
     '--non-telemetry=true',
     '--migrated-test=true'
   ]
 
 def generate_performance_test(tester_config, test):
-  if test.get('telemetry', True):
-    test_args = generate_telemetry_args(tester_config)
-  else:
-    test_args = generate_non_telemetry_args()
-  # Append any additional args specific to an isolate
-  test_args += test.get('extra_args', [])
   isolate_name = test['isolate']
 
   # Check to see if the name is different than the isolate
@@ -926,6 +924,13 @@
   if test.get('test_suite', False):
     test_suite = test['test_suite']
 
+  if test.get('telemetry', True):
+    test_args = generate_telemetry_args(tester_config)
+  else:
+    test_args = generate_non_telemetry_args(test_name=test_suite)
+  # Append any additional args specific to an isolate
+  test_args += test.get('extra_args', [])
+
   result = {
     'args': test_args,
     'isolate_name': isolate_name,
diff --git a/tools/perf/core/perf_data_generator_unittest.py b/tools/perf/core/perf_data_generator_unittest.py
index 33c197d..dc3e65b 100644
--- a/tools/perf/core/perf_data_generator_unittest.py
+++ b/tools/perf/core/perf_data_generator_unittest.py
@@ -104,7 +104,8 @@
     expected_generated_test = {
         'override_compile_targets': ['angle_perftest'],
         'isolate_name': 'angle_perftest',
-        'args': ['--non-telemetry=true', '--migrated-test=true'],
+        'args': ['--gtest-benchmark-name', 'angle_perftest',
+                 '--non-telemetry=true', '--migrated-test=true'],
         'trigger_script': {
           'args': [
             '--multiple-dimension-script-verbose',
diff --git a/ui/accessibility/ax_tree_serializer.h b/ui/accessibility/ax_tree_serializer.h
index b97ad76..aef87e2 100644
--- a/ui/accessibility/ax_tree_serializer.h
+++ b/ui/accessibility/ax_tree_serializer.h
@@ -83,9 +83,10 @@
   bool SerializeChanges(AXSourceNode node,
                         AXTreeUpdateBase<AXNodeData, AXTreeData>* out_update);
 
-  // Delete the client subtree for this node, ensuring that the subtree
-  // is re-serialized.
-  void DeleteClientSubtree(AXSourceNode node);
+  // Invalidate the subtree rooted at this node, ensuring that the whole
+  // subtree is re-serialized the next time any of those nodes end up
+  // being serialized.
+  void InvalidateSubtree(AXSourceNode node);
 
   // Only for unit testing. Normally this class relies on getting a call
   // to SerializeChanges() every time the source tree changes. For unit
@@ -134,8 +135,8 @@
 
   // Return the least common ancestor of |node| that's in the client tree.
   // This just walks up the ancestors of |node| until it finds a node that's
-  // also in the client tree, and then calls LeastCommonAncestor on the
-  // source node and client node.
+  // also in the client tree and not inside an invalid subtree, and then calls
+  // LeastCommonAncestor on the source node and client node.
   AXSourceNode LeastCommonAncestor(AXSourceNode node);
 
   // Walk the subtree rooted at |node| and return true if any nodes that
@@ -147,8 +148,10 @@
 
   ClientTreeNode* ClientTreeNodeById(int32_t id);
 
-  // Delete the given client tree node and recursively delete all of its
-  // descendants.
+  // Invalidate the subtree rooted at this node.
+  void InvalidateClientSubtree(ClientTreeNode* client_node);
+
+  // Delete the client subtree rooted at this node.
   void DeleteClientSubtree(ClientTreeNode* client_node);
 
   // Helper function, called recursively with each new node to serialize.
@@ -178,13 +181,14 @@
 
 // In order to keep track of what nodes the client knows about, we keep a
 // representation of the client tree - just IDs and parent/child
-// relationships.
+// relationships, and a marker indicating whether it's been invalidated.
 struct AX_EXPORT ClientTreeNode {
   ClientTreeNode();
   virtual ~ClientTreeNode();
   int32_t id;
   ClientTreeNode* parent;
   std::vector<ClientTreeNode*> children;
+  bool invalid;
 };
 
 template <typename AXSourceNode, typename AXNodeData, typename AXTreeData>
@@ -261,9 +265,18 @@
 AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>::LeastCommonAncestor(
     AXSourceNode node) {
   // Walk up the tree until the source node's id also exists in the
-  // client tree, then call LeastCommonAncestor on those two nodes.
+  // client tree, whose parent is not invalid, then call LeastCommonAncestor
+  // on those two nodes.
+  //
+  // Note that it's okay if |client_node| is invalid - the LCA can be the
+  // root of an invalid subtree, since we're going to serialize the
+  // LCA. But it's not okay if |client_node->parent| is invalid - that means
+  // that we're inside of an invalid subtree that all needs to be
+  // re-serialized, so the LCA should be higher.
   ClientTreeNode* client_node = ClientTreeNodeById(tree_->GetId(node));
-  while (tree_->IsValid(node) && !client_node) {
+  while (
+      tree_->IsValid(node) &&
+      (!client_node || (client_node->parent && client_node->parent->invalid))) {
     node = tree_->GetParent(node);
     if (tree_->IsValid(node))
       client_node = ClientTreeNodeById(tree_->GetId(node));
@@ -385,12 +398,19 @@
 }
 
 template <typename AXSourceNode, typename AXNodeData, typename AXTreeData>
-void AXTreeSerializer<AXSourceNode,
-                      AXNodeData,
-                      AXTreeData>::DeleteClientSubtree(AXSourceNode node) {
+void AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>::InvalidateSubtree(
+    AXSourceNode node) {
   ClientTreeNode* client_node = ClientTreeNodeById(tree_->GetId(node));
   if (client_node)
-    DeleteClientSubtree(client_node);
+    InvalidateClientSubtree(client_node);
+}
+
+template <typename AXSourceNode, typename AXNodeData, typename AXTreeData>
+void AXTreeSerializer<AXSourceNode, AXNodeData, AXTreeData>::
+    InvalidateClientSubtree(ClientTreeNode* client_node) {
+  client_node->invalid = true;
+  for (size_t i = 0; i < client_node->children.size(); ++i)
+    InvalidateClientSubtree(client_node->children[i]);
 }
 
 template <typename AXSourceNode, typename AXNodeData, typename AXTreeData>
@@ -432,6 +452,9 @@
     client_id_map_[client_node->id] = client_node;
   }
 
+  // We're about to serialize it, so mark it as valid.
+  client_node->invalid = false;
+
   // Iterate over the ids of the children of |node|.
   // Create a set of the child ids so we can quickly look
   // up which children are new and which ones were there before.
@@ -521,10 +544,17 @@
     if (client_child_id_map.find(child_id) != client_child_id_map.end()) {
       ClientTreeNode* reused_child = client_child_id_map[child_id];
       client_node->children.push_back(reused_child);
+      // Re-serialize it if the child is marked as invalid, otherwise
+      // we don't have to because the client already has it.
+      if (reused_child->invalid) {
+        if (!SerializeChangedNodes(child, out_update))
+          return false;
+      }
     } else {
       ClientTreeNode* new_child = new ClientTreeNode();
       new_child->id = child_id;
       new_child->parent = client_node;
+      new_child->invalid = false;
       client_node->children.push_back(new_child);
       client_id_map_[child_id] = new_child;
       if (!SerializeChangedNodes(child, out_update))
diff --git a/ui/chromeos/search_box/search_box_view_base.cc b/ui/chromeos/search_box/search_box_view_base.cc
index 59d5c38a..9634b12 100644
--- a/ui/chromeos/search_box/search_box_view_base.cc
+++ b/ui/chromeos/search_box/search_box_view_base.cc
@@ -248,7 +248,8 @@
   content_container_->Layout();
 }
 
-void SearchBoxViewBase::SetSearchBoxActive(bool active) {
+void SearchBoxViewBase::SetSearchBoxActive(bool active,
+                                           ui::EventType event_type) {
   if (active == is_search_box_active_)
     return;
 
@@ -263,8 +264,10 @@
                                                  : search_box_color_);
   search_box_->SetCursorEnabled(active);
 
-  if (active)
+  if (active) {
     search_box_->RequestFocus();
+    RecordSearchBoxActivationHistogram(event_type);
+  }
 
   search_box_right_space_->SetVisible(!active);
 
@@ -278,11 +281,11 @@
   SchedulePaint();
 }
 
-bool SearchBoxViewBase::OnTextfieldEvent() {
+bool SearchBoxViewBase::OnTextfieldEvent(ui::EventType type) {
   if (is_search_box_active_)
     return false;
 
-  SetSearchBoxActive(true);
+  SetSearchBoxActive(true, type);
   return true;
 }
 
@@ -410,19 +413,19 @@
   search_box_->RequestFocus();
   UpdateModel(true);
   NotifyQueryChanged();
-  SetSearchBoxActive(true);
+  SetSearchBoxActive(true, ui::ET_KEY_PRESSED);
   UpdateCloseButtonVisisbility();
 }
 
 bool SearchBoxViewBase::HandleMouseEvent(views::Textfield* sender,
                                          const ui::MouseEvent& mouse_event) {
-  return OnTextfieldEvent();
+  return OnTextfieldEvent(mouse_event.type());
 }
 
 bool SearchBoxViewBase::HandleGestureEvent(
     views::Textfield* sender,
     const ui::GestureEvent& gesture_event) {
-  return OnTextfieldEvent();
+  return OnTextfieldEvent(gesture_event.type());
 }
 
 void SearchBoxViewBase::SetSearchBoxBackgroundCornerRadius(int corner_radius) {
@@ -448,7 +451,8 @@
       return;
     // If the event was within the searchbox bounds and in an inactive empty
     // search box, enable the search box.
-    SetSearchBoxActive(true);
+
+    SetSearchBoxActive(true, located_event->type());
   }
   located_event->SetHandled();
 }
diff --git a/ui/chromeos/search_box/search_box_view_base.h b/ui/chromeos/search_box/search_box_view_base.h
index 846dd0e..91f89e4 100644
--- a/ui/chromeos/search_box/search_box_view_base.h
+++ b/ui/chromeos/search_box/search_box_view_base.h
@@ -11,6 +11,7 @@
 #include "base/strings/string16.h"
 #include "ui/chromeos/search_box/search_box_constants.h"
 #include "ui/chromeos/search_box/search_box_export.h"
+#include "ui/events/event_constants.h"
 #include "ui/views/controls/button/image_button.h"
 #include "ui/views/controls/textfield/textfield_controller.h"
 #include "ui/views/widget/widget_delegate.h"
@@ -32,6 +33,16 @@
 class SearchBoxBackground;
 class SearchBoxImageButton;
 
+// These are used in histograms, do not remove/renumber entries. If you're
+// adding to this enum with the intention that it will be logged, update the
+// SearchBoxActivationSource enum listing in tools/metrics/histograms/enums.xml.
+enum class ActivationSource {
+  kMousePress = 0,
+  kKeyPress = 1,
+  kGestureTap = 2,
+  kMaxValue = kGestureTap,
+};
+
 // TODO(wutao): WidgetDelegateView owns itself and cannot be deleted from the
 // views hierarchy automatically. Make SearchBoxViewBase a subclass of View
 // instead of WidgetDelegateView.
@@ -70,10 +81,10 @@
   // the color of the placeholder text, and enables cursor blink. Setting the
   // search box inactive center aligns the placeholder text, sets the color, and
   // disables cursor blink.
-  void SetSearchBoxActive(bool active);
+  void SetSearchBoxActive(bool active, ui::EventType event_type);
 
   // Handles Gesture and Mouse Events sent from |search_box_|.
-  bool OnTextfieldEvent();
+  bool OnTextfieldEvent(ui::EventType type);
 
   // Overridden from views::View:
   gfx::Size CalculatePreferredSize() const override;
@@ -179,6 +190,9 @@
   virtual void SetupCloseButton() = 0;
   virtual void SetupBackButton() = 0;
 
+  // Records in histograms the activation of the searchbox.
+  virtual void RecordSearchBoxActivationHistogram(ui::EventType event_type){};
+
   // Gets the search box background.
   SearchBoxBackground* GetSearchBoxBackground() const;
 
diff --git a/ui/events/win/keyboard_hook_win.cc b/ui/events/win/keyboard_hook_win.cc
index 1b23ed8a..1688827 100644
--- a/ui/events/win/keyboard_hook_win.cc
+++ b/ui/events/win/keyboard_hook_win.cc
@@ -53,6 +53,10 @@
 
   HHOOK hook_ = nullptr;
 
+  // Tracks the last key down seen in order to determine if the current key
+  // event is a repeated key press.
+  DWORD last_key_down_ = 0;
+
   DISALLOW_COPY_AND_ASSIGN(KeyboardHookWin);
 };
 
@@ -114,6 +118,22 @@
                (ll_hooks->scanCode << 16) | (ll_hooks->flags & 0xFFFF),
                ll_hooks->time};
     KeyEvent key_event = KeyEventFromMSG(msg);
+
+    // Determine if this key event represents a repeated key event.
+    // A low level KB Hook is not passed a parameter or flag which indicates
+    // whether the key event is a repeat or not as it is called very early in
+    // input handling pipeline. That means we need to apply our own heuristic
+    // to determine if it should be treated as a repeated key press or not.
+    if (key_event.type() == ET_KEY_PRESSED) {
+      if (instance_->last_key_down_ != 0 &&
+          instance_->last_key_down_ == ll_hooks->vkCode) {
+        key_event.set_flags(key_event.flags() | EF_IS_REPEAT);
+      }
+      instance_->last_key_down_ = ll_hooks->vkCode;
+    } else {
+      DCHECK_EQ(key_event.type(), ET_KEY_RELEASED);
+      instance_->last_key_down_ = 0;
+    }
     instance_->ForwardCapturedKeyEvent(std::make_unique<KeyEvent>(key_event));
     return 1;
   }
diff --git a/ui/gl/gl_context.cc b/ui/gl/gl_context.cc
index 67ac664..466a321 100644
--- a/ui/gl/gl_context.cc
+++ b/ui/gl/gl_context.cc
@@ -194,8 +194,6 @@
 }
 
 void GLContext::BackpressureFenceWait(uint64_t fence) {}
-
-void GLContext::FlushForDebugging() {}
 #endif
 
 bool GLContext::HasExtension(const char* name) {
diff --git a/ui/gl/gl_context.h b/ui/gl/gl_context.h
index 6187b44..7c58ad62 100644
--- a/ui/gl/gl_context.h
+++ b/ui/gl/gl_context.h
@@ -162,11 +162,12 @@
   virtual bool WasAllocatedUsingRobustnessExtension();
 
   // Make this context current when used for context virtualization.
-  bool MakeVirtuallyCurrent(GLContext* virtual_context, GLSurface* surface);
+  virtual bool MakeVirtuallyCurrent(GLContext* virtual_context,
+                                    GLSurface* surface);
 
   // Notify this context that |virtual_context|, that was using us, is
   // being released or destroyed.
-  void OnReleaseVirtuallyCurrent(GLContext* virtual_context);
+  virtual void OnReleaseVirtuallyCurrent(GLContext* virtual_context);
 
   // Returns the GL version string. The context must be current.
   virtual std::string GetGLVersion();
@@ -202,8 +203,6 @@
   virtual uint64_t BackpressureFenceCreate();
   // Perform a client-side wait on a previously-created fence.
   virtual void BackpressureFenceWait(uint64_t fence);
-  // Temporary instrumentation for https://crbug.com/863817
-  virtual void FlushForDebugging();
 #endif
 
  protected:
diff --git a/ui/gl/gl_context_cgl.cc b/ui/gl/gl_context_cgl.cc
index c2f167d..32216b2f 100644
--- a/ui/gl/gl_context_cgl.cc
+++ b/ui/gl/gl_context_cgl.cc
@@ -214,6 +214,33 @@
   return true;
 }
 
+void GLContextCGL::OnReleaseVirtuallyCurrent(GLContext* virtual_context) {
+  TRACE_EVENT0("gpu", "GLContextCGL::OnReleaseVirtuallyCurrent");
+  // Flush before switching contexts, to avoid driver crashes.
+  // https://crbug.com/863817
+  if (IsCurrent(nullptr))
+    glFlush();
+  GLContext::OnReleaseVirtuallyCurrent(virtual_context);
+}
+
+bool GLContextCGL::MakeVirtuallyCurrent(GLContext* virtual_context,
+                                        GLSurface* surface) {
+  TRACE_EVENT0("gpu", "GLContextCGL::MakeVirtuallyCurrent");
+  // Flush before restoring the new context's state, to avoid driver crashes.
+  // https://crbug.com/863817
+  if (IsCurrent(nullptr))
+    glFlush();
+
+  // Restore the state of the new context.
+  if (!GLContext::MakeVirtuallyCurrent(virtual_context, surface))
+    return false;
+
+  // Flush after having restored this context's state, to avoid driver crashes.
+  // https://crbug.com/863817
+  glFlush();
+  return true;
+}
+
 YUVToRGBConverter* GLContextCGL::GetYUVToRGBConverter(
     const gfx::ColorSpace& color_space) {
   std::unique_ptr<YUVToRGBConverter>& yuv_to_rgb_converter =
@@ -229,9 +256,6 @@
 uint64_t GLContextCGL::BackpressureFenceCreate() {
   TRACE_EVENT0("gpu", "GLContextCGL::BackpressureFenceCreate");
 
-  // This flush will trigger a crash.
-  glFlush();
-
   if (gl::GLFence::IsSupported()) {
     next_backpressure_fence_ += 1;
     backpressure_fences_[next_backpressure_fence_] = GLFence::Create();
@@ -286,12 +310,6 @@
     backpressure_fences_.erase(backpressure_fences_.begin());
 }
 
-void GLContextCGL::FlushForDebugging() {
-  if (!context_ || CGLGetCurrentContext() != context_)
-    return;
-  glFlush();
-}
-
 bool GLContextCGL::MakeCurrent(GLSurface* surface) {
   DCHECK(context_);
 
diff --git a/ui/gl/gl_context_cgl.h b/ui/gl/gl_context_cgl.h
index e757639..3d2e7c2 100644
--- a/ui/gl/gl_context_cgl.h
+++ b/ui/gl/gl_context_cgl.h
@@ -34,11 +34,13 @@
   void* GetHandle() override;
   void SetSafeToForceGpuSwitch() override;
   bool ForceGpuSwitchIfNeeded() override;
+  void OnReleaseVirtuallyCurrent(GLContext* virtual_context) override;
+  bool MakeVirtuallyCurrent(GLContext* virtual_context,
+                            GLSurface* surface) override;
   YUVToRGBConverter* GetYUVToRGBConverter(
       const gfx::ColorSpace& color_space) override;
   uint64_t BackpressureFenceCreate() override;
   void BackpressureFenceWait(uint64_t fence) override;
-  void FlushForDebugging() override;
 
  protected:
   ~GLContextCGL() override;
diff --git a/ui/ozone/common/linux/BUILD.gn b/ui/ozone/common/linux/BUILD.gn
index d10a9786..de979e68 100644
--- a/ui/ozone/common/linux/BUILD.gn
+++ b/ui/ozone/common/linux/BUILD.gn
@@ -11,10 +11,9 @@
   sources = [
     "drm_util_linux.cc",
     "drm_util_linux.h",
-    "gbm_buffer.cc",
     "gbm_buffer.h",
-    "gbm_device.cc",
     "gbm_device.h",
+    "gbm_wrapper.cc",
   ]
 
   deps = [
diff --git a/ui/ozone/common/linux/gbm_buffer.cc b/ui/ozone/common/linux/gbm_buffer.cc
deleted file mode 100644
index 34b0e5d..0000000
--- a/ui/ozone/common/linux/gbm_buffer.cc
+++ /dev/null
@@ -1,206 +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 "ui/ozone/common/linux/gbm_buffer.h"
-
-#include <gbm.h>
-
-#include "base/posix/eintr_wrapper.h"
-#include "ui/gfx/buffer_format_util.h"
-#include "ui/gfx/native_pixmap_handle.h"
-#include "ui/ozone/common/linux/drm_util_linux.h"
-
-namespace ui {
-
-namespace {
-
-std::unique_ptr<GbmBuffer> CreateBufferForBO(gbm_device* gbm,
-                                             struct gbm_bo* bo,
-                                             uint32_t format,
-                                             const gfx::Size& size,
-                                             uint32_t flags) {
-  DCHECK(bo);
-  std::vector<base::ScopedFD> fds;
-  std::vector<gfx::NativePixmapPlane> planes;
-
-  const uint64_t modifier = gbm_bo_get_format_modifier(bo);
-  for (size_t i = 0; i < gbm_bo_get_num_planes(bo); ++i) {
-    // The fd returned by gbm_bo_get_fd is not ref-counted and need to be
-    // kept open for the lifetime of the buffer.
-    base::ScopedFD fd(gbm_bo_get_plane_fd(bo, i));
-
-    // TODO(dcastagna): support multiple fds.
-    // crbug.com/642410
-    if (!i) {
-      if (!fd.is_valid()) {
-        PLOG(ERROR) << "Failed to export buffer to dma_buf";
-        gbm_bo_destroy(bo);
-        return nullptr;
-      }
-      fds.emplace_back(std::move(fd));
-    }
-
-    planes.emplace_back(gbm_bo_get_plane_stride(bo, i),
-                        gbm_bo_get_plane_offset(bo, i),
-                        gbm_bo_get_plane_size(bo, i), modifier);
-  }
-  return std::make_unique<GbmBuffer>(bo, format, flags, modifier,
-                                     std::move(fds), size, std::move(planes));
-}
-
-}  // namespace
-
-// static
-std::unique_ptr<GbmBuffer> GbmBuffer::CreateBufferWithModifiers(
-    gbm_device* gbm,
-    uint32_t format,
-    const gfx::Size& size,
-    uint32_t flags,
-    const std::vector<uint64_t>& modifiers) {
-  struct gbm_bo* bo =
-      gbm_bo_create_with_modifiers(gbm, size.width(), size.height(), format,
-                                   modifiers.data(), modifiers.size());
-  if (!bo)
-    return nullptr;
-
-  return CreateBufferForBO(gbm, bo, format, size, flags);
-}
-
-// static
-std::unique_ptr<GbmBuffer> GbmBuffer::CreateBuffer(gbm_device* gbm,
-                                                   uint32_t format,
-                                                   const gfx::Size& size,
-                                                   uint32_t flags) {
-  struct gbm_bo* bo =
-      gbm_bo_create(gbm, size.width(), size.height(), format, flags);
-  if (!bo)
-    return nullptr;
-
-  return CreateBufferForBO(gbm, bo, format, size, flags);
-}
-
-// static
-std::unique_ptr<GbmBuffer> GbmBuffer::CreateBufferFromFds(
-    gbm_device* gbm,
-    uint32_t format,
-    const gfx::Size& size,
-    std::vector<base::ScopedFD> fds,
-    const std::vector<gfx::NativePixmapPlane>& planes) {
-  DCHECK_LE(fds.size(), planes.size());
-  DCHECK_EQ(planes[0].offset, 0);
-
-  // Try to use scanout if supported.
-  int gbm_flags = GBM_BO_USE_SCANOUT | GBM_BO_USE_TEXTURING;
-  if (!gbm_device_is_format_supported(gbm, format, gbm_flags))
-    gbm_flags &= ~GBM_BO_USE_SCANOUT;
-
-  struct gbm_bo* bo = nullptr;
-  if (gbm_device_is_format_supported(gbm, format, gbm_flags)) {
-    struct gbm_import_fd_planar_data fd_data;
-    fd_data.width = size.width();
-    fd_data.height = size.height();
-    fd_data.format = format;
-
-    DCHECK_LE(planes.size(), 3u);
-    for (size_t i = 0; i < planes.size(); ++i) {
-      fd_data.fds[i] = fds[i < fds.size() ? i : 0].get();
-      fd_data.strides[i] = planes[i].stride;
-      fd_data.offsets[i] = planes[i].offset;
-      fd_data.format_modifiers[i] = planes[i].modifier;
-    }
-
-    // The fd passed to gbm_bo_import is not ref-counted and need to be
-    // kept open for the lifetime of the buffer.
-    bo = gbm_bo_import(gbm, GBM_BO_IMPORT_FD_PLANAR, &fd_data, gbm_flags);
-    if (!bo) {
-      LOG(ERROR) << "nullptr returned from gbm_bo_import";
-      return nullptr;
-    }
-  }
-
-  return std::make_unique<GbmBuffer>(bo, format, gbm_flags, planes[0].modifier,
-                                     std::move(fds), size, std::move(planes));
-}
-
-GbmBuffer::GbmBuffer(struct gbm_bo* bo,
-                     uint32_t format,
-                     uint32_t flags,
-                     uint64_t modifier,
-                     std::vector<base::ScopedFD> fds,
-                     const gfx::Size& size,
-                     std::vector<gfx::NativePixmapPlane> planes)
-    : bo_(bo),
-      format_(format),
-      format_modifier_(modifier),
-      flags_(flags),
-      fds_(std::move(fds)),
-      size_(size),
-      planes_(std::move(planes)) {}
-
-GbmBuffer::~GbmBuffer() {
-  gbm_bo_destroy(bo_);
-}
-
-gfx::BufferFormat GbmBuffer::GetBufferFormat() const {
-  return ui::GetBufferFormatFromFourCCFormat(format_);
-}
-
-bool GbmBuffer::AreFdsValid() const {
-  if (fds_.empty())
-    return false;
-
-  for (const auto& fd : fds_) {
-    if (fd.get() == -1)
-      return false;
-  }
-  return true;
-}
-
-int GbmBuffer::GetFd(size_t index) const {
-  DCHECK_LT(index, fds_.size());
-  return fds_[index].get();
-}
-
-int GbmBuffer::GetStride(size_t index) const {
-  DCHECK_LT(index, planes_.size());
-  return planes_[index].stride;
-}
-
-int GbmBuffer::GetOffset(size_t index) const {
-  DCHECK_LT(index, planes_.size());
-  return planes_[index].offset;
-}
-
-size_t GbmBuffer::GetPlaneSize(size_t index) const {
-  DCHECK_LT(index, planes_.size());
-  return planes_[index].size;
-}
-
-uint32_t GbmBuffer::GetHandle() const {
-  return gbm_bo_get_handle(bo_).u32;
-}
-
-gfx::NativePixmapHandle GbmBuffer::ExportHandle() const {
-  gfx::NativePixmapHandle handle;
-  gfx::BufferFormat format = ui::GetBufferFormatFromFourCCFormat(format_);
-  // TODO(dcastagna): Use gbm_bo_get_num_planes once all the formats we use are
-  // supported by gbm.
-  for (size_t i = 0; i < gfx::NumberOfPlanesForBufferFormat(format); ++i) {
-    // Some formats (e.g: YVU_420) might have less than one fd per plane.
-    if (i < fd_count()) {
-      base::ScopedFD scoped_fd(HANDLE_EINTR(dup(GetFd(i))));
-      if (!scoped_fd.is_valid()) {
-        PLOG(ERROR) << "dup";
-        return gfx::NativePixmapHandle();
-      }
-      handle.fds.emplace_back(
-          base::FileDescriptor(scoped_fd.release(), true /* auto_close */));
-    }
-    handle.planes.emplace_back(GetStride(i), GetOffset(i), GetPlaneSize(i),
-                               format_modifier());
-  }
-  return handle;
-}
-
-}  // namespace ui
diff --git a/ui/ozone/common/linux/gbm_buffer.h b/ui/ozone/common/linux/gbm_buffer.h
index b2b28f8..12c13bb 100644
--- a/ui/ozone/common/linux/gbm_buffer.h
+++ b/ui/ozone/common/linux/gbm_buffer.h
@@ -5,82 +5,35 @@
 #ifndef UI_OZONE_COMMON_LINUX_GBM_BUFFER_H_
 #define UI_OZONE_COMMON_LINUX_GBM_BUFFER_H_
 
-#include <vector>
+#include <inttypes.h>
 
-#include "base/files/scoped_file.h"
-#include "base/macros.h"
 #include "ui/gfx/buffer_types.h"
 #include "ui/gfx/geometry/size.h"
-
-struct gbm_bo;
-struct gbm_device;
-
-namespace gfx {
-struct NativePixmapHandle;
-struct NativePixmapPlane;
-}  // namespace gfx
+#include "ui/gfx/native_pixmap_handle.h"
 
 namespace ui {
 
 class GbmBuffer {
  public:
-  static std::unique_ptr<GbmBuffer> CreateBuffer(gbm_device* gbm,
-                                                 uint32_t format,
-                                                 const gfx::Size& size,
-                                                 uint32_t flags);
-  static std::unique_ptr<GbmBuffer> CreateBufferWithModifiers(
-      gbm_device* gbm,
-      uint32_t format,
-      const gfx::Size& size,
-      uint32_t flags,
-      const std::vector<uint64_t>& modifiers);
-  static std::unique_ptr<GbmBuffer> CreateBufferFromFds(
-      gbm_device* gbm,
-      uint32_t format,
-      const gfx::Size& size,
-      std::vector<base::ScopedFD> fds,
-      const std::vector<gfx::NativePixmapPlane>& planes);
+  virtual ~GbmBuffer() {}
 
-  GbmBuffer(gbm_bo* bo,
-            uint32_t format,
-            uint32_t flags,
-            uint64_t modifier,
-            std::vector<base::ScopedFD> fds,
-            const gfx::Size& size,
-            std::vector<gfx::NativePixmapPlane> planes);
-  ~GbmBuffer();
-
-  uint32_t format() const { return format_; }
-  uint64_t format_modifier() const { return format_modifier_; }
-  uint32_t flags() const { return flags_; }
-  size_t fd_count() const { return fds_.size(); }
-  gbm_bo* bo() const { return bo_; }
-  // TODO(reveman): This should not be needed once crbug.com/597932 is fixed,
-  // as the size would be queried directly from the underlying bo.
-  gfx::Size size() const { return size_; }
-  gfx::BufferFormat GetBufferFormat() const;
-  bool AreFdsValid() const;
-  int GetFd(size_t plane) const;
-  int GetStride(size_t plane) const;
-  int GetOffset(size_t plane) const;
-  size_t GetPlaneSize(size_t plane) const;
-  uint32_t GetHandle() const;
-  gfx::NativePixmapHandle ExportHandle() const;
-
- private:
-  gbm_bo* bo_ = nullptr;
-
-  uint32_t format_ = 0;
-  uint64_t format_modifier_ = 0;
-  uint32_t flags_ = 0;
-
-  std::vector<base::ScopedFD> fds_;
-
-  gfx::Size size_;
-
-  std::vector<gfx::NativePixmapPlane> planes_;
-
-  DISALLOW_COPY_AND_ASSIGN(GbmBuffer);
+  virtual uint32_t GetFormat() const = 0;
+  virtual uint64_t GetFormatModifier() const = 0;
+  virtual uint32_t GetFlags() const = 0;
+  virtual size_t GetFdCount() const = 0;
+  // TODO(reveman): This should not be needed once crbug.com/597932 is
+  // fixed, as the size would be queried directly from the underlying bo.
+  virtual gfx::Size GetSize() const = 0;
+  virtual gfx::BufferFormat GetBufferFormat() const = 0;
+  virtual bool AreFdsValid() const = 0;
+  virtual size_t GetNumPlanes() const = 0;
+  virtual int GetPlaneFd(size_t plane) const = 0;
+  virtual uint32_t GetPlaneHandle(size_t plane) const = 0;
+  virtual int GetPlaneStride(size_t plane) const = 0;
+  virtual int GetPlaneOffset(size_t plane) const = 0;
+  virtual size_t GetPlaneSize(size_t plane) const = 0;
+  virtual uint32_t GetHandle() const = 0;
+  virtual gfx::NativePixmapHandle ExportHandle() const = 0;
 };
 
 }  // namespace ui
diff --git a/ui/ozone/common/linux/gbm_device.cc b/ui/ozone/common/linux/gbm_device.cc
deleted file mode 100644
index 8892869..0000000
--- a/ui/ozone/common/linux/gbm_device.cc
+++ /dev/null
@@ -1,26 +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 "ui/ozone/common/linux/gbm_device.h"
-
-#include <gbm.h>
-
-namespace ui {
-
-GbmDevice::GbmDevice() {}
-
-GbmDevice::~GbmDevice() {
-  if (device_) {
-    gbm_device_destroy(device_);
-    device_ = nullptr;
-  }
-}
-
-bool GbmDevice::Initialize(int fd) {
-  DCHECK(!device_);
-  device_ = gbm_create_device(fd);
-  return !!device_;
-}
-
-}  // namespace ui
diff --git a/ui/ozone/common/linux/gbm_device.h b/ui/ozone/common/linux/gbm_device.h
index d498c931..29d9e0a 100644
--- a/ui/ozone/common/linux/gbm_device.h
+++ b/ui/ozone/common/linux/gbm_device.h
@@ -7,24 +7,32 @@
 
 #include "base/files/file.h"
 #include "base/macros.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/native_pixmap_handle.h"
 
-struct gbm_device;
+#include <gbm.h>
 
 namespace ui {
 
+class GbmBuffer;
+
 class GbmDevice {
  public:
-  GbmDevice();
-  ~GbmDevice();
+  virtual ~GbmDevice() {}
 
-  gbm_device* device() const { return device_; }
-
-  bool Initialize(int fd);
-
- private:
-  gbm_device* device_ = nullptr;
-
-  DISALLOW_COPY_AND_ASSIGN(GbmDevice);
+  virtual std::unique_ptr<GbmBuffer> CreateBuffer(uint32_t format,
+                                                  const gfx::Size& size,
+                                                  uint32_t flags) = 0;
+  virtual std::unique_ptr<GbmBuffer> CreateBufferWithModifiers(
+      uint32_t format,
+      const gfx::Size& size,
+      uint32_t flags,
+      const std::vector<uint64_t>& modifiers) = 0;
+  virtual std::unique_ptr<GbmBuffer> CreateBufferFromFds(
+      uint32_t format,
+      const gfx::Size& size,
+      std::vector<base::ScopedFD> fds,
+      const std::vector<gfx::NativePixmapPlane>& planes) = 0;
 };
 
 }  // namespace ui
diff --git a/ui/ozone/common/linux/gbm_wrapper.cc b/ui/ozone/common/linux/gbm_wrapper.cc
new file mode 100644
index 0000000..3c15127
--- /dev/null
+++ b/ui/ozone/common/linux/gbm_wrapper.cc
@@ -0,0 +1,237 @@
+// 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/ozone/common/linux/gbm_wrapper.h"
+
+#include <gbm.h>
+
+#include "base/posix/eintr_wrapper.h"
+#include "ui/gfx/buffer_format_util.h"
+#include "ui/ozone/common/linux/drm_util_linux.h"
+#include "ui/ozone/common/linux/gbm_buffer.h"
+#include "ui/ozone/common/linux/gbm_device.h"
+
+namespace gbm_wrapper {
+
+class Buffer final : public ui::GbmBuffer {
+ public:
+  Buffer(struct gbm_bo* bo,
+         uint32_t format,
+         uint32_t flags,
+         uint64_t modifier,
+         std::vector<base::ScopedFD> fds,
+         const gfx::Size& size,
+         std::vector<gfx::NativePixmapPlane> planes)
+      : bo_(bo),
+        format_(format),
+        format_modifier_(modifier),
+        flags_(flags),
+        fds_(std::move(fds)),
+        size_(size),
+        planes_(std::move(planes)) {}
+
+  ~Buffer() override { gbm_bo_destroy(bo_); }
+
+  uint32_t GetFormat() const override { return format_; }
+  uint64_t GetFormatModifier() const override { return format_modifier_; }
+  uint32_t GetFlags() const override { return flags_; }
+  size_t GetFdCount() const override { return fds_.size(); }
+  // TODO(reveman): This should not be needed once crbug.com/597932 is fixed,
+  // as the size would be queried directly from the underlying bo.
+  gfx::Size GetSize() const override { return size_; }
+  gfx::BufferFormat GetBufferFormat() const override {
+    return ui::GetBufferFormatFromFourCCFormat(format_);
+  }
+  bool AreFdsValid() const override {
+    if (fds_.empty())
+      return false;
+
+    for (const auto& fd : fds_) {
+      if (fd.get() == -1)
+        return false;
+    }
+    return true;
+  }
+  size_t GetNumPlanes() const override { return planes_.size(); }
+  int GetPlaneFd(size_t plane) const override {
+    DCHECK_LT(plane, fds_.size());
+    return fds_[plane].get();
+  }
+  int GetPlaneStride(size_t plane) const override {
+    DCHECK_LT(plane, planes_.size());
+    return planes_[plane].stride;
+  }
+  int GetPlaneOffset(size_t plane) const override {
+    DCHECK_LT(plane, planes_.size());
+    return planes_[plane].offset;
+  }
+  size_t GetPlaneSize(size_t plane) const override {
+    DCHECK_LT(plane, planes_.size());
+    return planes_[plane].size;
+  }
+  uint32_t GetPlaneHandle(size_t plane) const override {
+    DCHECK_LT(plane, planes_.size());
+    return gbm_bo_get_plane_handle(bo_, plane).u32;
+  }
+  uint32_t GetHandle() const override { return gbm_bo_get_handle(bo_).u32; }
+  gfx::NativePixmapHandle ExportHandle() const override {
+    gfx::NativePixmapHandle handle;
+    gfx::BufferFormat format = ui::GetBufferFormatFromFourCCFormat(format_);
+    // TODO(dcastagna): Use gbm_bo_get_num_planes once all the formats we use
+    // are supported by gbm.
+    for (size_t i = 0; i < gfx::NumberOfPlanesForBufferFormat(format); ++i) {
+      // Some formats (e.g: YVU_420) might have less than one fd per plane.
+      if (i < fds_.size()) {
+        base::ScopedFD scoped_fd(HANDLE_EINTR(dup(GetPlaneFd(i))));
+        if (!scoped_fd.is_valid()) {
+          PLOG(ERROR) << "dup";
+          return gfx::NativePixmapHandle();
+        }
+        handle.fds.emplace_back(
+            base::FileDescriptor(scoped_fd.release(), true /* auto_close */));
+      }
+      handle.planes.emplace_back(GetPlaneStride(i), GetPlaneOffset(i),
+                                 GetPlaneSize(i), GetFormatModifier());
+    }
+    return handle;
+  }
+
+ private:
+  gbm_bo* bo_ = nullptr;
+
+  uint32_t format_ = 0;
+  uint64_t format_modifier_ = 0;
+  uint32_t flags_ = 0;
+
+  std::vector<base::ScopedFD> fds_;
+
+  gfx::Size size_;
+
+  std::vector<gfx::NativePixmapPlane> planes_;
+
+  DISALLOW_COPY_AND_ASSIGN(Buffer);
+};
+
+std::unique_ptr<Buffer> CreateBufferForBO(struct gbm_bo* bo,
+                                          uint32_t format,
+                                          const gfx::Size& size,
+                                          uint32_t flags) {
+  DCHECK(bo);
+  std::vector<base::ScopedFD> fds;
+  std::vector<gfx::NativePixmapPlane> planes;
+
+  const uint64_t modifier = gbm_bo_get_format_modifier(bo);
+  for (size_t i = 0; i < gbm_bo_get_num_planes(bo); ++i) {
+    // The fd returned by gbm_bo_get_fd is not ref-counted and need to be
+    // kept open for the lifetime of the buffer.
+    base::ScopedFD fd(gbm_bo_get_plane_fd(bo, i));
+
+    // TODO(dcastagna): support multiple fds.
+    // crbug.com/642410
+    if (!i) {
+      if (!fd.is_valid()) {
+        PLOG(ERROR) << "Failed to export buffer to dma_buf";
+        gbm_bo_destroy(bo);
+        return nullptr;
+      }
+      fds.emplace_back(std::move(fd));
+    }
+
+    planes.emplace_back(gbm_bo_get_plane_stride(bo, i),
+                        gbm_bo_get_plane_offset(bo, i),
+                        gbm_bo_get_plane_size(bo, i), modifier);
+  }
+  return std::make_unique<Buffer>(bo, format, flags, modifier, std::move(fds),
+                                  size, std::move(planes));
+}
+
+class Device final : public ui::GbmDevice {
+ public:
+  Device(gbm_device* device) : device_(device) {}
+  ~Device() override { gbm_device_destroy(device_); }
+
+  std::unique_ptr<ui::GbmBuffer> CreateBuffer(uint32_t format,
+                                              const gfx::Size& size,
+                                              uint32_t flags) override {
+    struct gbm_bo* bo =
+        gbm_bo_create(device_, size.width(), size.height(), format, flags);
+    if (!bo)
+      return nullptr;
+
+    return CreateBufferForBO(bo, format, size, flags);
+  }
+
+  std::unique_ptr<ui::GbmBuffer> CreateBufferWithModifiers(
+      uint32_t format,
+      const gfx::Size& size,
+      uint32_t flags,
+      const std::vector<uint64_t>& modifiers) override {
+    struct gbm_bo* bo = gbm_bo_create_with_modifiers(
+        device_, size.width(), size.height(), format, modifiers.data(),
+        modifiers.size());
+    if (!bo)
+      return nullptr;
+
+    return CreateBufferForBO(bo, format, size, flags);
+  }
+
+  std::unique_ptr<ui::GbmBuffer> CreateBufferFromFds(
+      uint32_t format,
+      const gfx::Size& size,
+      std::vector<base::ScopedFD> fds,
+      const std::vector<gfx::NativePixmapPlane>& planes) override {
+    DCHECK_LE(fds.size(), planes.size());
+    DCHECK_EQ(planes[0].offset, 0);
+
+    // Try to use scanout if supported.
+    int gbm_flags = GBM_BO_USE_SCANOUT | GBM_BO_USE_TEXTURING;
+    if (!gbm_device_is_format_supported(device_, format, gbm_flags))
+      gbm_flags &= ~GBM_BO_USE_SCANOUT;
+
+    struct gbm_bo* bo = nullptr;
+    if (gbm_device_is_format_supported(device_, format, gbm_flags)) {
+      struct gbm_import_fd_planar_data fd_data;
+      fd_data.width = size.width();
+      fd_data.height = size.height();
+      fd_data.format = format;
+
+      DCHECK_LE(planes.size(), 3u);
+      for (size_t i = 0; i < planes.size(); ++i) {
+        fd_data.fds[i] = fds[i < fds.size() ? i : 0].get();
+        fd_data.strides[i] = planes[i].stride;
+        fd_data.offsets[i] = planes[i].offset;
+        fd_data.format_modifiers[i] = planes[i].modifier;
+      }
+
+      // The fd passed to gbm_bo_import is not ref-counted and need to be
+      // kept open for the lifetime of the buffer.
+      bo = gbm_bo_import(device_, GBM_BO_IMPORT_FD_PLANAR, &fd_data, gbm_flags);
+      if (!bo) {
+        LOG(ERROR) << "nullptr returned from gbm_bo_import";
+        return nullptr;
+      }
+    }
+
+    return std::make_unique<Buffer>(bo, format, gbm_flags, planes[0].modifier,
+                                    std::move(fds), size, std::move(planes));
+  }
+
+ private:
+  gbm_device* device_;
+
+  DISALLOW_COPY_AND_ASSIGN(Device);
+};
+
+}  // namespace gbm_wrapper
+
+namespace ui {
+
+std::unique_ptr<GbmDevice> CreateGbmDevice(int fd) {
+  gbm_device* device = gbm_create_device(fd);
+  if (!device)
+    return nullptr;
+  return std::make_unique<gbm_wrapper::Device>(device);
+}
+
+}  // namespace ui
diff --git a/ui/ozone/common/linux/gbm_wrapper.h b/ui/ozone/common/linux/gbm_wrapper.h
new file mode 100644
index 0000000..926549d
--- /dev/null
+++ b/ui/ozone/common/linux/gbm_wrapper.h
@@ -0,0 +1,18 @@
+// 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_OZONE_COMMON_LINUX_GBM_WRAPPER_H_
+#define UI_OZONE_COMMON_LINUX_GBM_WRAPPER_H_
+
+#include <memory>
+
+#include "ui/ozone/common/linux/gbm_device.h"
+
+namespace ui {
+
+std::unique_ptr<ui::GbmDevice> CreateGbmDevice(int fd);
+
+}  // namespace ui
+
+#endif  // UI_OZONE_COMMON_LINUX_GBM_WRAPPER_H_
diff --git a/ui/ozone/platform/drm/gpu/drm_device.cc b/ui/ozone/platform/drm/gpu/drm_device.cc
index fc2a3b7..f380a97 100644
--- a/ui/ozone/platform/drm/gpu/drm_device.cc
+++ b/ui/ozone/platform/drm/gpu/drm_device.cc
@@ -239,11 +239,13 @@
 
 DrmDevice::DrmDevice(const base::FilePath& device_path,
                      base::File file,
-                     bool is_primary_device)
+                     bool is_primary_device,
+                     std::unique_ptr<GbmDevice> gbm)
     : device_path_(device_path),
       file_(std::move(file)),
       page_flip_manager_(new PageFlipManager()),
-      is_primary_device_(is_primary_device) {}
+      is_primary_device_(is_primary_device),
+      gbm_(std::move(gbm)) {}
 
 DrmDevice::~DrmDevice() {}
 
@@ -275,11 +277,6 @@
   watcher_.reset(
       new IOWatcher(file_.GetPlatformFile(), page_flip_manager_.get()));
 
-  if (!gbm_.Initialize(file_.GetPlatformFile())) {
-    PLOG(ERROR) << "Unable to initialize GBM for " << device_path_.value();
-    return false;
-  }
-
   return true;
 }
 
diff --git a/ui/ozone/platform/drm/gpu/drm_device.h b/ui/ozone/platform/drm/gpu/drm_device.h
index 30ee350..2d229ee 100644
--- a/ui/ozone/platform/drm/gpu/drm_device.h
+++ b/ui/ozone/platform/drm/gpu/drm_device.h
@@ -73,7 +73,8 @@
 
   DrmDevice(const base::FilePath& device_path,
             base::File file,
-            bool is_primary_device);
+            bool is_primary_device,
+            std::unique_ptr<GbmDevice> gbm_device);
 
   bool is_primary_device() const { return is_primary_device_; }
 
@@ -246,7 +247,7 @@
 
   HardwareDisplayPlaneManager* plane_manager() { return plane_manager_.get(); }
 
-  gbm_device* gbm_device() const { return gbm_.device(); }
+  GbmDevice* gbm_device() const { return gbm_.get(); }
 
  protected:
   friend class base::RefCountedThreadSafe<DrmDevice>;
@@ -259,8 +260,6 @@
   class IOWatcher;
   class PageFlipManager;
 
-  GbmDevice gbm_;
-
   // Path to the DRM device (in sysfs).
   const base::FilePath device_path_;
 
@@ -276,6 +275,8 @@
 
   bool allow_addfb2_modifiers_;
 
+  std::unique_ptr<GbmDevice> gbm_;
+
   DISALLOW_COPY_AND_ASSIGN(DrmDevice);
 };
 
diff --git a/ui/ozone/platform/drm/gpu/drm_device_generator.cc b/ui/ozone/platform/drm/gpu/drm_device_generator.cc
index 9f293eb5..0c32fb74 100644
--- a/ui/ozone/platform/drm/gpu/drm_device_generator.cc
+++ b/ui/ozone/platform/drm/gpu/drm_device_generator.cc
@@ -16,16 +16,4 @@
 DrmDeviceGenerator::~DrmDeviceGenerator() {
 }
 
-scoped_refptr<DrmDevice> DrmDeviceGenerator::CreateDevice(
-    const base::FilePath& device_path,
-    base::File file,
-    bool is_primary_device) {
-  scoped_refptr<DrmDevice> drm =
-      new DrmDevice(device_path, std::move(file), is_primary_device);
-  if (drm->Initialize())
-    return drm;
-
-  return nullptr;
-}
-
 }  // namespace ui
diff --git a/ui/ozone/platform/drm/gpu/drm_device_generator.h b/ui/ozone/platform/drm/gpu/drm_device_generator.h
index d0d3d26f..f85d05cc 100644
--- a/ui/ozone/platform/drm/gpu/drm_device_generator.h
+++ b/ui/ozone/platform/drm/gpu/drm_device_generator.h
@@ -23,7 +23,7 @@
   virtual scoped_refptr<DrmDevice> CreateDevice(
       const base::FilePath& device_path,
       base::File file,
-      bool is_primary_device);
+      bool is_primary_device) = 0;
 
  public:
   DISALLOW_COPY_AND_ASSIGN(DrmDeviceGenerator);
diff --git a/ui/ozone/platform/drm/gpu/drm_thread.cc b/ui/ozone/platform/drm/gpu/drm_thread.cc
index 6d8de41..a90f3c4 100644
--- a/ui/ozone/platform/drm/gpu/drm_thread.cc
+++ b/ui/ozone/platform/drm/gpu/drm_thread.cc
@@ -17,6 +17,7 @@
 #include "ui/gfx/presentation_feedback.h"
 #include "ui/ozone/common/linux/drm_util_linux.h"
 #include "ui/ozone/common/linux/gbm_device.h"
+#include "ui/ozone/common/linux/gbm_wrapper.h"
 #include "ui/ozone/platform/drm/common/drm_util.h"
 #include "ui/ozone/platform/drm/gpu/drm_buffer.h"
 #include "ui/ozone/platform/drm/gpu/drm_device_generator.h"
@@ -35,21 +36,20 @@
 
 namespace {
 
-scoped_refptr<DrmFramebuffer> AddFramebuffersForBo(
+scoped_refptr<DrmFramebuffer> AddFramebuffersForBuffer(
     const scoped_refptr<DrmDevice>& drm,
-    gbm_bo* bo,
-    uint32_t format,
-    uint64_t format_modifier) {
+    GbmBuffer* buffer) {
+  gfx::Size size = buffer->GetSize();
   DrmFramebuffer::AddFramebufferParams params;
-  params.format = format;
-  params.modifier = format_modifier;
-  params.width = gbm_bo_get_width(bo);
-  params.height = gbm_bo_get_height(bo);
-  params.num_planes = gbm_bo_get_num_planes(bo);
+  params.format = buffer->GetFormat();
+  params.modifier = buffer->GetFormatModifier();
+  params.width = size.width();
+  params.height = size.height();
+  params.num_planes = buffer->GetNumPlanes();
   for (size_t i = 0; i < params.num_planes; ++i) {
-    params.handles[i] = gbm_bo_get_plane_handle(bo, i).u32;
-    params.strides[i] = gbm_bo_get_plane_stride(bo, i);
-    params.offsets[i] = gbm_bo_get_plane_offset(bo, i);
+    params.handles[i] = buffer->GetPlaneHandle(i);
+    params.strides[i] = buffer->GetPlaneStride(i);
+    params.offsets[i] = buffer->GetPlaneOffset(i);
   }
 
   // AddFramebuffer2 only considers the modifiers if addfb_flags has
@@ -97,18 +97,16 @@
                               scoped_refptr<DrmFramebuffer>* out_framebuffer) {
   std::unique_ptr<GbmBuffer> buffer;
   if (modifiers.empty())
-    buffer =
-        GbmBuffer::CreateBuffer(drm->gbm_device(), fourcc_format, size, flags);
+    buffer = drm->gbm_device()->CreateBuffer(fourcc_format, size, flags);
   else
-    buffer = GbmBuffer::CreateBufferWithModifiers(
-        drm->gbm_device(), fourcc_format, size, flags, modifiers);
+    buffer = drm->gbm_device()->CreateBufferWithModifiers(fourcc_format, size,
+                                                          flags, modifiers);
   if (!buffer)
     return;
 
   scoped_refptr<DrmFramebuffer> framebuffer;
   if (flags & GBM_BO_USE_SCANOUT) {
-    framebuffer = AddFramebuffersForBo(drm, buffer->bo(), buffer->format(),
-                                       buffer->format_modifier());
+    framebuffer = AddFramebuffersForBuffer(drm, buffer.get());
     if (!framebuffer)
       return;
   }
@@ -130,18 +128,17 @@
     std::unique_ptr<GbmBuffer> buffer;
 
     if (modifiers.size() > 0) {
-      buffer = GbmBuffer::CreateBufferWithModifiers(
-          drm->gbm_device(), format, size, GBM_BO_USE_SCANOUT, modifiers);
+      buffer = drm->gbm_device()->CreateBufferWithModifiers(
+          format, size, GBM_BO_USE_SCANOUT, modifiers);
     } else {
-      buffer = GbmBuffer::CreateBuffer(drm->gbm_device(), format, size,
-                                       GBM_BO_USE_SCANOUT);
+      buffer =
+          drm->gbm_device()->CreateBuffer(format, size, GBM_BO_USE_SCANOUT);
     }
 
     if (!buffer)
       return nullptr;
 
-    return AddFramebuffersForBo(drm, buffer->bo(), buffer->format(),
-                                buffer->format_modifier());
+    return AddFramebuffersForBuffer(drm, buffer.get());
   }
 
  protected:
@@ -157,8 +154,14 @@
   scoped_refptr<DrmDevice> CreateDevice(const base::FilePath& path,
                                         base::File file,
                                         bool is_primary_device) override {
-    auto drm = base::MakeRefCounted<DrmDevice>(path, std::move(file),
-                                               is_primary_device);
+    auto gbm = CreateGbmDevice(file.GetPlatformFile());
+    if (!gbm) {
+      PLOG(ERROR) << "Unable to initialize GBM for " << path.value();
+      return nullptr;
+    }
+
+    auto drm = base::MakeRefCounted<DrmDevice>(
+        path, std::move(file), is_primary_device, std::move(gbm));
     if (!drm->Initialize())
       return nullptr;
     return drm;
@@ -251,16 +254,15 @@
   scoped_refptr<ui::DrmDevice> drm = device_manager_->GetDrmDevice(widget);
   DCHECK(drm);
 
-  std::unique_ptr<GbmBuffer> buffer = GbmBuffer::CreateBufferFromFds(
-      drm->gbm_device(), ui::GetFourCCFormatFromBufferFormat(format), size,
-      std::move(fds), planes);
+  std::unique_ptr<GbmBuffer> buffer = drm->gbm_device()->CreateBufferFromFds(
+      ui::GetFourCCFormatFromBufferFormat(format), size, std::move(fds),
+      planes);
   if (!buffer)
     return;
 
   scoped_refptr<DrmFramebuffer> framebuffer;
-  if (buffer->flags() & GBM_BO_USE_SCANOUT) {
-    framebuffer = AddFramebuffersForBo(drm, buffer->bo(), buffer->format(),
-                                       buffer->format_modifier());
+  if (buffer->GetFlags() & GBM_BO_USE_SCANOUT) {
+    framebuffer = AddFramebuffersForBuffer(drm, buffer.get());
     if (!framebuffer)
       return;
   }
diff --git a/ui/ozone/platform/drm/gpu/gbm_pixmap.cc b/ui/ozone/platform/drm/gpu/gbm_pixmap.cc
index 8713849..a79897a7 100644
--- a/ui/ozone/platform/drm/gpu/gbm_pixmap.cc
+++ b/ui/ozone/platform/drm/gpu/gbm_pixmap.cc
@@ -31,23 +31,23 @@
 }
 
 size_t GbmPixmap::GetDmaBufFdCount() const {
-  return buffer_->fd_count();
+  return buffer_->GetFdCount();
 }
 
 int GbmPixmap::GetDmaBufFd(size_t plane) const {
-  return buffer_->GetFd(plane);
+  return buffer_->GetPlaneFd(plane);
 }
 
 int GbmPixmap::GetDmaBufPitch(size_t plane) const {
-  return buffer_->GetStride(plane);
+  return buffer_->GetPlaneStride(plane);
 }
 
 int GbmPixmap::GetDmaBufOffset(size_t plane) const {
-  return buffer_->GetOffset(plane);
+  return buffer_->GetPlaneOffset(plane);
 }
 
 uint64_t GbmPixmap::GetDmaBufModifier(size_t plane) const {
-  return buffer_->format_modifier();
+  return buffer_->GetFormatModifier();
 }
 
 gfx::BufferFormat GbmPixmap::GetBufferFormat() const {
@@ -55,7 +55,7 @@
 }
 
 gfx::Size GbmPixmap::GetBufferSize() const {
-  return buffer_->size();
+  return buffer_->GetSize();
 }
 
 uint32_t GbmPixmap::GetUniqueId() const {
@@ -69,7 +69,7 @@
                                      const gfx::RectF& crop_rect,
                                      bool enable_blend,
                                      std::unique_ptr<gfx::GpuFence> gpu_fence) {
-  DCHECK(buffer_->flags() & GBM_BO_USE_SCANOUT);
+  DCHECK(buffer_->GetFlags() & GBM_BO_USE_SCANOUT);
   // |framebuffer_id| might be 0 if AddFramebuffer2 failed, in that case we
   // already logged the error in GbmBuffer ctor. We avoid logging the error
   // here since this method might be called every pageflip.
diff --git a/ui/ozone/platform/drm/gpu/gbm_surface_factory.cc b/ui/ozone/platform/drm/gpu/gbm_surface_factory.cc
index c8ef2c1..3b748ce 100644
--- a/ui/ozone/platform/drm/gpu/gbm_surface_factory.cc
+++ b/ui/ozone/platform/drm/gpu/gbm_surface_factory.cc
@@ -183,9 +183,9 @@
   }
 
   DCHECK(buffer->AreFdsValid());
-  DCHECK_EQ(buffer->fd_count(), 1U);
+  DCHECK_EQ(buffer->GetFdCount(), 1U);
 
-  base::ScopedFD vk_image_fd(dup(buffer->GetFd(0)));
+  base::ScopedFD vk_image_fd(dup(buffer->GetPlaneFd(0)));
   DCHECK(vk_image_fd.is_valid());
 
   VkDmaBufImageCreateInfo dma_buf_image_create_info = {
@@ -194,7 +194,7 @@
       .fd = vk_image_fd.release(),
       .format = VK_FORMAT_B8G8R8A8_SRGB,
       .extent = (VkExtent3D){size.width(), size.height(), 1},
-      .strideInBytes = buffer->GetStride(0),
+      .strideInBytes = buffer->GetPlaneStride(0),
   };
 
   VkResult result =
diff --git a/ui/ozone/platform/drm/gpu/mock_drm_device.cc b/ui/ozone/platform/drm/gpu/mock_drm_device.cc
index 5f4f78a..abdc7adc 100644
--- a/ui/ozone/platform/drm/gpu/mock_drm_device.cc
+++ b/ui/ozone/platform/drm/gpu/mock_drm_device.cc
@@ -74,7 +74,10 @@
 MockDrmDevice::PlaneProperties::~PlaneProperties() = default;
 
 MockDrmDevice::MockDrmDevice()
-    : DrmDevice(base::FilePath(), base::File(), true /* is_primary_device */),
+    : DrmDevice(base::FilePath(),
+                base::File(),
+                true /* is_primary_device */,
+                nullptr),
       get_crtc_call_count_(0),
       set_crtc_call_count_(0),
       restore_crtc_call_count_(0),
diff --git a/ui/views/cocoa/bridged_native_widget.mm b/ui/views/cocoa/bridged_native_widget.mm
index 60e134a..98fd9ba0 100644
--- a/ui/views/cocoa/bridged_native_widget.mm
+++ b/ui/views/cocoa/bridged_native_widget.mm
@@ -510,8 +510,22 @@
     // parent window. So, if there's a parent, order above that. Otherwise, this
     // will order above all windows at the same level.
     NSInteger parent_window_number = 0;
-    if (parent_)
+    if (parent_) {
+      // When there's a parent, check if the window is already visible. If
+      // ShowInactive() is called on an already-visible window, there should be
+      // no effect: the macOS childWindow mechanism should have already raised
+      // the window to the right stacking order. More importantly, invoking
+      // -[NSWindow orderWindow:] could cause a Space switch, which defeats the
+      // point of ShowInactive(), so avoid it. See https://crbug.com/866760.
+
+      // Sanity check: if the window is visible, the prior Show should have
+      // hooked it up as a native child window already.
+      DCHECK_EQ(window_visible_, !![window_ parentWindow]);
+      if (window_visible_)
+        return;  // Avoid a Spaces transition.
+
       parent_window_number = [parent_->GetNSWindow() windowNumber];
+    }
 
     [window_ orderWindow:NSWindowAbove
               relativeTo:parent_window_number];
diff --git a/ui/views/mus/ax_remote_host.cc b/ui/views/mus/ax_remote_host.cc
index 70b63d5..f28c1ce9 100644
--- a/ui/views/mus/ax_remote_host.cc
+++ b/ui/views/mus/ax_remote_host.cc
@@ -114,15 +114,26 @@
   tree_source_->HandleAccessibleAction(action_data);
 }
 
+void AXRemoteHost::OnWidgetClosing(Widget* widget) {
+  // In the typical case, clean up early during widget close. Waiting until
+  // widget destroy can sometimes lead to crashes due to AX window visibility
+  // events being triggered during close. https://crbug.com/869608
+  DCHECK_EQ(widget_, widget);
+  StopMonitoringWidget();
+}
+
 void AXRemoteHost::OnWidgetDestroying(Widget* widget) {
+  // Widgets can be deleted without being closed, which is common in tests
+  // and possible in production.
   DCHECK_EQ(widget_, widget);
   StopMonitoringWidget();
 }
 
 void AXRemoteHost::OnChildWindowRemoved(AXAuraObjWrapper* parent) {
-  if (!enabled_)
+  if (!enabled_ || !widget_)
     return;
 
+  DCHECK(tree_source_);
   if (!parent)
     parent = tree_source_->GetRoot();
 
@@ -174,6 +185,7 @@
 
 void AXRemoteHost::SendEvent(AXAuraObjWrapper* aura_obj,
                              ax::mojom::Event event_type) {
+  DCHECK(aura_obj);
   if (!enabled_ || !tree_serializer_)
     return;
 
diff --git a/ui/views/mus/ax_remote_host.h b/ui/views/mus/ax_remote_host.h
index 2a89dea4..0f979445 100644
--- a/ui/views/mus/ax_remote_host.h
+++ b/ui/views/mus/ax_remote_host.h
@@ -65,6 +65,7 @@
   void PerformAction(const ui::AXActionData& action) override;
 
   // WidgetObserver:
+  void OnWidgetClosing(Widget* widget) override;
   void OnWidgetDestroying(Widget* widget) override;
 
   // AXAuraObjCache::Delegate:
@@ -73,6 +74,7 @@
                ax::mojom::Event event_type) override;
 
   void FlushForTesting();
+  Widget* widget_for_testing() { return widget_; }
 
  private:
   // Registers this object as a remote host for the parent AXHost.
diff --git a/ui/views/mus/ax_remote_host_unittest.cc b/ui/views/mus/ax_remote_host_unittest.cc
index 0a1baad..a8fdfad6 100644
--- a/ui/views/mus/ax_remote_host_unittest.cc
+++ b/ui/views/mus/ax_remote_host_unittest.cc
@@ -5,6 +5,7 @@
 #include "ui/views/mus/ax_remote_host.h"
 
 #include "base/macros.h"
+#include "base/run_loop.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/accessibility/mojom/ax_host.mojom.h"
 #include "ui/views/accessibility/ax_aura_obj_cache.h"
@@ -140,6 +141,34 @@
   // No crash.
 }
 
+// Verifies that the AXRemoteHost stops monitoring widgets that are closed
+// asynchronously, like when ash requests close via DesktopWindowTreeHostMus.
+// https://crbug.com/869608
+TEST_F(AXRemoteHostTest, AsyncWidgetClose) {
+  TestAXHostService service(true /*automation_enabled*/);
+  AXRemoteHost* remote = CreateRemote(&service);
+  remote->FlushForTesting();
+
+  Widget* widget = new Widget();  // Owned by native widget.
+  Widget::InitParams params;
+  params.bounds = gfx::Rect(1, 2, 333, 444);
+  widget->Init(params);
+  widget->Show();
+
+  // AXRemoteHost is tracking the widget.
+  EXPECT_TRUE(remote->widget_for_testing());
+
+  // Asynchronously close the widget using Close() instead of CloseNow().
+  widget->Close();
+
+  // AXRemoteHost stops tracking the widget, even though it isn't destroyed yet.
+  EXPECT_FALSE(remote->widget_for_testing());
+
+  // Widget finishes closing.
+  base::RunLoop().RunUntilIdle();
+  // No crash.
+}
+
 TEST_F(AXRemoteHostTest, CreateWidgetThenEnableAutomation) {
   TestAXHostService service(false /*automation_enabled*/);
   AXRemoteHost* remote = CreateRemote(&service);
diff --git a/ui/views/mus/mus_client.cc b/ui/views/mus/mus_client.cc
index a42f6860..19b816d 100644
--- a/ui/views/mus/mus_client.cc
+++ b/ui/views/mus/mus_client.cc
@@ -147,6 +147,10 @@
 }
 
 MusClient::~MusClient() {
+  // Tear down accessibility before WindowTreeClient to ensure window tree
+  // cleanup doesn't trigger accessibility events.
+  ax_remote_host_.reset();
+
   // ~WindowTreeClient calls back to us (we're its delegate), destroy it while
   // we are still valid.
   owned_window_tree_client_.reset();
diff --git a/ui/views/widget/native_widget_mac_unittest.mm b/ui/views/widget/native_widget_mac_unittest.mm
index 7c316923..b079f64 100644
--- a/ui/views/widget/native_widget_mac_unittest.mm
+++ b/ui/views/widget/native_widget_mac_unittest.mm
@@ -60,9 +60,11 @@
 @interface NativeWidgetMacTestWindow : NativeWidgetMacNSWindow {
  @private
   int invalidateShadowCount_;
+  int orderWindowCount_;
   bool* deallocFlag_;
 }
 @property(readonly, nonatomic) int invalidateShadowCount;
+@property(readonly, nonatomic) int orderWindowCount;
 @property(assign, nonatomic) bool* deallocFlag;
 @end
 
@@ -468,6 +470,44 @@
   widget->Close();
 }
 
+// Test that ShowInactive() on already-visible child widgets is ignored, since
+// it may cause a space transition. See https://crbug.com/866760.
+TEST_F(NativeWidgetMacTest, ShowInactiveOnChildWidget) {
+  NativeWidgetMacTestWindow* parent_window;
+  NativeWidgetMacTestWindow* child_window;
+
+  Widget::InitParams init_params =
+      CreateParams(Widget::InitParams::TYPE_WINDOW);
+  init_params.bounds = gfx::Rect(100, 100, 200, 200);
+  Widget* parent = CreateWidgetWithTestWindow(init_params, &parent_window);
+
+  // CreateWidgetWithTestWindow calls Show()
+  EXPECT_EQ(1, [parent_window orderWindowCount]);
+
+  init_params.parent = parent->GetNativeView();
+  Widget* child = CreateWidgetWithTestWindow(init_params, &child_window);
+
+  // The child is ordered twice, once by Show() and again (by AppKit) when it is
+  // registered as a child window.
+  EXPECT_EQ(2, [child_window orderWindowCount]);
+
+  // Parent is unchanged.
+  EXPECT_EQ(1, [parent_window orderWindowCount]);
+
+  // ShowInactive() on a visible regular window may serve to raise its stacking
+  // order without taking focus, so it should invoke -[NSWindow orderWindow:..].
+  parent->ShowInactive();
+  EXPECT_EQ(2, [parent_window orderWindowCount]);  // Increases.
+
+  // However, ShowInactive() on the child should have no effect. It should
+  // already be in a correct stacking order and we must avoid a Space switch.
+  child->ShowInactive();
+  EXPECT_EQ(2, [child_window orderWindowCount]);   // No change.
+  EXPECT_EQ(2, [parent_window orderWindowCount]);  // Parent also unchanged.
+
+  parent->CloseNow();
+}
+
 // Test minimized states triggered externally, implied visibility and restored
 // bounds whilst minimized.
 TEST_F(NativeWidgetMacTest, MiniaturizeExternally) {
@@ -2343,6 +2383,7 @@
 @implementation NativeWidgetMacTestWindow
 
 @synthesize invalidateShadowCount = invalidateShadowCount_;
+@synthesize orderWindowCount = orderWindowCount_;
 @synthesize deallocFlag = deallocFlag_;
 
 - (void)dealloc {
@@ -2358,6 +2399,12 @@
   [super invalidateShadow];
 }
 
+- (void)orderWindow:(NSWindowOrderingMode)orderingMode
+         relativeTo:(NSInteger)otherWindowNumber {
+  ++orderWindowCount_;
+  [super orderWindow:orderingMode relativeTo:otherWindowNumber];
+}
+
 @end
 
 @implementation MockBridgedView