diff --git a/DEPS b/DEPS
index 9b23b45f7..735f292 100644
--- a/DEPS
+++ b/DEPS
@@ -44,7 +44,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'c9a1f8e834f11acb2bf7c56cd8292cf3c761539c',
+  'v8_revision': '8b00387af39d3b35326c4510f1f7e213fbd56a5d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContents.java b/android_webview/java/src/org/chromium/android_webview/AwContents.java
index 697d1b0..dabc79b 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwContents.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java
@@ -123,7 +123,8 @@
     private static class ForceAuxiliaryBitmapRendering {
         private static final boolean sResult = lazyCheck();
         private static boolean lazyCheck() {
-            return !nativeHasRequiredHardwareExtensions();
+            return "goldfish".equals(Build.HARDWARE) || "ranchu".equals(Build.HARDWARE)
+                    || !nativeHasRequiredHardwareExtensions();
         }
     }
 
diff --git a/ash/test/test_keyboard_ui.cc b/ash/test/test_keyboard_ui.cc
index 46558a8e..f0d03c1 100644
--- a/ash/test/test_keyboard_ui.cc
+++ b/ash/test/test_keyboard_ui.cc
@@ -38,7 +38,6 @@
   return root_window->GetHost()->GetInputMethod();
 }
 
-void TestKeyboardUI::SetUpdateInputType(ui::TextInputType type) {}
 void TestKeyboardUI::ReloadKeyboardIfNeeded() {}
 void TestKeyboardUI::InitInsets(const gfx::Rect& keyboard_bounds) {}
 void TestKeyboardUI::ResetInsets() {}
diff --git a/ash/test/test_keyboard_ui.h b/ash/test/test_keyboard_ui.h
index a377c63..cd46236 100644
--- a/ash/test/test_keyboard_ui.h
+++ b/ash/test/test_keyboard_ui.h
@@ -30,7 +30,6 @@
  private:
   // Overridden from keyboard::KeyboardUI:
   ui::InputMethod* GetInputMethod() override;
-  void SetUpdateInputType(ui::TextInputType type) override;
   void ReloadKeyboardIfNeeded() override;
   void InitInsets(const gfx::Rect& keyboard_bounds) override;
   void ResetInsets() override;
diff --git a/build/android/lint/suppressions.xml b/build/android/lint/suppressions.xml
index 6eebc4a..b483735 100644
--- a/build/android/lint/suppressions.xml
+++ b/build/android/lint/suppressions.xml
@@ -297,6 +297,9 @@
   <!-- TODO(crbug.com/635567): Fix this properly. -->
   <issue id="UnusedResources" severity="ignore"/>
   <issue id="UnusedResources">
+    <!-- The two dimens below will be changing soon so please leave them in -->
+    <ignore regexp="design_bottom_navigation_text_size"/>
+    <ignore regexp="design_bottom_navigation_active_text_size"/>
     <ignore regexp="PRODUCT_DIR/gen/remoting/android/remoting_android_raw_resources/res/raw/credits.html"/>
     <ignore regexp="PRODUCT_DIR/gen/remoting/android/remoting_android_raw_resources/res/raw/credits_css.css"/>
     <ignore regexp="PRODUCT_DIR/gen/remoting/android/remoting_android_raw_resources/res/raw/credits_js.js"/>
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index f4f9e44..3eb20471 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -1746,9 +1746,11 @@
     # builds, currently get
     # "third_party/binutils/Linux_x64/Release/bin/ld.gold: warning:
     # /tmp/lto-llvm-0b5201.o: corrupt debug info in .debug_info"
+    # TODO(thakis): Re-enable on fuchsia once lld can process R_X86_64_DTPOFF64
+    # relocations, https://crbug.com/735101
     if (!is_mac && !is_ios && !is_nacl && target_cpu != "x86" &&
         (use_gold || use_lld) && !allow_posix_link_time_opt &&
-        !is_official_build) {
+        !is_official_build && !is_fuchsia) {
       ldflags += [ "-Wl,--gdb-index" ]
     }
   }
diff --git a/build/toolchain/win/setup_toolchain.py b/build/toolchain/win/setup_toolchain.py
index d91bf9b2..8150d3a 100644
--- a/build/toolchain/win/setup_toolchain.py
+++ b/build/toolchain/win/setup_toolchain.py
@@ -139,10 +139,9 @@
         raise Exception('%s is missing - make sure VC++ tools are installed.' %
                         script_path)
       script_path = other_path
-    # Chromium requires the 10.0.14393.0 SDK. Previous versions don't have all
-    # of the required declarations, and 10.0.15063.0 is buggy.
-    args = [script_path, 'amd64_x86' if cpu == 'x86' else 'amd64',
-            '10.0.14393.0']
+    # Chromium requires the 10.0.14393.0 SDK or higher - previous versions don't
+    # have all of the required declarations.
+    args = [script_path, 'amd64_x86' if cpu == 'x86' else 'amd64']
     variables = _LoadEnvFromBat(args)
   return _ExtractImportantEnvironment(variables)
 
diff --git a/cc/animation/BUILD.gn b/cc/animation/BUILD.gn
index 44d5b37..7ae45962 100644
--- a/cc/animation/BUILD.gn
+++ b/cc/animation/BUILD.gn
@@ -21,6 +21,7 @@
     "animation_id_provider.h",
     "animation_player.cc",
     "animation_player.h",
+    "animation_target.h",
     "animation_timeline.cc",
     "animation_timeline.h",
     "element_animations.cc",
diff --git a/cc/animation/animation_events.h b/cc/animation/animation_events.h
index 65a7ae6..a129ed55 100644
--- a/cc/animation/animation_events.h
+++ b/cc/animation/animation_events.h
@@ -19,7 +19,7 @@
 namespace cc {
 
 struct CC_ANIMATION_EXPORT AnimationEvent {
-  enum Type { STARTED, FINISHED, ABORTED, PROPERTY_UPDATE, TAKEOVER };
+  enum Type { STARTED, FINISHED, ABORTED, TAKEOVER };
 
   AnimationEvent(Type type,
                  ElementId element_id,
diff --git a/cc/animation/animation_host.cc b/cc/animation/animation_host.cc
index d56e7585..3d6092d 100644
--- a/cc/animation/animation_host.cc
+++ b/cc/animation/animation_host.cc
@@ -345,11 +345,6 @@
           (*iter).second->NotifyAnimationAborted(events->events_[event_index]);
           break;
 
-        case AnimationEvent::PROPERTY_UPDATE:
-          (*iter).second->NotifyAnimationPropertyUpdate(
-              events->events_[event_index]);
-          break;
-
         case AnimationEvent::TAKEOVER:
           (*iter).second->NotifyAnimationTakeover(events->events_[event_index]);
           break;
diff --git a/cc/animation/animation_player.cc b/cc/animation/animation_player.cc
index 4213773..1c43f39 100644
--- a/cc/animation/animation_player.cc
+++ b/cc/animation/animation_player.cc
@@ -13,6 +13,7 @@
 #include "cc/animation/animation_timeline.h"
 #include "cc/animation/scroll_offset_animation_curve.h"
 #include "cc/animation/transform_operations.h"
+#include "cc/base/math_util.h"
 #include "cc/trees/property_animation_state.h"
 
 namespace cc {
@@ -723,82 +724,53 @@
     SetNeedsPushProperties();
 }
 
-void AnimationPlayer::TickAnimations(base::TimeTicks monotonic_time) {
-  DCHECK(element_animations_);
-
-  for (size_t i = 0; i < animations_.size(); ++i) {
-    if (animations_[i]->run_state() == Animation::STARTING ||
-        animations_[i]->run_state() == Animation::RUNNING ||
-        animations_[i]->run_state() == Animation::PAUSED) {
-      if (!animations_[i]->InEffect(monotonic_time))
-        continue;
-
-      base::TimeDelta trimmed =
-          animations_[i]->TrimTimeToCurrentIteration(monotonic_time);
-
-      switch (animations_[i]->target_property()) {
-        case TargetProperty::TRANSFORM: {
-          const TransformAnimationCurve* transform_animation_curve =
-              animations_[i]->curve()->ToTransformAnimationCurve();
-          const TransformOperations operations =
-              transform_animation_curve->GetValue(trimmed);
-          element_animations_->NotifyClientTransformOperationsAnimated(
-              operations, animations_[i]->affects_active_elements(),
-              animations_[i]->affects_pending_elements());
-          break;
-        }
-
-        case TargetProperty::OPACITY: {
-          const FloatAnimationCurve* float_animation_curve =
-              animations_[i]->curve()->ToFloatAnimationCurve();
-          const float opacity = std::max(
-              std::min(float_animation_curve->GetValue(trimmed), 1.0f), 0.f);
-          element_animations_->NotifyClientOpacityAnimated(
-              opacity, animations_[i]->affects_active_elements(),
-              animations_[i]->affects_pending_elements());
-          break;
-        }
-
-        case TargetProperty::FILTER: {
-          const FilterAnimationCurve* filter_animation_curve =
-              animations_[i]->curve()->ToFilterAnimationCurve();
-          const FilterOperations filter =
-              filter_animation_curve->GetValue(trimmed);
-          element_animations_->NotifyClientFilterAnimated(
-              filter, animations_[i]->affects_active_elements(),
-              animations_[i]->affects_pending_elements());
-          break;
-        }
-
-        case TargetProperty::BACKGROUND_COLOR: {
-          // Not yet implemented.
-          break;
-        }
-
-        case TargetProperty::SCROLL_OFFSET: {
-          const ScrollOffsetAnimationCurve* scroll_offset_animation_curve =
-              animations_[i]->curve()->ToScrollOffsetAnimationCurve();
-          const gfx::ScrollOffset scroll_offset =
-              scroll_offset_animation_curve->GetValue(trimmed);
-          element_animations_->NotifyClientScrollOffsetAnimated(
-              scroll_offset, animations_[i]->affects_active_elements(),
-              animations_[i]->affects_pending_elements());
-          break;
-        }
-
-        case TargetProperty::BOUNDS: {
-          const SizeAnimationCurve* size_animation_curve =
-              animations_[i]->curve()->ToSizeAnimationCurve();
-          const gfx::SizeF size = size_animation_curve->GetValue(trimmed);
-          element_animations_->NotifyClientBoundsAnimated(
-              size, animations_[i]->affects_active_elements(),
-              animations_[i]->affects_pending_elements());
-          break;
-        }
-      }
-    }
+void AnimationPlayer::TickAnimation(base::TimeTicks monotonic_time,
+                                    Animation* animation,
+                                    AnimationTarget* target) {
+  if ((animation->run_state() != Animation::STARTING &&
+       animation->run_state() != Animation::RUNNING &&
+       animation->run_state() != Animation::PAUSED) ||
+      !animation->InEffect(monotonic_time)) {
+    return;
   }
 
+  AnimationCurve* curve = animation->curve();
+  base::TimeDelta trimmed =
+      animation->TrimTimeToCurrentIteration(monotonic_time);
+
+  switch (animation->target_property()) {
+    case TargetProperty::TRANSFORM:
+      target->NotifyClientTransformOperationsAnimated(
+          curve->ToTransformAnimationCurve()->GetValue(trimmed), animation);
+      break;
+    case TargetProperty::OPACITY:
+      target->NotifyClientOpacityAnimated(
+          MathUtil::ClampToRange(
+              curve->ToFloatAnimationCurve()->GetValue(trimmed), 0.0f, 1.0f),
+          animation);
+      break;
+    case TargetProperty::FILTER:
+      target->NotifyClientFilterAnimated(
+          curve->ToFilterAnimationCurve()->GetValue(trimmed), animation);
+      break;
+    case TargetProperty::BACKGROUND_COLOR:
+      // Not yet implemented.
+      break;
+    case TargetProperty::SCROLL_OFFSET:
+      target->NotifyClientScrollOffsetAnimated(
+          curve->ToScrollOffsetAnimationCurve()->GetValue(trimmed), animation);
+      break;
+    case TargetProperty::BOUNDS:
+      target->NotifyClientBoundsAnimated(
+          curve->ToSizeAnimationCurve()->GetValue(trimmed), animation);
+      break;
+  }
+}
+
+void AnimationPlayer::TickAnimations(base::TimeTicks monotonic_time) {
+  DCHECK(element_animations_);
+  for (auto& animation : animations_)
+    TickAnimation(monotonic_time, animation.get(), element_animations_.get());
   last_tick_time_ = monotonic_time;
 }
 
diff --git a/cc/animation/animation_player.h b/cc/animation/animation_player.h
index 98475a6..c2df51f 100644
--- a/cc/animation/animation_player.h
+++ b/cc/animation/animation_player.h
@@ -109,7 +109,12 @@
                                 AnimationEvents* events);
   void MarkAnimationsForDeletion(base::TimeTicks monotonic_time,
                                  AnimationEvents* events);
+
+  static void TickAnimation(base::TimeTicks monotonic_time,
+                            Animation* animation,
+                            AnimationTarget* target);
   void TickAnimations(base::TimeTicks monotonic_time);
+
   void MarkFinishedAnimations(base::TimeTicks monotonic_time);
 
   // Make animations affect active elements if and only if they affect
diff --git a/cc/animation/animation_target.h b/cc/animation/animation_target.h
new file mode 100644
index 0000000..ecda3a0
--- /dev/null
+++ b/cc/animation/animation_target.h
@@ -0,0 +1,45 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CC_ANIMATION_ANIMATION_TARGET_H_
+#define CC_ANIMATION_ANIMATION_TARGET_H_
+
+#include "cc/animation/animation_export.h"
+
+namespace gfx {
+
+class ScrollOffset;
+class SizeF;
+
+}  // namespace gfx
+
+namespace cc {
+
+class Animation;
+class FilterOperations;
+class TransformOperations;
+
+// An AnimationTarget is an entity that can be affected by a ticking
+// cc:Animation. Any object that expects to have an opacity update, for
+// example, should derive from this class.
+class CC_ANIMATION_EXPORT AnimationTarget {
+ public:
+  virtual ~AnimationTarget() {}
+  virtual void NotifyClientOpacityAnimated(float opacity,
+                                           Animation* animation) {}
+  virtual void NotifyClientFilterAnimated(const FilterOperations& filter,
+                                          Animation* animation) {}
+  virtual void NotifyClientBoundsAnimated(const gfx::SizeF& size,
+                                          Animation* animation) {}
+  virtual void NotifyClientTransformOperationsAnimated(
+      const TransformOperations& operations,
+      Animation* animation) {}
+  virtual void NotifyClientScrollOffsetAnimated(
+      const gfx::ScrollOffset& scroll_offset,
+      Animation* animation) {}
+};
+
+}  // namespace cc
+
+#endif  // CC_ANIMATION_ANIMATION_TARGET_H_
diff --git a/cc/animation/element_animations.cc b/cc/animation/element_animations.cc
index 322530ba..f962545c8 100644
--- a/cc/animation/element_animations.cc
+++ b/cc/animation/element_animations.cc
@@ -195,29 +195,6 @@
   UpdateClientAnimationState();
 }
 
-void ElementAnimations::NotifyAnimationPropertyUpdate(
-    const AnimationEvent& event) {
-  DCHECK(!event.is_impl_only);
-  bool notify_active_elements = true;
-  bool notify_pending_elements = true;
-  switch (event.target_property) {
-    case TargetProperty::OPACITY: {
-      NotifyClientOpacityAnimated(event.opacity, notify_active_elements,
-                                  notify_pending_elements);
-      break;
-    }
-    case TargetProperty::TRANSFORM: {
-      TransformOperations operations;
-      operations.AppendMatrix(event.transform);
-      NotifyClientTransformOperationsAnimated(
-          operations, notify_active_elements, notify_pending_elements);
-      break;
-    }
-    default:
-      NOTREACHED();
-  }
-}
-
 bool ElementAnimations::HasFilterAnimationThatInflatesBounds() const {
   for (auto& player : players_list_) {
     if (player.HasFilterAnimationThatInflatesBounds())
@@ -319,56 +296,42 @@
   SetNeedsPushProperties();
 }
 
-void ElementAnimations::NotifyClientOpacityAnimated(
-    float opacity,
-    bool notify_active_elements,
-    bool notify_pending_elements) {
-  if (notify_active_elements && has_element_in_active_list())
+void ElementAnimations::NotifyClientOpacityAnimated(float opacity,
+                                                    Animation* animation) {
+  if (AnimationAffectsActiveElements(animation))
     OnOpacityAnimated(ElementListType::ACTIVE, opacity);
-  if (notify_pending_elements && has_element_in_pending_list())
+  if (AnimationAffectsPendingElements(animation))
     OnOpacityAnimated(ElementListType::PENDING, opacity);
 }
 
-void ElementAnimations::NotifyClientTransformOperationsAnimated(
-    const TransformOperations& operations,
-    bool notify_active_elements,
-    bool notify_pending_elements) {
-  gfx::Transform transform = operations.Apply();
-  if (notify_active_elements && has_element_in_active_list())
-    OnTransformAnimated(ElementListType::ACTIVE, transform);
-  if (notify_pending_elements && has_element_in_pending_list())
-    OnTransformAnimated(ElementListType::PENDING, transform);
-}
-
 void ElementAnimations::NotifyClientFilterAnimated(
     const FilterOperations& filters,
-    bool notify_active_elements,
-    bool notify_pending_elements) {
-  if (notify_active_elements && has_element_in_active_list())
+    Animation* animation) {
+  if (AnimationAffectsActiveElements(animation))
     OnFilterAnimated(ElementListType::ACTIVE, filters);
-  if (notify_pending_elements && has_element_in_pending_list())
+  if (AnimationAffectsPendingElements(animation))
     OnFilterAnimated(ElementListType::PENDING, filters);
 }
 
+void ElementAnimations::NotifyClientTransformOperationsAnimated(
+    const TransformOperations& operations,
+    Animation* animation) {
+  gfx::Transform transform = operations.Apply();
+  if (AnimationAffectsActiveElements(animation))
+    OnTransformAnimated(ElementListType::ACTIVE, transform);
+  if (AnimationAffectsPendingElements(animation))
+    OnTransformAnimated(ElementListType::PENDING, transform);
+}
+
 void ElementAnimations::NotifyClientScrollOffsetAnimated(
     const gfx::ScrollOffset& scroll_offset,
-    bool notify_active_elements,
-    bool notify_pending_elements) {
-  if (notify_active_elements && has_element_in_active_list())
+    Animation* animation) {
+  if (AnimationAffectsActiveElements(animation))
     OnScrollOffsetAnimated(ElementListType::ACTIVE, scroll_offset);
-  if (notify_pending_elements && has_element_in_pending_list())
+  if (AnimationAffectsPendingElements(animation))
     OnScrollOffsetAnimated(ElementListType::PENDING, scroll_offset);
 }
 
-void ElementAnimations::NotifyClientBoundsAnimated(
-    const gfx::SizeF& size,
-    bool notify_active_elements,
-    bool notify_pending_elements) {
-  // TODO(vollick): once we have an animation observer, we can remove client
-  // animated notifications we do not use in element animations, such as this
-  // one.
-}
-
 void ElementAnimations::UpdateClientAnimationState() {
   if (!element_id())
     return;
@@ -509,4 +472,22 @@
   return gfx::ScrollOffset();
 }
 
+bool ElementAnimations::AnimationAffectsActiveElements(
+    Animation* animation) const {
+  // When we force an animation update due to a notification, we do not have an
+  // Animation instance. In this case, we force an update of active elements.
+  if (!animation)
+    return true;
+  return animation->affects_active_elements() && has_element_in_active_list();
+}
+
+bool ElementAnimations::AnimationAffectsPendingElements(
+    Animation* animation) const {
+  // When we force an animation update due to a notification, we do not have an
+  // Animation instance. In this case, we force an update of pending elements.
+  if (!animation)
+    return true;
+  return animation->affects_pending_elements() && has_element_in_pending_list();
+}
+
 }  // namespace cc
diff --git a/cc/animation/element_animations.h b/cc/animation/element_animations.h
index 95929cf3..61b7299 100644
--- a/cc/animation/element_animations.h
+++ b/cc/animation/element_animations.h
@@ -12,6 +12,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/observer_list.h"
 #include "cc/animation/animation_export.h"
+#include "cc/animation/animation_target.h"
 #include "cc/trees/element_id.h"
 #include "cc/trees/property_animation_state.h"
 #include "cc/trees/target_property.h"
@@ -20,7 +21,6 @@
 
 namespace gfx {
 class BoxF;
-class SizeF;
 }
 
 namespace cc {
@@ -39,7 +39,8 @@
 // This is a CC counterpart for blink::ElementAnimations (in 1:1 relationship).
 // No pointer to/from respective blink::ElementAnimations object for now.
 class CC_ANIMATION_EXPORT ElementAnimations
-    : public base::RefCounted<ElementAnimations> {
+    : public AnimationTarget,
+      public base::RefCounted<ElementAnimations> {
  public:
   static scoped_refptr<ElementAnimations> Create();
 
@@ -148,21 +149,14 @@
   void SetNeedsUpdateImplClientState();
 
   void NotifyClientOpacityAnimated(float opacity,
-                                   bool notify_active_elements,
-                                   bool notify_pending_elements);
+                                   Animation* animation) override;
+  void NotifyClientFilterAnimated(const FilterOperations& filter,
+                                  Animation* animation) override;
   void NotifyClientTransformOperationsAnimated(
       const TransformOperations& operations,
-      bool notify_active_elements,
-      bool notify_pending_elements);
-  void NotifyClientFilterAnimated(const FilterOperations& filter,
-                                  bool notify_active_elements,
-                                  bool notify_pending_elements);
+      Animation* animation) override;
   void NotifyClientScrollOffsetAnimated(const gfx::ScrollOffset& scroll_offset,
-                                        bool notify_active_elements,
-                                        bool notify_pending_elements);
-  void NotifyClientBoundsAnimated(const gfx::SizeF& size,
-                                  bool notify_active_elements,
-                                  bool notify_pending_elements);
+                                        Animation* animation) override;
 
   gfx::ScrollOffset ScrollOffsetForAnimation() const;
 
@@ -170,7 +164,7 @@
   friend class base::RefCounted<ElementAnimations>;
 
   ElementAnimations();
-  ~ElementAnimations();
+  ~ElementAnimations() override;
 
   void OnFilterAnimated(ElementListType list_type,
                         const FilterOperations& filters);
@@ -185,6 +179,9 @@
   void UpdatePlayersTickingState(UpdateTickingType update_ticking_type) const;
   void RemovePlayersFromTicking() const;
 
+  bool AnimationAffectsActiveElements(Animation* animation) const;
+  bool AnimationAffectsPendingElements(Animation* animation) const;
+
   PlayersList players_list_;
   AnimationHost* animation_host_;
   ElementId element_id_;
diff --git a/cc/animation/element_animations_unittest.cc b/cc/animation/element_animations_unittest.cc
index 8974943..6c10785 100644
--- a/cc/animation/element_animations_unittest.cc
+++ b/cc/animation/element_animations_unittest.cc
@@ -687,16 +687,6 @@
   return Animation::Create(std::move(curve), 0, group_id, property);
 }
 
-static const AnimationEvent* GetMostRecentPropertyUpdateEvent(
-    const AnimationEvents* events) {
-  const AnimationEvent* event = 0;
-  for (size_t i = 0; i < events->events_.size(); ++i)
-    if (events->events_[i].type == AnimationEvent::PROPERTY_UPDATE)
-      event = &events->events_[i];
-
-  return event;
-}
-
 TEST_F(ElementAnimationsTest, TrivialTransition) {
   CreateTestLayer(true, false);
   AttachTimelinePlayerLayer();
@@ -715,15 +705,11 @@
   player_->UpdateState(true, events.get());
   EXPECT_TRUE(player_->HasTickingAnimation());
   EXPECT_EQ(0.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE));
-  // A non-impl-only animation should not generate property updates.
-  const AnimationEvent* event = GetMostRecentPropertyUpdateEvent(events.get());
-  EXPECT_FALSE(event);
+
   player_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000));
   player_->UpdateState(true, events.get());
   EXPECT_EQ(1.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE));
   EXPECT_FALSE(player_->HasTickingAnimation());
-  event = GetMostRecentPropertyUpdateEvent(events.get());
-  EXPECT_FALSE(event);
 }
 
 TEST_F(ElementAnimationsTest, FilterTransition) {
@@ -753,9 +739,6 @@
   EXPECT_TRUE(player_->HasTickingAnimation());
   EXPECT_EQ(start_filters,
             client_.GetFilters(element_id_, ElementListType::ACTIVE));
-  // A non-impl-only animation should not generate property updates.
-  const AnimationEvent* event = GetMostRecentPropertyUpdateEvent(events.get());
-  EXPECT_FALSE(event);
 
   player_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(500));
   player_->UpdateState(true, events.get());
@@ -763,16 +746,12 @@
             client_.GetFilters(element_id_, ElementListType::ACTIVE).size());
   EXPECT_EQ(FilterOperation::CreateBrightnessFilter(1.5f),
             client_.GetFilters(element_id_, ElementListType::ACTIVE).at(0));
-  event = GetMostRecentPropertyUpdateEvent(events.get());
-  EXPECT_FALSE(event);
 
   player_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000));
   player_->UpdateState(true, events.get());
   EXPECT_EQ(end_filters,
             client_.GetFilters(element_id_, ElementListType::ACTIVE));
   EXPECT_FALSE(player_->HasTickingAnimation());
-  event = GetMostRecentPropertyUpdateEvent(events.get());
-  EXPECT_FALSE(event);
 }
 
 TEST_F(ElementAnimationsTest, ScrollOffsetTransition) {
@@ -816,9 +795,6 @@
   EXPECT_TRUE(player_impl_->HasTickingAnimation());
   EXPECT_EQ(initial_value,
             client_impl_.GetScrollOffset(element_id_, ElementListType::ACTIVE));
-  // Scroll offset animations should not generate property updates.
-  const AnimationEvent* event = GetMostRecentPropertyUpdateEvent(events.get());
-  EXPECT_FALSE(event);
 
   player_->NotifyAnimationStarted(events->events_[0]);
   player_->Tick(kInitialTickTime + duration / 2);
@@ -833,16 +809,12 @@
   EXPECT_VECTOR2DF_EQ(
       gfx::Vector2dF(200.f, 250.f),
       client_impl_.GetScrollOffset(element_id_, ElementListType::ACTIVE));
-  event = GetMostRecentPropertyUpdateEvent(events.get());
-  EXPECT_FALSE(event);
 
   player_impl_->Tick(kInitialTickTime + duration);
   player_impl_->UpdateState(true, events.get());
   EXPECT_VECTOR2DF_EQ(target_value, client_impl_.GetScrollOffset(
                                         element_id_, ElementListType::ACTIVE));
   EXPECT_FALSE(player_impl_->HasTickingAnimation());
-  event = GetMostRecentPropertyUpdateEvent(events.get());
-  EXPECT_FALSE(event);
 
   player_->Tick(kInitialTickTime + duration);
   player_->UpdateState(true, nullptr);
@@ -877,9 +849,6 @@
   EXPECT_TRUE(player_impl_->HasTickingAnimation());
   EXPECT_EQ(initial_value,
             client_impl_.GetScrollOffset(element_id_, ElementListType::ACTIVE));
-  // Scroll offset animations should not generate property updates.
-  const AnimationEvent* event = GetMostRecentPropertyUpdateEvent(events.get());
-  EXPECT_FALSE(event);
 
   TimeDelta duration = TimeDelta::FromMicroseconds(
       duration_in_seconds * base::Time::kMicrosecondsPerSecond);
@@ -889,16 +858,12 @@
   EXPECT_VECTOR2DF_EQ(
       gfx::Vector2dF(200.f, 250.f),
       client_impl_.GetScrollOffset(element_id_, ElementListType::ACTIVE));
-  event = GetMostRecentPropertyUpdateEvent(events.get());
-  EXPECT_FALSE(event);
 
   player_impl_->Tick(kInitialTickTime + duration);
   player_impl_->UpdateState(true, events.get());
   EXPECT_VECTOR2DF_EQ(target_value, client_impl_.GetScrollOffset(
                                         element_id_, ElementListType::ACTIVE));
   EXPECT_FALSE(player_impl_->HasTickingAnimation());
-  event = GetMostRecentPropertyUpdateEvent(events.get());
-  EXPECT_FALSE(event);
 }
 
 // This test verifies that if an animation is added after a layer is animated,
@@ -1009,10 +974,6 @@
   player_impl_->UpdateState(true, events.get());
   DCHECK_EQ(1UL, events->events_.size());
 
-  // Scroll offset animations should not generate property updates.
-  const AnimationEvent* event = GetMostRecentPropertyUpdateEvent(events.get());
-  EXPECT_FALSE(event);
-
   player_->NotifyAnimationStarted(events->events_[0]);
   player_->Tick(kInitialTickTime + duration / 2);
   player_->UpdateState(true, nullptr);
@@ -1026,16 +987,12 @@
   EXPECT_VECTOR2DF_EQ(
       gfx::Vector2dF(400.f, 150.f),
       client_impl_.GetScrollOffset(element_id_, ElementListType::PENDING));
-  event = GetMostRecentPropertyUpdateEvent(events.get());
-  EXPECT_FALSE(event);
 
   player_impl_->Tick(kInitialTickTime + duration);
   player_impl_->UpdateState(true, events.get());
   EXPECT_VECTOR2DF_EQ(target_value, client_impl_.GetScrollOffset(
                                         element_id_, ElementListType::PENDING));
   EXPECT_FALSE(player_impl_->HasTickingAnimation());
-  event = GetMostRecentPropertyUpdateEvent(events.get());
-  EXPECT_FALSE(event);
 
   player_->Tick(kInitialTickTime + duration);
   player_->UpdateState(true, nullptr);
diff --git a/cc/layers/layer.cc b/cc/layers/layer.cc
index 27cf2a19..d96b381 100644
--- a/cc/layers/layer.cc
+++ b/cc/layers/layer.cc
@@ -1114,21 +1114,6 @@
   SetNeedsCommit();
 }
 
-static void RunCopyCallbackOnMainThread(
-    std::unique_ptr<CopyOutputRequest> request,
-    std::unique_ptr<CopyOutputResult> result) {
-  request->SendResult(std::move(result));
-}
-
-static void PostCopyCallbackToMainThread(
-    scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner,
-    std::unique_ptr<CopyOutputRequest> request,
-    std::unique_ptr<CopyOutputResult> result) {
-  main_thread_task_runner->PostTask(
-      FROM_HERE, base::BindOnce(&RunCopyCallbackOnMainThread,
-                                base::Passed(&request), base::Passed(&result)));
-}
-
 bool Layer::IsSnapped() {
   return scrollable();
 }
@@ -1218,21 +1203,17 @@
 
 void Layer::TakeCopyRequests(
     std::vector<std::unique_ptr<CopyOutputRequest>>* requests) {
-  for (auto& it : inputs_.copy_requests) {
-    scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner =
-        layer_tree_host()->GetTaskRunnerProvider()->MainThreadTaskRunner();
-    std::unique_ptr<CopyOutputRequest> original_request = std::move(it);
-    const CopyOutputRequest& original_request_ref = *original_request;
-    std::unique_ptr<CopyOutputRequest> main_thread_request =
-        CopyOutputRequest::CreateRelayRequest(
-            original_request_ref,
-            base::Bind(&PostCopyCallbackToMainThread, main_thread_task_runner,
-                       base::Passed(&original_request)));
-    if (main_thread_request->has_area()) {
-      main_thread_request->set_area(gfx::IntersectRects(
-          main_thread_request->area(), gfx::Rect(bounds())));
+  for (std::unique_ptr<CopyOutputRequest>& request : inputs_.copy_requests) {
+    // Ensure the result callback is not invoked on the compositing thread.
+    if (!request->has_result_task_runner()) {
+      request->set_result_task_runner(
+          layer_tree_host()->GetTaskRunnerProvider()->MainThreadTaskRunner());
     }
-    requests->push_back(std::move(main_thread_request));
+    if (request->has_area()) {
+      request->set_area(
+          gfx::IntersectRects(request->area(), gfx::Rect(bounds())));
+    }
+    requests->push_back(std::move(request));
   }
 
   inputs_.copy_requests.clear();
diff --git a/cc/output/copy_output_request.cc b/cc/output/copy_output_request.cc
index b748c5f..b08a066 100644
--- a/cc/output/copy_output_request.cc
+++ b/cc/output/copy_output_request.cc
@@ -14,17 +14,6 @@
 
 namespace cc {
 
-// static
-std::unique_ptr<CopyOutputRequest> CopyOutputRequest::CreateRelayRequest(
-    const CopyOutputRequest& original_request,
-    const CopyOutputRequestCallback& result_callback) {
-  std::unique_ptr<CopyOutputRequest> relay = CreateRequest(result_callback);
-  relay->force_bitmap_result_ = original_request.force_bitmap_result_;
-  relay->area_ = original_request.area_;
-  relay->texture_mailbox_ = original_request.texture_mailbox_;
-  return relay;
-}
-
 CopyOutputRequest::CopyOutputRequest() : force_bitmap_result_(false) {}
 
 CopyOutputRequest::CopyOutputRequest(
@@ -42,9 +31,16 @@
 }
 
 void CopyOutputRequest::SendResult(std::unique_ptr<CopyOutputResult> result) {
-  bool success = !result->IsEmpty();
-  base::ResetAndReturn(&result_callback_).Run(std::move(result));
-  TRACE_EVENT_ASYNC_END1("cc", "CopyOutputRequest", this, "success", success);
+  TRACE_EVENT_ASYNC_END1("cc", "CopyOutputRequest", this, "success",
+                         !result->IsEmpty());
+  if (result_task_runner_) {
+    result_task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(base::ResetAndReturn(&result_callback_),
+                                  std::move(result)));
+    result_task_runner_ = nullptr;
+  } else {
+    base::ResetAndReturn(&result_callback_).Run(std::move(result));
+  }
 }
 
 void CopyOutputRequest::SendEmptyResult() {
diff --git a/cc/output/copy_output_request.h b/cc/output/copy_output_request.h
index 8147ab2..0194b77 100644
--- a/cc/output/copy_output_request.h
+++ b/cc/output/copy_output_request.h
@@ -10,6 +10,7 @@
 #include "base/callback.h"
 #include "base/memory/ptr_util.h"
 #include "base/optional.h"
+#include "base/task_runner.h"
 #include "base/unguessable_token.h"
 #include "cc/cc_export.h"
 #include "cc/resources/single_release_callback.h"
@@ -43,14 +44,19 @@
       const CopyOutputRequestCallback& result_callback) {
     return base::WrapUnique(new CopyOutputRequest(true, result_callback));
   }
-  static std::unique_ptr<CopyOutputRequest> CreateRelayRequest(
-      const CopyOutputRequest& original_request,
-      const CopyOutputRequestCallback& result_callback);
 
   ~CopyOutputRequest();
 
   bool IsEmpty() const { return result_callback_.is_null(); }
 
+  // Requests that the result callback be run as a task posted to the given
+  // |task_runner|. If this is not set, the result callback could be run from
+  // any context.
+  void set_result_task_runner(scoped_refptr<base::TaskRunner> task_runner) {
+    result_task_runner_ = std::move(task_runner);
+  }
+  bool has_result_task_runner() const { return !!result_task_runner_; }
+
   // Optionally specify the source of this copy request. If set when this copy
   // request is submitted to a layer, a prior uncommitted copy request from the
   // same source will be aborted.
@@ -93,6 +99,7 @@
   CopyOutputRequest(bool force_bitmap_result,
                     const CopyOutputRequestCallback& result_callback);
 
+  scoped_refptr<base::TaskRunner> result_task_runner_;
   base::Optional<base::UnguessableToken> source_;
   bool force_bitmap_result_;
   base::Optional<gfx::Rect> area_;
diff --git a/chrome/android/java/res/layout/chrome_home_incognito_new_tab_page.xml b/chrome/android/java/res/layout/chrome_home_incognito_new_tab_page.xml
deleted file mode 100644
index 6a35d9c..0000000
--- a/chrome/android/java/res/layout/chrome_home_incognito_new_tab_page.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2017 The Chromium Authors. All rights reserved.
-     Use of this source code is governed by a BSD-style license that can be
-     found in the LICENSE file. -->
-
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:chrome="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:focusable="true"
-    android:focusableInTouchMode="true"
-    android:background="@color/ntp_bg_incognito" >
-
-    <org.chromium.chrome.browser.widget.TintedImageButton
-        android:id="@+id/close_button"
-        android:layout_width="40dp"
-        android:layout_height="40dp"
-        android:layout_gravity="top|end"
-        android:src="@drawable/btn_close"
-        android:scaleType="center"
-        android:background="@android:color/transparent"
-        chrome:chrometint="@color/light_mode_tint" />
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
-        android:clipChildren="false" >
-
-        <FrameLayout
-            android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_weight="0.5"
-            android:layout_marginTop="10dp" >
-
-            <ImageView
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:contentDescription="@null"
-                android:layout_gravity="center"
-                android:src="@drawable/incognito_splash"/>
-
-        </FrameLayout>
-
-        <!-- This extra empty frame layout is used to ensure the FrameLayout
-             above is half of the parent view's height.  -->
-        <FrameLayout
-            android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_weight="0.5"
-            android:background="@android:color/transparent" />
-
-    </LinearLayout>
-
-</FrameLayout>
diff --git a/chrome/android/java/res/layout/chrome_home_new_tab_page.xml b/chrome/android/java/res/layout/chrome_home_new_tab_page.xml
deleted file mode 100644
index 2c8167c..0000000
--- a/chrome/android/java/res/layout/chrome_home_new_tab_page.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2017 The Chromium Authors. All rights reserved.
-     Use of this source code is governed by a BSD-style license that can be
-     found in the LICENSE file. -->
-
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:chrome="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:focusable="true"
-    android:focusableInTouchMode="true"
-    android:background="@android:color/white" >
-
-    <org.chromium.chrome.browser.widget.TintedImageButton
-        android:id="@+id/close_button"
-        android:layout_width="40dp"
-        android:layout_height="40dp"
-        android:layout_gravity="top|end"
-        android:src="@drawable/btn_close"
-        android:scaleType="center"
-        android:background="@android:color/transparent"
-        chrome:chrometint="@color/dark_mode_tint" />
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
-        android:clipChildren="false" >
-
-        <FrameLayout
-            android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_weight="0.5" >
-
-            <org.chromium.chrome.browser.ntp.LogoView
-                android:id="@+id/search_provider_logo"
-                android:layout_width="match_parent"
-                android:layout_height="@dimen/ntp_logo_height"
-                android:layout_marginStart="16dp"
-                android:layout_marginEnd="16dp"
-                android:layout_gravity="center" />
-
-        </FrameLayout>
-
-        <!-- This extra empty frame layout is used to ensure the FrameLayout
-             above is half of the parent view's height.  -->
-        <FrameLayout
-            android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_weight="0.5"
-            android:background="@android:color/transparent" />
-
-    </LinearLayout>
-
-</FrameLayout>
diff --git a/chrome/android/java/res/layout/fragment_lock_screen.xml b/chrome/android/java/res/layout/fragment_lock_screen.xml
deleted file mode 100644
index db87660..0000000
--- a/chrome/android/java/res/layout/fragment_lock_screen.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2017 The Chromium Authors. All rights reserved.
-     Use of this source code is governed by a BSD-style license that can be
-     found in the LICENSE file. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/lockscreen_main"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="vertical"
-    android:title="@string/lockscreen_verification_title">
-
-    <TextView
-        android:id="@+id/lockscreen_description_text"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/lockscreen_verification_text"/>
-
-    <Button
-        android:id="@+id/lockscreen_next_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="start"
-        android:paddingStart="0dp"
-        android:text="@string/next"
-        style="?android:attr/borderlessButtonStyle"/>
-</LinearLayout>
diff --git a/chrome/android/java/res/layout/reader_mode_text_view.xml b/chrome/android/java/res/layout/reader_mode_text_view.xml
deleted file mode 100644
index 822e1bd..0000000
--- a/chrome/android/java/res/layout/reader_mode_text_view.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2015 The Chromium Authors. All rights reserved.
-     Use of this source code is governed by a BSD-style license that can be
-     found in the LICENSE file. -->
-
-<!-- Reader Mode bar text -->
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/reader_mode_text_view"
-    style="@style/ContextualSearchTextViewLayout" >
-    <TextView
-        android:id="@+id/reader_mode_text"
-        android:text="@string/reader_view_text"
-        style="@style/ContextualSearchTextView"
-        android:layout_width="match_parent"
-        android:layout_gravity="bottom"
-        android:background="#FFF"
-        android:layout_marginStart="7dp"
-        android:layout_marginEnd="7dp" />
-</FrameLayout>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeBackgroundService.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeBackgroundService.java
index b452d8a5..c74b588 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeBackgroundService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeBackgroundService.java
@@ -21,8 +21,6 @@
 import org.chromium.chrome.browser.ntp.snippets.SnippetsLauncher;
 import org.chromium.chrome.browser.offlinepages.BackgroundScheduler;
 import org.chromium.chrome.browser.offlinepages.OfflinePageUtils;
-import org.chromium.chrome.browser.precache.PrecacheController;
-import org.chromium.chrome.browser.precache.PrecacheUMA;
 
 /**
  * {@link ChromeBackgroundService} is scheduled through the {@link GcmNetworkManager} when the
@@ -58,11 +56,6 @@
                         handleFetchSnippets(context, taskTag);
                         break;
 
-                    case PrecacheController.PERIODIC_TASK_TAG:
-                    case PrecacheController.CONTINUATION_TASK_TAG:
-                        handlePrecache(context, taskTag);
-                        break;
-
                     case DownloadResumptionScheduler.TASK_TAG:
                         DownloadResumptionScheduler.getDownloadResumptionScheduler(
                                 context.getApplicationContext()).handleDownloadResumption();
@@ -107,23 +100,6 @@
         SnippetsBridge.rescheduleFetching();
     }
 
-    private void handlePrecache(Context context, String tag) {
-        if (!hasPrecacheInstance()) {
-            launchBrowser(context, tag);
-        }
-        precache(context, tag);
-    }
-
-    @VisibleForTesting
-    protected boolean hasPrecacheInstance() {
-        return PrecacheController.hasInstance();
-    }
-
-    @VisibleForTesting
-    protected void precache(Context context, String tag) {
-        PrecacheController.get(context).precache(tag);
-    }
-
     @VisibleForTesting
     @SuppressFBWarnings("DM_EXIT")
     protected void launchBrowser(Context context, String tag) {
@@ -132,16 +108,6 @@
             ChromeBrowserInitializer.getInstance(this).handleSynchronousStartup();
         } catch (ProcessInitException e) {
             Log.e(TAG, "ProcessInitException while starting the browser process");
-            switch (tag) {
-                case PrecacheController.PERIODIC_TASK_TAG:
-                case PrecacheController.CONTINUATION_TASK_TAG:
-                    // Record the failure persistently, and upload to UMA when the library
-                    // successfully loads next time.
-                    PrecacheUMA.record(PrecacheUMA.Event.PRECACHE_TASK_LOAD_LIBRARY_FAIL);
-                    break;
-                default:
-                    break;
-            }
             // Since the library failed to initialize nothing in the application
             // can work, so kill the whole application not just the activity.
             System.exit(-1);
@@ -153,11 +119,6 @@
         BackgroundSyncLauncher.rescheduleTasksOnUpgrade(this);
     }
 
-    @VisibleForTesting
-    protected void reschedulePrecacheTasksOnUpgrade() {
-        PrecacheController.rescheduleTasksOnUpgrade(this);
-    }
-
     private void rescheduleSnippetsTasksOnUpgrade() {
         if (SnippetsLauncher.shouldRescheduleTasksOnUpgrade()) {
             if (!SnippetsLauncher.hasInstance()) {
@@ -175,7 +136,6 @@
     @Override
     public void onInitializeTasks() {
         rescheduleBackgroundSyncTasksOnUpgrade();
-        reschedulePrecacheTasksOnUpgrade();
         rescheduleSnippetsTasksOnUpgrade();
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
index 8ce75fd8..ec137b619 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
@@ -60,7 +60,6 @@
 import org.chromium.chrome.browser.partnercustomizations.PartnerBrowserCustomizations;
 import org.chromium.chrome.browser.photo_picker.PhotoPickerDialog;
 import org.chromium.chrome.browser.physicalweb.PhysicalWeb;
-import org.chromium.chrome.browser.precache.PrecacheLauncher;
 import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
 import org.chromium.chrome.browser.preferences.PrefServiceBridge;
 import org.chromium.chrome.browser.rlz.RevenueStats;
@@ -451,9 +450,6 @@
                     // killed.
                     BookmarkWidgetProvider.refreshAllWidgets(context);
 
-                    // Initialize whether or not precaching is enabled.
-                    PrecacheLauncher.updatePrecachingEnabled(context);
-
                     WebApkVersionManager.updateWebApksIfNeeded();
 
                     removeSnapshotDatabase(context);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ActionItem.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ActionItem.java
index fb977ee..3cb91bd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ActionItem.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ActionItem.java
@@ -32,7 +32,8 @@
         mCategoryInfo = section.getCategoryInfo();
         mParentSection = section;
         mSuggestionsRanker = ranker;
-        setVisible(mCategoryInfo.getAdditionalAction() != ContentSuggestionsAdditionalAction.NONE);
+        setVisibilityInternal(
+                mCategoryInfo.getAdditionalAction() != ContentSuggestionsAdditionalAction.NONE);
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/AllDismissedItem.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/AllDismissedItem.java
index 1809b653..a07a2292 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/AllDismissedItem.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/AllDismissedItem.java
@@ -40,6 +40,10 @@
         visitor.visitAllDismissedItem();
     }
 
+    public void setVisible(boolean visible) {
+        setVisibilityInternal(visible);
+    }
+
     /**
      * ViewHolder for an item of type {@link ItemViewType#ALL_DISMISSED}.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/Footer.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/Footer.java
index dd851e9..07d1a3d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/Footer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/Footer.java
@@ -37,6 +37,10 @@
         visitor.visitFooter();
     }
 
+    public void setVisible(boolean visible) {
+        setVisibilityInternal(visible);
+    }
+
     /**
      * The {@code ViewHolder} for the {@link Footer}.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/NewTabPageAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/NewTabPageAdapter.java
index 88530ed..6d44e90 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/NewTabPageAdapter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/NewTabPageAdapter.java
@@ -15,9 +15,14 @@
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.ntp.ContextMenuManager;
 import org.chromium.chrome.browser.ntp.cards.NewTabPageViewHolder.PartialBindCallback;
+import org.chromium.chrome.browser.ntp.snippets.CategoryInt;
+import org.chromium.chrome.browser.ntp.snippets.CategoryStatus;
 import org.chromium.chrome.browser.ntp.snippets.SectionHeaderViewHolder;
 import org.chromium.chrome.browser.ntp.snippets.SnippetArticleViewHolder;
+import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge;
+import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource;
 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
+import org.chromium.chrome.browser.suggestions.DestructionObserver;
 import org.chromium.chrome.browser.suggestions.SuggestionsRecyclerView;
 import org.chromium.chrome.browser.suggestions.SuggestionsUiDelegate;
 import org.chromium.chrome.browser.suggestions.TileGrid;
@@ -103,6 +108,9 @@
             mRoot.addChild(mBottomSpacer);
         }
 
+        RemoteSuggestionsStatusObserver suggestionsObserver = new RemoteSuggestionsStatusObserver();
+        mUiDelegate.addDestructionObserver(suggestionsObserver);
+
         updateAllDismissedVisibility();
         mRoot.setParent(this);
     }
@@ -222,7 +230,8 @@
     }
 
     private void updateAllDismissedVisibility() {
-        boolean showAllDismissed = hasAllBeenDismissed();
+        boolean showAllDismissed = hasAllBeenDismissed()
+                && mUiDelegate.getSuggestionsSource().areRemoteSuggestionsEnabled();
         mAllDismissed.setVisible(showAllDismissed);
         mFooter.setVisible(!showAllDismissed);
     }
@@ -309,4 +318,24 @@
     InnerNode getRootForTesting() {
         return mRoot;
     }
+
+    private class RemoteSuggestionsStatusObserver
+            extends SuggestionsSource.EmptyObserver implements DestructionObserver {
+        public RemoteSuggestionsStatusObserver() {
+            mUiDelegate.getSuggestionsSource().addObserver(this);
+        }
+
+        @Override
+        public void onCategoryStatusChanged(
+                @CategoryInt int category, @CategoryStatus int newStatus) {
+            if (!SnippetsBridge.isCategoryRemote(category)) return;
+
+            updateAllDismissedVisibility();
+        }
+
+        @Override
+        public void onDestroy() {
+            mUiDelegate.getSuggestionsSource().removeObserver(this);
+        }
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/OptionalLeaf.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/OptionalLeaf.java
index 19af66a..26174cc7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/OptionalLeaf.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/OptionalLeaf.java
@@ -67,7 +67,7 @@
      * initially considered hidden.
      */
     @CallSuper
-    public void setVisible(boolean visible) {
+    protected void setVisibilityInternal(boolean visible) {
         if (mVisible == visible) return;
         mVisible = visible;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ProgressItem.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ProgressItem.java
index 50e89fc..b1b39f0c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ProgressItem.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ProgressItem.java
@@ -28,4 +28,8 @@
     protected void visitOptionalItem(NodeVisitor visitor) {
         visitor.visitProgressItem();
     }
+
+    public void setVisible(boolean visible) {
+        setVisibilityInternal(visible);
+    }
 }
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 cf3437a..4cc059122 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
@@ -43,7 +43,7 @@
 
     public SectionList(SuggestionsUiDelegate uiDelegate, OfflinePageBridge offlinePageBridge) {
         mUiDelegate = uiDelegate;
-        mUiDelegate.getSuggestionsSource().setObserver(this);
+        mUiDelegate.getSuggestionsSource().addObserver(this);
         mOfflinePageBridge = offlinePageBridge;
 
         mUiDelegate.addDestructionObserver(new DestructionObserver() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SignInPromo.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SignInPromo.java
index 1a53583..857c604 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SignInPromo.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SignInPromo.java
@@ -15,6 +15,10 @@
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ntp.ContextMenuManager;
+import org.chromium.chrome.browser.ntp.snippets.CategoryInt;
+import org.chromium.chrome.browser.ntp.snippets.CategoryStatus;
+import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge;
+import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource;
 import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
 import org.chromium.chrome.browser.signin.AccountSigninActivity;
 import org.chromium.chrome.browser.signin.SigninAccessPoint;
@@ -39,23 +43,37 @@
      */
     private boolean mDismissed;
 
+    /**
+     * Whether the signin status means that the user has the possibility to sign in.
+     */
+    private boolean mCanSignIn;
+
+    /**
+     * Whether personalized suggestions can be shown. If it's not the case, we have no reason to
+     * offer the user to sign in.
+     */
+    private boolean mCanShowPersonalizedSuggestions;
+
     private final ImpressionTracker mImpressionTracker = new ImpressionTracker(null, this);
 
     @Nullable
-    private final SigninObserver mObserver;
+    private final SigninObserver mSigninObserver;
 
     public SignInPromo(SuggestionsUiDelegate uiDelegate) {
         mDismissed = ChromePreferenceManager.getInstance().getNewTabPageSigninPromoDismissed();
 
+        SuggestionsSource suggestionsSource = uiDelegate.getSuggestionsSource();
         SigninManager signinManager = SigninManager.get(ContextUtils.getApplicationContext());
         if (mDismissed) {
-            mObserver = null;
+            mSigninObserver = null;
         } else {
-            mObserver = new SigninObserver(signinManager);
-            uiDelegate.addDestructionObserver(mObserver);
+            mSigninObserver = new SigninObserver(signinManager, suggestionsSource);
+            uiDelegate.addDestructionObserver(mSigninObserver);
         }
 
-        setVisible(signinManager.isSignInAllowed() && !signinManager.isSignedInOnNative());
+        mCanSignIn = signinManager.isSignInAllowed() && !signinManager.isSignedInOnNative();
+        mCanShowPersonalizedSuggestions = suggestionsSource.areRemoteSuggestionsEnabled();
+        updateVisibility();
     }
 
     @Override
@@ -70,7 +88,7 @@
      */
     @Nullable
     public DestructionObserver getObserver() {
-        return mObserver;
+        return mSigninObserver;
     }
 
     @Override
@@ -114,9 +132,8 @@
         mImpressionTracker.reset(null);
     }
 
-    @Override
-    public void setVisible(boolean visible) {
-        super.setVisible(!mDismissed && visible);
+    private void updateVisibility() {
+        setVisibilityInternal(!mDismissed && mCanSignIn && mCanShowPersonalizedSuggestions);
     }
 
     @Override
@@ -127,26 +144,31 @@
     /** Hides the sign in promo and sets a preference to make sure it is not shown again. */
     @Override
     public void dismiss(Callback<String> itemRemovedCallback) {
+        assert mSigninObserver != null;
         mDismissed = true;
-        setVisible(false);
+        updateVisibility();
 
         ChromePreferenceManager.getInstance().setNewTabPageSigninPromoDismissed(true);
-        mObserver.unregister();
+        mSigninObserver.unregister();
         itemRemovedCallback.onResult(ContextUtils.getApplicationContext().getString(getHeader()));
     }
 
     @VisibleForTesting
-    class SigninObserver
+    class SigninObserver extends SuggestionsSource.EmptyObserver
             implements SignInStateObserver, SignInAllowedObserver, DestructionObserver {
         private final SigninManager mSigninManager;
+        private final SuggestionsSource mSuggestionsSource;
 
         /** Guards {@link #unregister()}, which can be called multiple times. */
         private boolean mUnregistered;
 
-        private SigninObserver(SigninManager signinManager) {
+        private SigninObserver(SigninManager signinManager, SuggestionsSource suggestionsSource) {
             mSigninManager = signinManager;
             mSigninManager.addSignInAllowedObserver(this);
             mSigninManager.addSignInStateObserver(this);
+
+            mSuggestionsSource = suggestionsSource;
+            mSuggestionsSource.addObserver(this);
         }
 
         private void unregister() {
@@ -155,6 +177,8 @@
 
             mSigninManager.removeSignInAllowedObserver(this);
             mSigninManager.removeSignInStateObserver(this);
+
+            mSuggestionsSource.removeObserver(this);
         }
 
         @Override
@@ -167,17 +191,31 @@
             // Listening to onSignInAllowedChanged is important for the FRE. Sign in is not allowed
             // until it is completed, but the NTP is initialised before the FRE is even shown. By
             // implementing this we can show the promo if the user did not sign in during the FRE.
-            setVisible(mSigninManager.isSignInAllowed());
+            mCanSignIn = mSigninManager.isSignInAllowed();
+            updateVisibility();
         }
 
         @Override
         public void onSignedIn() {
-            setVisible(false);
+            mCanSignIn = false;
+            updateVisibility();
         }
 
         @Override
         public void onSignedOut() {
-            setVisible(true);
+            mCanSignIn = mSigninManager.isSignInAllowed();
+            updateVisibility();
+        }
+
+        @Override
+        public void onCategoryStatusChanged(
+                @CategoryInt int category, @CategoryStatus int newStatus) {
+            if (!SnippetsBridge.isCategoryRemote(category)) return;
+
+            // Checks whether the category is enabled first to avoid unnecessary calls across JNI.
+            mCanShowPersonalizedSuggestions = SnippetsBridge.isCategoryEnabled(category)
+                    || mSuggestionsSource.areRemoteSuggestionsEnabled();
+            updateVisibility();
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/StatusItem.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/StatusItem.java
index e603b04..f359a848 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/StatusItem.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/StatusItem.java
@@ -63,4 +63,8 @@
         assert holder instanceof StatusCardViewHolder;
         ((StatusCardViewHolder) holder).onBindViewHolder(this);
     }
+
+    public void setVisible(boolean visible) {
+        setVisibilityInternal(visible);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeader.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeader.java
index 9f70931f..4cad997b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeader.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeader.java
@@ -18,7 +18,7 @@
 
     public SectionHeader(String headerText) {
         this.mHeaderText = headerText;
-        setVisible(true);
+        setVisibilityInternal(true);
     }
 
     @Override
@@ -41,4 +41,8 @@
     public void visitOptionalItem(NodeVisitor visitor) {
         visitor.visitHeader();
     }
+
+    public void setVisible(boolean visible) {
+        setVisibilityInternal(visible);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java
index ee4f3ef4..34130f7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java
@@ -7,6 +7,7 @@
 import android.graphics.Bitmap;
 
 import org.chromium.base.Callback;
+import org.chromium.base.ObserverList;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.chrome.browser.ntp.cards.SuggestionsCategoryInfo;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -22,13 +23,17 @@
     private static final String TAG = "SnippetsBridge";
 
     private long mNativeSnippetsBridge;
-    private SuggestionsSource.Observer mObserver;
+    private final ObserverList<Observer> mObserverList = new ObserverList<>();
 
     public static boolean isCategoryStatusAvailable(@CategoryStatus int status) {
-        // Note: This code is duplicated in content_suggestions_category_status.cc.
+        // Note: This code is duplicated in category_status.cc.
         return status == CategoryStatus.AVAILABLE_LOADING || status == CategoryStatus.AVAILABLE;
     }
 
+    public static boolean isCategoryRemote(@CategoryInt int category) {
+        return category > KnownCategories.REMOTE_CATEGORIES_OFFSET;
+    }
+
     /** Returns whether the category is considered "enabled", and can show content suggestions. */
     public static boolean isCategoryEnabled(@CategoryStatus int status) {
         switch (status) {
@@ -62,7 +67,7 @@
         assert mNativeSnippetsBridge != 0;
         nativeDestroy(mNativeSnippetsBridge);
         mNativeSnippetsBridge = 0;
-        mObserver = null;
+        mObserverList.clear();
     }
 
     /**
@@ -83,8 +88,9 @@
         nativeSetRemoteSuggestionsEnabled(enabled);
     }
 
-    public static boolean areRemoteSuggestionsEnabled() {
-        return nativeAreRemoteSuggestionsEnabled();
+    @Override
+    public boolean areRemoteSuggestionsEnabled() {
+        return nativeAreRemoteSuggestionsEnabled(mNativeSnippetsBridge);
     }
 
     public static boolean areRemoteSuggestionsManaged() {
@@ -174,9 +180,14 @@
     }
 
     @Override
-    public void setObserver(Observer observer) {
+    public void addObserver(Observer observer) {
         assert observer != null;
-        mObserver = observer;
+        mObserverList.addObserver(observer);
+    }
+
+    @Override
+    public void removeObserver(Observer observer) {
+        mObserverList.removeObserver(observer);
     }
 
     @Override
@@ -229,22 +240,26 @@
 
     @CalledByNative
     private void onNewSuggestions(@CategoryInt int category) {
-        if (mObserver != null) mObserver.onNewSuggestions(category);
+        for (Observer observer : mObserverList) observer.onNewSuggestions(category);
     }
 
     @CalledByNative
     private void onCategoryStatusChanged(@CategoryInt int category, @CategoryStatus int newStatus) {
-        if (mObserver != null) mObserver.onCategoryStatusChanged(category, newStatus);
+        for (Observer observer : mObserverList) {
+            observer.onCategoryStatusChanged(category, newStatus);
+        }
     }
 
     @CalledByNative
     private void onSuggestionInvalidated(@CategoryInt int category, String idWithinCategory) {
-        if (mObserver != null) mObserver.onSuggestionInvalidated(category, idWithinCategory);
+        for (Observer observer : mObserverList) {
+            observer.onSuggestionInvalidated(category, idWithinCategory);
+        }
     }
 
     @CalledByNative
     private void onFullRefreshRequired() {
-        if (mObserver != null) mObserver.onFullRefreshRequired();
+        for (Observer observer : mObserverList) observer.onFullRefreshRequired();
     }
 
     private native long nativeInit(Profile profile);
@@ -253,7 +268,7 @@
     private static native void nativeRemoteSuggestionsSchedulerOnFetchDue();
     private static native void nativeRemoteSuggestionsSchedulerRescheduleFetching();
     private static native void nativeSetRemoteSuggestionsEnabled(boolean enabled);
-    private static native boolean nativeAreRemoteSuggestionsEnabled();
+    private native boolean nativeAreRemoteSuggestionsEnabled(long nativeNTPSnippetsBridge);
     private static native boolean nativeAreRemoteSuggestionsManaged();
     private static native boolean nativeAreRemoteSuggestionsManagedByCustodian();
     private static native void nativeSetContentSuggestionsNotificationsEnabled(boolean enabled);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SuggestionsSource.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SuggestionsSource.java
index b20d676..8d36eca 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SuggestionsSource.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SuggestionsSource.java
@@ -43,6 +43,11 @@
     void fetchRemoteSuggestions();
 
     /**
+     * @return Whether remote suggestions are enabled.
+     */
+    boolean areRemoteSuggestionsEnabled();
+
+    /**
      * Gets the categories in the order in which they should be displayed.
      * @return The categories.
      */
@@ -120,5 +125,26 @@
     /**
      * Sets the recipient for update events from the source.
      */
-    void setObserver(Observer observer);
+    void addObserver(Observer observer);
+
+    /**
+     * Removes an observer. Is no-op if the observer was not already registered.
+     */
+    void removeObserver(Observer observer);
+
+    /** No-op implementation of {@link SuggestionsSource.Observer}. */
+    class EmptyObserver implements Observer {
+        @Override
+        public void onNewSuggestions(@CategoryInt int category) {}
+
+        @Override
+        public void onCategoryStatusChanged(
+                @CategoryInt int category, @CategoryStatus int newStatus) {}
+
+        @Override
+        public void onSuggestionInvalidated(@CategoryInt int category, String idWithinCategory) {}
+
+        @Override
+        public void onFullRefreshRequired() {}
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/physicalweb/PhysicalWeb.java b/chrome/android/java/src/org/chromium/chrome/browser/physicalweb/PhysicalWeb.java
index 2405c574..cd6af2b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/physicalweb/PhysicalWeb.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/physicalweb/PhysicalWeb.java
@@ -14,6 +14,7 @@
 import android.os.Build;
 
 import org.chromium.base.ContextUtils;
+import org.chromium.base.SysUtils;
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.IntentHandler;
 import org.chromium.chrome.browser.UrlConstants;
@@ -97,7 +98,9 @@
         // In the case that the user has disabled our flag and restarted, this is a minimal code
         // path to disable our subscription to Nearby.
         if (!featureIsEnabled()) {
-            new NearbyBackgroundSubscription(NearbySubscription.UNSUBSCRIBE).run();
+            if (!SysUtils.isLowEndDevice()) {
+                new NearbyBackgroundSubscription(NearbySubscription.UNSUBSCRIBE).run();
+            }
             return;
         }
 
@@ -131,7 +134,8 @@
         return locationUtils.isSystemLocationSettingEnabled()
                 && locationUtils.hasAndroidLocationPermission()
                 && TemplateUrlService.getInstance().isDefaultSearchEngineGoogle()
-                && !Profile.getLastUsedProfile().isOffTheRecord();
+                && !Profile.getLastUsedProfile().isOffTheRecord()
+                && !SysUtils.isLowEndDevice();
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ContentSuggestionsPreferences.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ContentSuggestionsPreferences.java
index 529a5d6..b264fd4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ContentSuggestionsPreferences.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ContentSuggestionsPreferences.java
@@ -23,7 +23,9 @@
 import org.chromium.chrome.browser.ntp.snippets.ContentSuggestionsNotificationAction;
 import org.chromium.chrome.browser.ntp.snippets.ContentSuggestionsNotificationOptOut;
 import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge;
+import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.suggestions.SuggestionsDependencyFactory;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -74,7 +76,12 @@
         setHasOptionsMenu(true);
         finishSwitchInitialisation();
 
-        boolean isEnabled = SnippetsBridge.areRemoteSuggestionsEnabled();
+        SuggestionsSource suggestionsSource =
+                SuggestionsDependencyFactory.getInstance().createSuggestionSource(
+                        Profile.getLastUsedProfile());
+        boolean isEnabled = suggestionsSource.areRemoteSuggestionsEnabled();
+        suggestionsSource.onDestroy();
+
         mIsEnabled = !isEnabled; // Opposite so that we trigger side effects below.
         updatePreferences(isEnabled);
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/privacy/PrivacyPreferences.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/privacy/PrivacyPreferences.java
index d91b9520..95504f5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/privacy/PrivacyPreferences.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/privacy/PrivacyPreferences.java
@@ -19,7 +19,6 @@
 import org.chromium.chrome.browser.contextualsearch.ContextualSearchFieldTrial;
 import org.chromium.chrome.browser.help.HelpAndFeedback;
 import org.chromium.chrome.browser.physicalweb.PhysicalWeb;
-import org.chromium.chrome.browser.precache.PrecacheLauncher;
 import org.chromium.chrome.browser.preferences.ChromeBaseCheckBoxPreference;
 import org.chromium.chrome.browser.preferences.ManagedPreferenceDelegate;
 import org.chromium.chrome.browser.preferences.PrefServiceBridge;
@@ -130,7 +129,6 @@
                     (boolean) newValue);
         } else if (PREF_NETWORK_PREDICTIONS.equals(key)) {
             PrefServiceBridge.getInstance().setNetworkPredictionEnabled((boolean) newValue);
-            PrecacheLauncher.updatePrecachingEnabled(getActivity());
         } else if (PREF_NAVIGATION_ERROR.equals(key)) {
             PrefServiceBridge.getInstance().setResolveNavigationErrorEnabled((boolean) newValue);
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/signin/OWNERS
index f5f6e7ea..6d22a37 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/OWNERS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/OWNERS
@@ -1 +1,4 @@
+bsazonov@chromium.org
 gogerald@chromium.org
+
+# COMPONENT: Services>Signin
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/TileGrid.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/TileGrid.java
index 87cd6b2c..146072c6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/TileGrid.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/TileGrid.java
@@ -66,7 +66,7 @@
 
     @Override
     public void onTileDataChanged() {
-        setVisible(mTileGroup.getTiles().length != 0);
+        setVisibilityInternal(mTileGroup.getTiles().length != 0);
         if (isVisible()) notifyItemChanged(0, new ViewHolder.UpdateTilesCallback(mTileGroup));
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateImpl.java
index 7eb8dd1..730ebbcf 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateImpl.java
@@ -11,6 +11,7 @@
 import org.chromium.chrome.browser.AppHooks;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.datausage.DataUseTabUIManager;
+import org.chromium.chrome.browser.externalnav.ExternalNavigationDelegateImpl;
 import org.chromium.chrome.browser.externalnav.ExternalNavigationHandler;
 import org.chromium.chrome.browser.externalnav.ExternalNavigationHandler.OverrideUrlLoadingResult;
 import org.chromium.chrome.browser.externalnav.ExternalNavigationParams;
@@ -52,6 +53,14 @@
 
     /**
      * Constructs a new instance of {@link InterceptNavigationDelegateImpl} with the given
+     * {@link ExternalNavigationDelegate}.
+     */
+    public InterceptNavigationDelegateImpl(ExternalNavigationDelegateImpl delegate, Tab tab) {
+        this(new ExternalNavigationHandler(delegate), tab);
+    }
+
+    /**
+     * Constructs a new instance of {@link InterceptNavigationDelegateImpl} with the given
      * {@link ExternalNavigationHandler}.
      */
     public InterceptNavigationDelegateImpl(ExternalNavigationHandler externalNavHandler, Tab tab) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
index 44be9d6..1c1d0ae 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
@@ -2794,8 +2794,7 @@
     /**
      * See {@link #mInterceptNavigationDelegate}.
      */
-    @VisibleForTesting
-    protected void setInterceptNavigationDelegate(InterceptNavigationDelegateImpl delegate) {
+    public void setInterceptNavigationDelegate(InterceptNavigationDelegateImpl delegate) {
         mInterceptNavigationDelegate = delegate;
         nativeSetInterceptNavigationDelegate(mNativeTabAndroid, delegate);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrExternalNavigationDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrExternalNavigationDelegate.java
new file mode 100644
index 0000000..26bf71f
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrExternalNavigationDelegate.java
@@ -0,0 +1,41 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.vr_shell;
+
+import android.content.Intent;
+
+import org.chromium.chrome.browser.externalnav.ExternalNavigationDelegateImpl;
+import org.chromium.chrome.browser.tab.Tab;
+
+/**
+ * A custom external navigation delegate that show DOFF instead of sending intent to external app.
+ */
+public class VrExternalNavigationDelegate extends ExternalNavigationDelegateImpl {
+    public VrExternalNavigationDelegate(Tab tab) {
+        super(tab);
+    }
+
+    @Override
+    public void startActivity(Intent intent, boolean proxy) {
+        VrShellDelegate.showDoffAndExitVr(false);
+    }
+
+    @Override
+    public boolean startActivityIfNeeded(Intent intent, boolean proxy) {
+        return false;
+    }
+
+    @Override
+    public void startIncognitoIntent(Intent intent, String referrerUrl, String fallbackUrl, Tab tab,
+            boolean needsToCloseTab, boolean proxy) {
+        VrShellDelegate.showDoffAndExitVr(false);
+    }
+
+    @Override
+    public void startFileIntent(
+            Intent intent, String referrerUrl, Tab tab, boolean needsToCloseTab) {
+        VrShellDelegate.showDoffAndExitVr(false);
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java
index fdc2049..69032c5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellDelegate.java
@@ -363,6 +363,11 @@
         }
     }
 
+    public static void showDoffAndExitVr(boolean optional) {
+        assert sInstance != null;
+        sInstance.showDoffAndExitVrInternal(optional);
+    }
+
     @CalledByNative
     private static VrShellDelegate getInstance() {
         Activity activity = ApplicationStatus.getLastTrackedFocusedActivity();
@@ -1145,7 +1150,7 @@
         mShouldShowPageInfo = false;
     }
 
-    /* package */ void showDoffAndExitVr(boolean optional) {
+    private void showDoffAndExitVrInternal(boolean optional) {
         if (mShowingDaydreamDoff) return;
         if (showDoff(optional)) return;
         shutdownVr(true /* disableVrMode */, false /* canReenter */, true /* stayingInChrome */);
@@ -1153,7 +1158,7 @@
 
     /* package */ void onUnhandledPageInfo() {
         mShouldShowPageInfo = true;
-        showDoffAndExitVr(true);
+        showDoffAndExitVrInternal(true);
     }
 
     /* package */ void exitCct() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellImpl.java
index fe861316..3b52a8a3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr_shell/VrShellImpl.java
@@ -35,6 +35,7 @@
 import org.chromium.chrome.browser.UrlConstants;
 import org.chromium.chrome.browser.ntp.NewTabPage;
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
+import org.chromium.chrome.browser.tab.InterceptNavigationDelegateImpl;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabObserver;
 import org.chromium.chrome.browser.tab.TabRedirectHandler;
@@ -71,8 +72,6 @@
 
     // Increasing DPR any more than this doesn't appear to increase text quality.
     private static final float DEFAULT_DPR = 1.4f;
-    // For WebVR we just create a DPR 1.0 display that matches the physical display size.
-    private static final float WEBVR_DPR = 1.0f;
     // Fairly arbitrary values that put a good amount of content on the screen without making the
     // text too small to read.
     @VisibleForTesting
@@ -87,6 +86,7 @@
     private final ChromeActivity mActivity;
     private final VrShellDelegate mDelegate;
     private final VirtualDisplayAndroid mContentVirtualDisplay;
+    private final InterceptNavigationDelegateImpl mInterceptNavigationDelegate;
     private final TabRedirectHandler mTabRedirectHandler;
     private final TabObserver mTabObserver;
     private final TabModelSelectorObserver mTabModelSelectorObserver;
@@ -111,6 +111,7 @@
 
     private boolean mReprojectedRendering;
 
+    private InterceptNavigationDelegateImpl mNonVrInterceptNavigationDelegate;
     private TabRedirectHandler mNonVrTabRedirectHandler;
     private TabModelSelector mTabModelSelector;
     private float mLastContentWidth;
@@ -163,6 +164,10 @@
         mContentVirtualDisplay = VirtualDisplayAndroid.createVirtualDisplay();
         mContentVirtualDisplay.setTo(primaryDisplay);
 
+        mInterceptNavigationDelegate = new InterceptNavigationDelegateImpl(
+                new VrExternalNavigationDelegate(mActivity.getActivityTab()),
+                mActivity.getActivityTab());
+
         mTabRedirectHandler = new TabRedirectHandler(mActivity) {
             @Override
             public boolean shouldStayInChrome(boolean hasExternalProtocol) {
@@ -354,7 +359,7 @@
         setSplashScreenIcon();
 
         // Set the UI and content sizes before we load the UI.
-        updateWebVrDisplaySize(forWebVr);
+        setContentCssSize(DEFAULT_CONTENT_WIDTH, DEFAULT_CONTENT_HEIGHT, DEFAULT_DPR);
 
         reparentAllTabs(mContentVrWindowAndroid);
         swapToForegroundTab();
@@ -404,6 +409,8 @@
     }
 
     private void initializeTabForVR() {
+        mNonVrInterceptNavigationDelegate = mTab.getInterceptNavigationDelegate();
+        mTab.setInterceptNavigationDelegate(mInterceptNavigationDelegate);
         // Make sure we are not redirecting to another app, i.e. out of VR mode.
         mNonVrTabRedirectHandler = mTab.getTabRedirectHandler();
         mTab.setTabRedirectHandler(mTabRedirectHandler);
@@ -411,6 +418,7 @@
     }
 
     private void restoreTabFromVR() {
+        mTab.setInterceptNavigationDelegate(mNonVrInterceptNavigationDelegate);
         mTab.setTabRedirectHandler(mNonVrTabRedirectHandler);
         mNonVrTabRedirectHandler = null;
     }
@@ -436,7 +444,7 @@
     // Exits VR, telling the user to remove their headset, and returning to Chromium.
     @CalledByNative
     public void forceExitVr() {
-        mDelegate.showDoffAndExitVr(false);
+        VrShellDelegate.showDoffAndExitVr(false);
     }
 
     // Called because showing PageInfo isn't supported in VR. This happens when the user clicks on
@@ -576,17 +584,6 @@
     public void setWebVrModeEnabled(boolean enabled, boolean showToast) {
         mContentVrWindowAndroid.setVSyncPaused(enabled);
         nativeSetWebVrMode(mNativeVrShell, enabled, showToast);
-        updateWebVrDisplaySize(enabled);
-    }
-
-    private void updateWebVrDisplaySize(boolean inWebVr) {
-        if (inWebVr) {
-            DisplayAndroid primaryDisplay = DisplayAndroid.getNonMultiDisplay(mActivity);
-            setContentCssSize(
-                    primaryDisplay.getDisplayWidth(), primaryDisplay.getDisplayHeight(), WEBVR_DPR);
-        } else {
-            setContentCssSize(DEFAULT_CONTENT_WIDTH, DEFAULT_CONTENT_HEIGHT, DEFAULT_DPR);
-        }
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java
index ee14c159..c2f76ec 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java
@@ -996,6 +996,12 @@
         for (BottomSheetObserver o : mObservers) o.onSheetOpened();
         announceForAccessibility(getResources().getString(R.string.bottom_sheet_opened));
         mActivity.addViewObscuringAllTabs(this);
+
+        setFocusable(true);
+        setFocusableInTouchMode(true);
+        setContentDescription(
+                getResources().getString(R.string.bottom_sheet_accessibility_description));
+        if (getFocusedChild() == null) requestFocus();
     }
 
     /**
@@ -1011,6 +1017,10 @@
         clearFocus();
         mActivity.removeViewObscuringAllTabs(this);
 
+        setFocusable(false);
+        setFocusableInTouchMode(false);
+        setContentDescription(null);
+
         showHelpBubbleIfNecessary();
     }
 
diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd
index 18c473a..1e53a36 100644
--- a/chrome/android/java/strings/android_chrome_strings.grd
+++ b/chrome/android/java/strings/android_chrome_strings.grd
@@ -3106,6 +3106,9 @@
       <message name="IDS_BOTTOM_SHEET_ACCESSIBILITY_EXPAND_BUTTON_HELP_BUBBLE_MESSAGE" desc="Text displayed in a help bubble above the toolbar expand button prompting users to tap the button to see their bookmarks and other content when accessibility is enabled.">
         Tap to see bookmarks, downloads, and history
       </message>
+     <message name="IDS_BOTTOM_SHEET_ACCESSIBILITY_DESCRIPTION" desc="Accessibility string read when the navigation panel is focused.">
+        Navigation panel
+      </message>
       <message name="IDS_BOTTOM_SHEET_OPENED" desc="Accessibility string read when the bottom navigation panel is opened.">
         Navigation panel opened
       </message>
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 0e5344fb..b22a7fc 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -1303,6 +1303,7 @@
   "java/src/org/chromium/chrome/browser/vr_shell/VrCoreInfo.java",
   "java/src/org/chromium/chrome/browser/vr_shell/VrCoreVersionCheckerImpl.java",
   "java/src/org/chromium/chrome/browser/vr_shell/VrDaydreamApiImpl.java",
+  "java/src/org/chromium/chrome/browser/vr_shell/VrExternalNavigationDelegate.java",
   "java/src/org/chromium/chrome/browser/vr_shell/VrShellImpl.java",
   "java/src/org/chromium/chrome/browser/vr_shell/VrWindowAndroid.java",
   "java/src/org/chromium/chrome/browser/vr_shell/OnDispatchTouchEventCallback.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeBackgroundServiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeBackgroundServiceTest.java
index 542575f..78806a0e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeBackgroundServiceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeBackgroundServiceTest.java
@@ -21,7 +21,6 @@
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.browser.ntp.snippets.SnippetsLauncher;
-import org.chromium.chrome.browser.precache.PrecacheController;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
 /**
@@ -38,8 +37,6 @@
         private boolean mDidLaunchBrowser = false;
         private boolean mDidFetchSnippets = false;
         private boolean mDidRescheduleFetching = false;
-        private boolean mHasPrecacheInstance = true;
-        private boolean mPrecachingStarted = false;
 
         @Override
         protected void launchBrowser(Context context, String tag) {
@@ -57,49 +54,27 @@
         }
 
         @Override
-        protected boolean hasPrecacheInstance() {
-            return mHasPrecacheInstance;
-        }
-
-        @Override
-        protected void precache(Context context, String tag) {
-            if (!mHasPrecacheInstance) {
-                mPrecachingStarted = true;
-            }
-        }
-
-        @Override
         protected void rescheduleBackgroundSyncTasksOnUpgrade() {}
 
         @Override
-        protected void reschedulePrecacheTasksOnUpgrade() {}
-
-        @Override
         protected void rescheduleOfflinePages() {}
 
         // Posts an assertion task to the UI thread. Since this is only called after the call
         // to onRunTask, it will be enqueued after any possible call to launchBrowser, and we
         // can reliably check whether launchBrowser was called.
         protected void checkExpectations(final boolean expectedLaunchBrowser,
-                final boolean expectedPrecacheStarted, final boolean expectedFetchSnippets,
-                final boolean expectedRescheduleFetching) {
+                final boolean expectedFetchSnippets, final boolean expectedRescheduleFetching) {
             ThreadUtils.runOnUiThread(new Runnable() {
                 @Override
                 public void run() {
                     Assert.assertEquals("StartedService", expectedLaunchBrowser, mDidLaunchBrowser);
                     Assert.assertEquals(
-                            "StartedPrecache", expectedPrecacheStarted, mPrecachingStarted);
-                    Assert.assertEquals(
                             "FetchedSnippets", expectedFetchSnippets, mDidFetchSnippets);
                     Assert.assertEquals("RescheduledFetching", expectedRescheduleFetching,
                             mDidRescheduleFetching);
                 }
             });
         }
-
-        protected void deletePrecacheInstance() {
-            mHasPrecacheInstance = false;
-        }
     }
 
     @Before
@@ -126,17 +101,17 @@
         mSnippetsLauncher = null;
     }
 
-    private void startOnRunTaskAndVerify(String taskTag, boolean shouldStart,
-            boolean shouldPrecache, boolean shouldFetchSnippets) {
+    private void startOnRunTaskAndVerify(
+            String taskTag, boolean shouldStart, boolean shouldFetchSnippets) {
         mTaskService.onRunTask(new TaskParams(taskTag));
-        mTaskService.checkExpectations(shouldStart, shouldPrecache, shouldFetchSnippets, false);
+        mTaskService.checkExpectations(shouldStart, shouldFetchSnippets, false);
     }
 
     @Test
     @SmallTest
     @Feature({"BackgroundSync"})
     public void testBackgroundSyncNoLaunchBrowserWhenInstanceExists() {
-        startOnRunTaskAndVerify(BackgroundSyncLauncher.TASK_TAG, false, false, false);
+        startOnRunTaskAndVerify(BackgroundSyncLauncher.TASK_TAG, false, false);
     }
 
     @Test
@@ -144,21 +119,21 @@
     @Feature({"BackgroundSync"})
     public void testBackgroundSyncLaunchBrowserWhenInstanceDoesNotExist() {
         deleteSyncLauncherInstance();
-        startOnRunTaskAndVerify(BackgroundSyncLauncher.TASK_TAG, true, false, false);
+        startOnRunTaskAndVerify(BackgroundSyncLauncher.TASK_TAG, true, false);
     }
 
     @Test
     @SmallTest
     @Feature({"NTPSnippets"})
     public void testNTPSnippetsFetchWifiNoLaunchBrowserWhenInstanceExists() {
-        startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG_WIFI, false, false, true);
+        startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG_WIFI, false, true);
     }
 
     @Test
     @SmallTest
     @Feature({"NTPSnippets"})
     public void testNTPSnippetsFetchFallbackNoLaunchBrowserWhenInstanceExists() {
-        startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG_FALLBACK, false, false, true);
+        startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG_FALLBACK, false, true);
     }
 
     @Test
@@ -166,7 +141,7 @@
     @Feature({"NTPSnippets"})
     public void testNTPSnippetsFetchWifiLaunchBrowserWhenInstanceDoesNotExist() {
         deleteSnippetsLauncherInstance();
-        startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG_WIFI, true, false, true);
+        startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG_WIFI, true, true);
     }
 
     @Test
@@ -174,27 +149,12 @@
     @Feature({"NTPSnippets"})
     public void testNTPSnippetsFetchFallbackLaunchBrowserWhenInstanceDoesNotExist() {
         deleteSnippetsLauncherInstance();
-        startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG_FALLBACK, true, false, true);
-    }
-
-    @Test
-    @SmallTest
-    @Feature({"Precache"})
-    public void testPrecacheNoLaunchBrowserWhenInstanceExists() {
-        startOnRunTaskAndVerify(PrecacheController.PERIODIC_TASK_TAG, false, false, false);
-    }
-
-    @Test
-    @SmallTest
-    @Feature({"Precache"})
-    public void testPrecacheLaunchBrowserWhenInstanceDoesNotExist() {
-        mTaskService.deletePrecacheInstance();
-        startOnRunTaskAndVerify(PrecacheController.PERIODIC_TASK_TAG, true, true, false);
+        startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG_FALLBACK, true, true);
     }
 
     private void startOnInitializeTasksAndVerify(boolean shouldStart, boolean shouldReschedule) {
         mTaskService.onInitializeTasks();
-        mTaskService.checkExpectations(shouldStart, false, false, shouldReschedule);
+        mTaskService.checkExpectations(shouldStart, false, shouldReschedule);
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/EmulatedVrController.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/EmulatedVrController.java
index 60352ac..4fd8810 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/EmulatedVrController.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/EmulatedVrController.java
@@ -9,6 +9,8 @@
 
 import com.google.vr.testframework.controller.ControllerTestApi;
 
+import org.junit.Assert;
+
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -21,6 +23,7 @@
  *   - PairedControllerAddress: "FOO"
  */
 public class EmulatedVrController {
+    public enum ScrollDirection { UP, DOWN, LEFT, RIGHT }
     private final ControllerTestApi mApi;
 
     public EmulatedVrController(Context context) {
@@ -87,31 +90,41 @@
     }
 
     /**
-     * Performs an upward swipe on the touchpad, which scrolls down in VR Shell.
+     * Performs an swipe on the touchpad in order to scroll in the specified
+     * direction while in the VR browser.
      * Note that scrolling this way is not consistent, i.e. scrolling down then
      * scrolling up at the same speed won't necessarily scroll back to the exact
      * starting position on the page.
      *
+     * @param direction the ScrollDirection to scroll with
      * @param steps the number of intermediate steps to send while scrolling
      * @param speed how long to wait between steps in the scroll, with higher
      * numbers resulting in a faster scroll
      */
-    public void scrollDown(int steps, int speed) {
-        performLinearTouchpadMovement(0.5f, 0.9f, 0.5f, 0.1f, steps, speed);
-    }
-
-    /**
-     * Performs a downward swipe on the touchpad, which scrolls up in VR Shell.
-     * Note that scrolling this way is not consistent, i.e. scrolling down then
-     * scrolling up at the same speed won't necessarily scroll back to the exact
-     * starting position on the page.
-     *
-     * @param steps the number of intermediate steps to send while scrolling
-     * @param speed how long to wait between steps in the scroll, with higher
-     * numbers resulting in a faster scroll
-     */
-    public void scrollUp(int steps, int speed) {
-        performLinearTouchpadMovement(0.5f, 0.1f, 0.5f, 0.9f, steps, speed);
+    public void scroll(ScrollDirection direction, int steps, int speed) {
+        float startX, startY, endX, endY;
+        startX = startY = endX = endY = 0.5f;
+        switch (direction) {
+            case UP:
+                startY = 0.1f;
+                endY = 0.9f;
+                break;
+            case DOWN:
+                startY = 0.9f;
+                endY = 0.1f;
+                break;
+            case LEFT:
+                startX = 0.1f;
+                endX = 0.9f;
+                break;
+            case RIGHT:
+                startX = 0.9f;
+                endX = 0.1f;
+                break;
+            default:
+                Assert.fail("Unknown scroll direction enum given");
+        }
+        performLinearTouchpadMovement(startX, startY, endX, endY, steps, speed);
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/VrShellControllerInputTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/VrShellControllerInputTest.java
index 2ccb9e9..88a4082 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/VrShellControllerInputTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/VrShellControllerInputTest.java
@@ -37,17 +37,17 @@
     @Rule
     public VrTestRule mVrTestRule = new VrTestRule();
 
-    // TODO(bsheedy): Modify test to also check horizontal scrolling
     /**
-     * Verifies that swiping up/down on the Daydream controller's touchpad scrolls
-     * the webpage while in the VR browser.
+     * Verifies that swiping up/down/left/right on the Daydream controller's
+     * touchpad scrolls the webpage while in the VR browser.
      */
     @Test
     @Restriction({RESTRICTION_TYPE_DEVICE_DAYDREAM, RESTRICTION_TYPE_VIEWER_DAYDREAM})
     @MediumTest
     public void testControllerScrolling() throws InterruptedException {
         // Load page in VR and make sure the controller is pointed at the content quad
-        mVrTestRule.loadUrl("chrome://credits", PAGE_LOAD_TIMEOUT_S);
+        mVrTestRule.loadUrl(
+                mVrTestRule.getHtmlTestFile("test_controller_scrolling"), PAGE_LOAD_TIMEOUT_S);
         VrTransitionUtils.forceEnterVr();
         VrTransitionUtils.waitForVrEntry(POLL_TIMEOUT_LONG_MS);
         EmulatedVrController controller = new EmulatedVrController(mVrTestRule.getActivity());
@@ -59,23 +59,39 @@
         CriteriaHelper.pollUiThread(new Criteria() {
             @Override
             public boolean isSatisfied() {
-                return cvc.computeVerticalScrollRange() > cvc.getContainerView().getHeight();
+                return cvc.computeVerticalScrollRange() > cvc.getContainerView().getHeight()
+                        && cvc.computeHorizontalScrollRange() > cvc.getContainerView().getWidth();
             }
         }, POLL_TIMEOUT_LONG_MS, POLL_CHECK_INTERVAL_LONG_MS);
 
         // Test that scrolling down works
-        int startScrollY = cvc.getNativeScrollYForTest();
+        int startScrollPoint = cvc.getNativeScrollYForTest();
         // Arbitrary, but valid values to scroll smoothly
         int scrollSteps = 20;
         int scrollSpeed = 60;
-        controller.scrollDown(scrollSteps, scrollSpeed);
-        int endScrollY = cvc.getNativeScrollYForTest();
-        Assert.assertTrue("Controller was able to scroll down", startScrollY < endScrollY);
+        controller.scroll(EmulatedVrController.ScrollDirection.DOWN, scrollSteps, scrollSpeed);
+        // We need this second scroll down, otherwise the horizontal scrolling becomes flaky
+        // TODO(bsheedy): Figure out why this is the case
+        controller.scroll(EmulatedVrController.ScrollDirection.DOWN, scrollSteps, scrollSpeed);
+        int endScrollPoint = cvc.getNativeScrollYForTest();
+        Assert.assertTrue("Controller was able to scroll down", startScrollPoint < endScrollPoint);
 
         // Test that scrolling up works
-        startScrollY = endScrollY;
-        controller.scrollUp(scrollSteps, scrollSpeed);
-        endScrollY = cvc.getNativeScrollYForTest();
-        Assert.assertTrue("Controller was able to scroll up", startScrollY > endScrollY);
+        startScrollPoint = endScrollPoint;
+        controller.scroll(EmulatedVrController.ScrollDirection.UP, scrollSteps, scrollSpeed);
+        endScrollPoint = cvc.getNativeScrollYForTest();
+        Assert.assertTrue("Controller was able to scroll up", startScrollPoint > endScrollPoint);
+
+        // Test that scrolling right works
+        startScrollPoint = cvc.getNativeScrollXForTest();
+        controller.scroll(EmulatedVrController.ScrollDirection.RIGHT, scrollSteps, scrollSpeed);
+        endScrollPoint = cvc.getNativeScrollXForTest();
+        Assert.assertTrue("Controller was able to scroll right", startScrollPoint < endScrollPoint);
+
+        // Test that scrolling left works
+        startScrollPoint = endScrollPoint;
+        controller.scroll(EmulatedVrController.ScrollDirection.LEFT, scrollSteps, scrollSpeed);
+        endScrollPoint = cvc.getNativeScrollXForTest();
+        Assert.assertTrue("Controller was able to scroll left", startScrollPoint > endScrollPoint);
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/VrShellTransitionTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/VrShellTransitionTest.java
index 08149a3..a5a1d465 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/VrShellTransitionTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/VrShellTransitionTest.java
@@ -29,7 +29,6 @@
 import org.chromium.chrome.browser.vr_shell.util.VrTransitionUtils;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.ui.display.DisplayAndroid;
 
 import java.util.concurrent.TimeoutException;
 
@@ -138,27 +137,20 @@
                 mVrTestRule.getHtmlTestFile("test_navigation_webvr_page"), PAGE_LOAD_TIMEOUT_S);
         VrTransitionUtils.enterPresentationOrFail(mVrTestRule.getFirstTabCvc());
 
-        // Validate our size is what we expect while presenting.
-        DisplayAndroid primaryDisplay =
-                DisplayAndroid.getNonMultiDisplay(mVrTestRule.getActivity());
-        float expectedWidth = primaryDisplay.getDisplayWidth();
-        float expectedHeight = primaryDisplay.getDisplayHeight();
+        // Validate our size is what we expect while in VR.
+        float expectedWidth = VrShellImpl.DEFAULT_CONTENT_WIDTH;
+        float expectedHeight = VrShellImpl.DEFAULT_CONTENT_HEIGHT;
+        String javascript = "Math.abs(screen.width - " + expectedWidth + ") <= 1 && "
+                + "Math.abs(screen.height - " + expectedHeight + ") <= 1";
         Assert.assertTrue(mVrTestRule.pollJavaScriptBoolean(
-                "screen.width == " + expectedWidth + " && screen.height == " + expectedHeight,
-                POLL_TIMEOUT_LONG_MS, mVrTestRule.getFirstTabWebContents()));
+                javascript, POLL_TIMEOUT_LONG_MS, mVrTestRule.getFirstTabWebContents()));
 
         // Exit presentation through JavaScript.
         mVrTestRule.runJavaScriptOrFail("vrDisplay.exitPresent();", POLL_TIMEOUT_SHORT_MS,
                 mVrTestRule.getFirstTabWebContents());
 
-        // Validate our size is what we expect while in the VR browser.
-        expectedWidth = VrShellImpl.DEFAULT_CONTENT_WIDTH;
-        expectedHeight = VrShellImpl.DEFAULT_CONTENT_HEIGHT;
-
         // We aren't comparing for equality because there is some rounding that occurs.
-        Assert.assertTrue(
-                mVrTestRule.pollJavaScriptBoolean("Math.abs(screen.width - " + expectedWidth
-                                + ") < 2 && Math.abs(screen.height - " + expectedHeight + ") < 2",
-                        POLL_TIMEOUT_LONG_MS, mVrTestRule.getFirstTabWebContents()));
+        Assert.assertTrue(mVrTestRule.pollJavaScriptBoolean(
+                javascript, POLL_TIMEOUT_LONG_MS, mVrTestRule.getFirstTabWebContents()));
     }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/NewTabPageAdapterTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/NewTabPageAdapterTest.java
index 99b03b47..7cb9ae3 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/NewTabPageAdapterTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/NewTabPageAdapterTest.java
@@ -11,7 +11,6 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
@@ -63,6 +62,7 @@
 import org.chromium.chrome.browser.ntp.snippets.CategoryStatus;
 import org.chromium.chrome.browser.ntp.snippets.KnownCategories;
 import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
+import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource;
 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
 import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
 import org.chromium.chrome.browser.signin.SigninManager;
@@ -80,6 +80,7 @@
 import org.chromium.testing.local.LocalRobolectricTestRunner;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -254,10 +255,7 @@
         mSource.setInfoForCategory(
                 TEST_CATEGORY, new CategoryInfoBuilder(TEST_CATEGORY).showIfEmpty().build());
 
-        when(mUiDelegate.getSuggestionsSource()).thenReturn(mSource);
-        when(mUiDelegate.getEventReporter()).thenReturn(mock(SuggestionsEventReporter.class));
-        when(mUiDelegate.getSuggestionsRanker()).thenReturn(mock(SuggestionsRanker.class));
-
+        resetUiDelegate();
         reloadNtp();
     }
 
@@ -892,28 +890,26 @@
     @Test
     @Feature({"Ntp"})
     public void testSigninPromo() {
+        @CategoryInt
+        final int remoteCategory = KnownCategories.REMOTE_CATEGORIES_OFFSET + TEST_CATEGORY;
+
         when(mMockSigninManager.isSignInAllowed()).thenReturn(true);
         when(mMockSigninManager.isSignedInOnNative()).thenReturn(false);
-        ArgumentCaptor<DestructionObserver> observers =
-                ArgumentCaptor.forClass(DestructionObserver.class);
-
-        doNothing().when(mUiDelegate).addDestructionObserver(observers.capture());
-
+        mSource.setRemoteSuggestionsEnabled(true);
+        resetUiDelegate();
         reloadNtp();
+
         assertTrue(isSignInPromoVisible());
 
-        // Note: As currently implemented, these two variables should point to the same object, a
+        // Note: As currently implemented, these variables should point to the same object, a
         // SignInPromo.SigninObserver
-        SignInStateObserver signInStateObserver = null;
-        SignInAllowedObserver signInAllowedObserver = null;
-        for (DestructionObserver observer : observers.getAllValues()) {
-            if (observer instanceof SignInStateObserver) {
-                signInStateObserver = (SignInStateObserver) observer;
-            }
-            if (observer instanceof SignInAllowedObserver) {
-                signInAllowedObserver = (SignInAllowedObserver) observer;
-            }
-        }
+        List<DestructionObserver> observers = getDestructionObserver(mUiDelegate);
+        SignInStateObserver signInStateObserver =
+                findFirstInstanceOf(observers, SignInStateObserver.class);
+        SignInAllowedObserver signInAllowedObserver =
+                findFirstInstanceOf(observers, SignInAllowedObserver.class);
+        SuggestionsSource.Observer suggestionsObserver =
+                findFirstInstanceOf(observers, SuggestionsSource.Observer.class);
 
         signInStateObserver.onSignedIn();
         assertFalse(isSignInPromoVisible());
@@ -928,6 +924,15 @@
         when(mMockSigninManager.isSignInAllowed()).thenReturn(true);
         signInAllowedObserver.onSignInAllowedChanged();
         assertTrue(isSignInPromoVisible());
+
+        mSource.setRemoteSuggestionsEnabled(false);
+        suggestionsObserver.onCategoryStatusChanged(
+                remoteCategory, CategoryStatus.CATEGORY_EXPLICITLY_DISABLED);
+        assertFalse(isSignInPromoVisible());
+
+        mSource.setRemoteSuggestionsEnabled(true);
+        suggestionsObserver.onCategoryStatusChanged(remoteCategory, CategoryStatus.AVAILABLE);
+        assertTrue(isSignInPromoVisible());
     }
 
     @Test
@@ -960,17 +965,8 @@
     @Test
     @Feature({"Ntp"})
     public void testAllDismissedVisibility() {
-        ArgumentCaptor<DestructionObserver> observers =
-                ArgumentCaptor.forClass(DestructionObserver.class);
-
-        verify(mUiDelegate, atLeastOnce()).addDestructionObserver(observers.capture());
-
-        SigninObserver signinObserver = null;
-        for (DestructionObserver observer : observers.getAllValues()) {
-            if (observer instanceof SigninObserver) {
-                signinObserver = (SigninObserver) observer;
-            }
-        }
+        SigninObserver signinObserver =
+                findFirstInstanceOf(getDestructionObserver(mUiDelegate), SigninObserver.class);
 
         @SuppressWarnings("unchecked")
         Callback<String> itemDismissedCallback = mock(Callback.class);
@@ -1045,8 +1041,28 @@
         assertEquals(RecyclerView.NO_POSITION,
                 mAdapter.getFirstPositionForType(ItemViewType.ALL_DISMISSED));
 
+        // Disabling remote suggestions should remove both the promo and the AllDismissed item
+        mSource.setRemoteSuggestionsEnabled(false);
+        signinObserver.onCategoryStatusChanged(
+                KnownCategories.REMOTE_CATEGORIES_OFFSET + TEST_CATEGORY,
+                CategoryStatus.CATEGORY_EXPLICITLY_DISABLED);
+        // Adapter content:
+        // Idx | Item
+        // ----|--------------------
+        // 0   | Above-the-fold
+        // 1   | Footer
+        // 2   | Spacer
+        assertEquals(ItemViewType.FOOTER, mAdapter.getItemViewType(1));
+        assertEquals(RecyclerView.NO_POSITION,
+                mAdapter.getFirstPositionForType(ItemViewType.ALL_DISMISSED));
+        assertEquals(
+                RecyclerView.NO_POSITION, mAdapter.getFirstPositionForType(ItemViewType.PROMO));
+
         // Prepare some suggestions. They should not load because the category is dismissed on
         // the current NTP.
+        mSource.setRemoteSuggestionsEnabled(true);
+        signinObserver.onCategoryStatusChanged(
+                KnownCategories.REMOTE_CATEGORIES_OFFSET + TEST_CATEGORY, CategoryStatus.AVAILABLE);
         mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE);
         mSource.setSuggestionsForCategory(TEST_CATEGORY, createDummySuggestions(1, TEST_CATEGORY));
         mSource.setInfoForCategory(TEST_CATEGORY, new CategoryInfoBuilder(TEST_CATEGORY).build());
@@ -1137,6 +1153,13 @@
         return new SectionDescriptor(Collections.<SnippetArticle>emptyList());
     }
 
+    private void resetUiDelegate() {
+        reset(mUiDelegate);
+        when(mUiDelegate.getSuggestionsSource()).thenReturn(mSource);
+        when(mUiDelegate.getEventReporter()).thenReturn(mock(SuggestionsEventReporter.class));
+        when(mUiDelegate.getSuggestionsRanker()).thenReturn(mock(SuggestionsRanker.class));
+    }
+
     private void reloadNtp() {
         mAdapter = new NewTabPageAdapter(mUiDelegate, mock(View.class), makeUiConfig(),
                 mOfflinePageBridge, mock(ContextMenuManager.class), /* tileGroupDelegate =
@@ -1151,4 +1174,25 @@
     private int getCategory(TreeNode item) {
         return ((SuggestionsSection) item).getCategory();
     }
+
+    /**
+     * Note: Currently the observers need to be re-registered to be returned again if this method
+     * has been called, as it relies on argument captors that don't repeatedly capture individual
+     * calls.
+     * @return The currently registered destruction observers.
+     */
+    private List<DestructionObserver> getDestructionObserver(SuggestionsUiDelegate delegate) {
+        ArgumentCaptor<DestructionObserver> observers =
+                ArgumentCaptor.forClass(DestructionObserver.class);
+        verify(delegate, atLeastOnce()).addDestructionObserver(observers.capture());
+        return observers.getAllValues();
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> T findFirstInstanceOf(Collection<?> collection, Class<T> clazz) {
+        for (Object item : collection) {
+            if (clazz.isAssignableFrom(item.getClass())) return (T) item;
+        }
+        return null;
+    }
 }
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 712beec2..a1a5aa3 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -8785,7 +8785,7 @@
       </message>
 
       <!--- Sync Confirmation section of the tab modal signin flow when sync is disabled by policy -->
-      <message name="IDS_SYNC_DISABLED_CONFIRMATION_CHROME_SYNC_TITLE" desc="Title of the chrome sync section of the sync confirmation dialog in the tab modal signin flow when sync is disabled by policy" formatter_data="android_java">
+      <message name="IDS_SYNC_DISABLED_CONFIRMATION_CHROME_SYNC_TITLE" desc="Title of the chrome sync section of the sync confirmation dialog in the tab modal signin flow when sync is disabled by policy">
         Sync is disabled by your administrator
       </message>
       <message name="IDS_SYNC_DISABLED_CONFIRMATION_DETAILS" desc="Body of the sync confirmation dialog in the tab modal signin flow when sync is disabled by policy">
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 102e80f..bd62b58 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1403,6 +1403,8 @@
     "usb/web_usb_histograms.h",
     "usb/web_usb_permission_provider.cc",
     "usb/web_usb_permission_provider.h",
+    "vr/vr_tab_helper.cc",
+    "vr/vr_tab_helper.h",
     "web_data_service_factory.cc",
     "web_data_service_factory.h",
     "webshare/share_target_pref_helper.cc",
@@ -3550,10 +3552,6 @@
 
   if (enable_vr) {
     if (is_android) {
-      sources += [
-        "android/vr_shell/vr_tab_helper.cc",
-        "android/vr_shell/vr_tab_helper.h",
-      ]
       deps += [ "android/vr_shell:vr_android" ]
       configs += [ "//third_party/gvr-android-sdk:libgvr_config" ]
     }
diff --git a/chrome/browser/android/download/download_controller.cc b/chrome/browser/android/download/download_controller.cc
index 2a27f7e..51fe451 100644
--- a/chrome/browser/android/download/download_controller.cc
+++ b/chrome/browser/android/download/download_controller.cc
@@ -22,6 +22,7 @@
 #include "chrome/browser/infobars/infobar_service.h"
 #include "chrome/browser/permissions/permission_update_infobar_delegate_android.h"
 #include "chrome/browser/ui/android/view_android_helper.h"
+#include "chrome/browser/vr/vr_tab_helper.h"
 #include "chrome/grit/chromium_strings.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
@@ -30,7 +31,6 @@
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_view_host.h"
 #include "content/public/common/referrer.h"
-#include "device/vr/features/features.h"
 #include "jni/DownloadController_jni.h"
 #include "net/base/filename_util.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
@@ -38,10 +38,6 @@
 #include "ui/android/window_android.h"
 #include "ui/base/page_transition_types.h"
 
-#if BUILDFLAG(ENABLE_VR)
-#include "chrome/browser/android/vr_shell/vr_tab_helper.h"
-#endif  // BUILDFLAG(ENABLE_VR)
-
 using base::android::ConvertUTF8ToJavaString;
 using base::android::JavaParamRef;
 using base::android::ScopedJavaLocalRef;
@@ -223,11 +219,9 @@
     const DownloadControllerBase::AcquireFileAccessPermissionCallback& cb) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-#if BUILDFLAG(ENABLE_VR)
   WebContents* web_contents = web_contents_getter.Run();
-  if (vr_shell::VrTabHelper::IsInVr(web_contents))
+  if (vr::VrTabHelper::IsInVr(web_contents))
     return;
-#endif
 
   if (HasFileAccessPermission()) {
     BrowserThread::PostTask(
diff --git a/chrome/browser/android/ntp/ntp_snippets_bridge.cc b/chrome/browser/android/ntp/ntp_snippets_bridge.cc
index f90fec1..27e4da5 100644
--- a/chrome/browser/android/ntp/ntp_snippets_bridge.cc
+++ b/chrome/browser/android/ntp/ntp_snippets_bridge.cc
@@ -163,18 +163,6 @@
   content_suggestions_service->SetRemoteSuggestionsEnabled(enabled);
 }
 
-static jboolean AreRemoteSuggestionsEnabled(
-    JNIEnv* env,
-    const JavaParamRef<jclass>& caller) {
-  ntp_snippets::ContentSuggestionsService* content_suggestions_service =
-      ContentSuggestionsServiceFactory::GetForProfile(
-          ProfileManager::GetLastUsedProfile());
-  if (!content_suggestions_service)
-    return false;
-
-  return content_suggestions_service->AreRemoteSuggestionsEnabled();
-}
-
 // Returns true if the remote provider is managed by an adminstrator's policy.
 static jboolean AreRemoteSuggestionsManaged(
     JNIEnv* env,
@@ -288,6 +276,12 @@
       content_suggestions_service_->GetSuggestionsForCategory(category));
 }
 
+jboolean NTPSnippetsBridge::AreRemoteSuggestionsEnabled(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& obj) {
+  return content_suggestions_service_->AreRemoteSuggestionsEnabled();
+}
+
 void NTPSnippetsBridge::FetchSuggestionImage(
     JNIEnv* env,
     const JavaParamRef<jobject>& obj,
diff --git a/chrome/browser/android/ntp/ntp_snippets_bridge.h b/chrome/browser/android/ntp/ntp_snippets_bridge.h
index 0860da0..396ac80 100644
--- a/chrome/browser/android/ntp/ntp_snippets_bridge.h
+++ b/chrome/browser/android/ntp/ntp_snippets_bridge.h
@@ -53,6 +53,10 @@
       const base::android::JavaParamRef<jobject>& obj,
       jint j_category_id);
 
+  jboolean AreRemoteSuggestionsEnabled(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj);
+
   void FetchSuggestionImage(
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj,
diff --git a/chrome/browser/android/tab_web_contents_delegate_android.cc b/chrome/browser/android/tab_web_contents_delegate_android.cc
index fc06a47..714998d 100644
--- a/chrome/browser/android/tab_web_contents_delegate_android.cc
+++ b/chrome/browser/android/tab_web_contents_delegate_android.cc
@@ -13,12 +13,6 @@
 #include "chrome/browser/android/banners/app_banner_manager_android.h"
 #include "chrome/browser/android/feature_utilities.h"
 #include "chrome/browser/android/hung_renderer_infobar_delegate.h"
-
-#include "device/vr/features/features.h"
-#if BUILDFLAG(ENABLE_VR)
-#include "chrome/browser/android/vr_shell/vr_tab_helper.h"
-#endif  // BUILDFLAG(ENABLE_VR)
-
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/file_select_helper.h"
 #include "chrome/browser/infobars/infobar_service.h"
@@ -33,6 +27,7 @@
 #include "chrome/browser/ui/find_bar/find_notification_details.h"
 #include "chrome/browser/ui/find_bar/find_tab_helper.h"
 #include "chrome/browser/ui/tab_helpers.h"
+#include "chrome/browser/vr/vr_tab_helper.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/url_constants.h"
 #include "components/app_modal/javascript_dialog_manager.h"
@@ -126,11 +121,9 @@
 void TabWebContentsDelegateAndroid::RunFileChooser(
     content::RenderFrameHost* render_frame_host,
     const FileChooserParams& params) {
-#if BUILDFLAG(ENABLE_VR)
-  if (vr_shell::VrTabHelper::IsInVr(
+  if (vr::VrTabHelper::IsInVr(
           WebContents::FromRenderFrameHost(render_frame_host)))
     return;
-#endif
   FileSelectHelper::RunFileChooser(render_frame_host, params);
 }
 
@@ -138,10 +131,8 @@
 TabWebContentsDelegateAndroid::RunBluetoothChooser(
     content::RenderFrameHost* frame,
     const BluetoothChooser::EventHandler& event_handler) {
-#if BUILDFLAG(ENABLE_VR)
-  if (vr_shell::VrTabHelper::IsInVr(WebContents::FromRenderFrameHost(frame)))
+  if (vr::VrTabHelper::IsInVr(WebContents::FromRenderFrameHost(frame)))
     return nullptr;
-#endif
   return base::MakeUnique<BluetoothChooserAndroid>(frame, event_handler);
 }
 
@@ -271,11 +262,9 @@
 content::JavaScriptDialogManager*
 TabWebContentsDelegateAndroid::GetJavaScriptDialogManager(
     WebContents* source) {
-#if BUILDFLAG(ENABLE_VR)
-  if (vr_shell::VrTabHelper::IsInVr(source)) {
+  if (vr::VrTabHelper::IsInVr(source)) {
     return nullptr;
   }
-#endif
   return app_modal::JavaScriptDialogManager::GetInstance();
 }
 
@@ -283,13 +272,11 @@
     content::WebContents* web_contents,
     const content::MediaStreamRequest& request,
     const content::MediaResponseCallback& callback) {
-#if BUILDFLAG(ENABLE_VR)
-  if (vr_shell::VrTabHelper::IsInVr(web_contents)) {
+  if (vr::VrTabHelper::IsInVr(web_contents)) {
     callback.Run(content::MediaStreamDevices(),
                  content::MEDIA_DEVICE_NOT_SUPPORTED, nullptr);
     return;
   }
-#endif
   MediaCaptureDevicesDispatcher::GetInstance()->ProcessMediaAccessRequest(
       web_contents, request, callback, nullptr);
 }
diff --git a/chrome/browser/android/vr_shell/vr_shell.cc b/chrome/browser/android/vr_shell/vr_shell.cc
index fd602959..478c2ed 100644
--- a/chrome/browser/android/vr_shell/vr_shell.cc
+++ b/chrome/browser/android/vr_shell/vr_shell.cc
@@ -27,7 +27,6 @@
 #include "chrome/browser/android/vr_shell/vr_input_manager.h"
 #include "chrome/browser/android/vr_shell/vr_shell_delegate.h"
 #include "chrome/browser/android/vr_shell/vr_shell_gl.h"
-#include "chrome/browser/android/vr_shell/vr_tab_helper.h"
 #include "chrome/browser/android/vr_shell/vr_usage_monitor.h"
 #include "chrome/browser/android/vr_shell/vr_web_contents_observer.h"
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
@@ -35,6 +34,7 @@
 #include "chrome/browser/vr/toolbar_helper.h"
 #include "chrome/browser/vr/ui_interface.h"
 #include "chrome/browser/vr/ui_scene_manager.h"
+#include "chrome/browser/vr/vr_tab_helper.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/navigation_controller.h"
@@ -84,7 +84,7 @@
     // VrTabHelper for details).
     contents->GetRenderWidgetHostView()->SetIsInVR(is_in_vr);
 
-    VrTabHelper* vr_tab_helper = VrTabHelper::FromWebContents(contents);
+    vr::VrTabHelper* vr_tab_helper = vr::VrTabHelper::FromWebContents(contents);
     DCHECK(vr_tab_helper);
     vr_tab_helper->SetIsInVr(is_in_vr);
   }
diff --git a/chrome/browser/android/vr_shell/vr_shell_gl.cc b/chrome/browser/android/vr_shell/vr_shell_gl.cc
index 7cca0b4..ed9b708d 100644
--- a/chrome/browser/android/vr_shell/vr_shell_gl.cc
+++ b/chrome/browser/android/vr_shell/vr_shell_gl.cc
@@ -636,6 +636,12 @@
 }
 
 void VrShellGl::OnContentMove(const gfx::PointF& normalized_hit_point) {
+  // TODO(mthiesse, vollick): Content is currently way too sensitive to mouse
+  // moves for how noisy the controller is. It's almost impossible to click a
+  // link without unintentionally starting a drag event. For this reason we
+  // disable mouse moves, only delivering a down and up event.
+  if (controller_->ButtonState(gvr::kControllerButtonClick))
+    return;
   SendGestureToContent(
       MakeMouseEvent(blink::WebInputEvent::kMouseMove, normalized_hit_point));
 }
diff --git a/chrome/browser/app_controller_mac.mm b/chrome/browser/app_controller_mac.mm
index 5563c919..1dc878b9 100644
--- a/chrome/browser/app_controller_mac.mm
+++ b/chrome/browser/app_controller_mac.mm
@@ -209,10 +209,6 @@
 // Opens a tab for each GURL in |urls|.
 - (void)openUrls:(const std::vector<GURL>&)urls;
 
-// Creates StartupBrowserCreator and opens |urls| with it.
-- (void)openUrlsViaStartupBrowserCreator:(const std::vector<GURL>&)urls
-                               inBrowser:(Browser*)browser;
-
 // This class cannot open urls until startup has finished. The urls that cannot
 // be opened are cached in |startupUrls_|. This method must be called exactly
 // once after startup has completed. It opens the urls in |startupUrls_|, and
@@ -1279,16 +1275,6 @@
          prefs->GetBoolean(prefs::kBrowserGuestModeEnabled);
 }
 
-- (void)openUrlsViaStartupBrowserCreator:(const std::vector<GURL>&)urls
-                               inBrowser:(Browser*)browser {
-  base::CommandLine dummy(base::CommandLine::NO_PROGRAM);
-  chrome::startup::IsFirstRun first_run =
-      first_run::IsChromeFirstRun() ? chrome::startup::IS_FIRST_RUN
-                                    : chrome::startup::IS_NOT_FIRST_RUN;
-  StartupBrowserCreatorImpl launch(base::FilePath(), dummy, first_run);
-  launch.OpenURLsInBrowser(browser, false, urls);
-}
-
 // Various methods to open URLs that we get in a native fashion. We use
 // StartupBrowserCreator here because on the other platforms, URLs to open come
 // through the ProcessSingleton, and it calls StartupBrowserCreator. It's best
@@ -1299,36 +1285,20 @@
     return;
   }
 
-  Profile* profile = [self safeLastProfileForNewWindows];
-  SessionStartupPref pref = StartupBrowserCreator::GetSessionStartupPref(
-      *base::CommandLine::ForCurrentProcess(), profile);
-
-  if (pref.type == SessionStartupPref::LAST) {
-    if (SessionRestore::IsRestoring(profile)) {
-      // In case of session restore, remember |urls|, so they will be opened
-      // after session is resored, in the tabs next to it.
-      SessionRestore::AddURLsToOpen(profile, urls);
-    } else {
-      Browser* browser = chrome::GetLastActiveBrowser();
-      if (!browser) {
-        // This behavior is needed for the case when chromium app is launched,
-        // but there are no open indows.
-        browser = new Browser(Browser::CreateParams(profile, true));
-        browser->window()->Show();
-        SessionRestore::AddURLsToOpen(profile, urls);
-      } else {
-        [self openUrlsViaStartupBrowserCreator:urls inBrowser:browser];
-      }
-    }
-  } else {
-    Browser* browser = chrome::GetLastActiveBrowser();
-    // If no browser window exists then create one with no tabs to be filled in.
-    if (!browser) {
-      browser = new Browser(Browser::CreateParams(profile, true));
-      browser->window()->Show();
-    }
-    [self openUrlsViaStartupBrowserCreator:urls inBrowser:browser];
+  Browser* browser = chrome::GetLastActiveBrowser();
+  // if no browser window exists then create one with no tabs to be filled in
+  if (!browser) {
+    browser = new Browser(
+        Browser::CreateParams([self safeLastProfileForNewWindows], true));
+    browser->window()->Show();
   }
+
+  base::CommandLine dummy(base::CommandLine::NO_PROGRAM);
+  chrome::startup::IsFirstRun first_run =
+      first_run::IsChromeFirstRun() ? chrome::startup::IS_FIRST_RUN
+                                    : chrome::startup::IS_NOT_FIRST_RUN;
+  StartupBrowserCreatorImpl launch(base::FilePath(), dummy, first_run);
+  launch.OpenURLsInBrowser(browser, false, urls);
 }
 
 - (void)getUrl:(NSAppleEventDescriptor*)event
diff --git a/chrome/browser/app_controller_mac_browsertest.mm b/chrome/browser/app_controller_mac_browsertest.mm
index 922ed687..63324c0 100644
--- a/chrome/browser/app_controller_mac_browsertest.mm
+++ b/chrome/browser/app_controller_mac_browsertest.mm
@@ -23,9 +23,7 @@
 #include "chrome/browser/apps/app_browsertest_util.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/history/history_service_factory.h"
-#include "chrome/browser/prefs/session_startup_pref.h"
 #include "chrome/browser/profiles/profile_attributes_entry.h"
 #include "chrome/browser/profiles/profile_attributes_storage.h"
 #include "chrome/browser/profiles/profile_manager.h"
@@ -48,10 +46,8 @@
 #include "components/bookmarks/test/bookmark_test_helpers.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/navigation_controller.h"
-#include "content/public/browser/notification_service.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test_utils.h"
-#include "content/public/test/repeated_notification_observer.h"
 #include "content/public/test/test_navigation_observer.h"
 #include "extensions/browser/app_window/app_window_registry.h"
 #include "extensions/common/extension.h"
@@ -60,7 +56,7 @@
 
 namespace {
 
-GURL g_open_shortcut_url = GURL("https://localhost");
+GURL g_open_shortcut_url = GURL::EmptyGURL();
 
 // Returns an Apple Event that instructs the application to open |url|.
 NSAppleEventDescriptor* AppleEventToOpenUrl(const GURL& url) {
@@ -107,6 +103,9 @@
 @implementation TestOpenShortcutOnStartup
 
 - (void)applicationWillFinishLaunching:(NSNotification*)notification {
+  if (!g_open_shortcut_url.is_valid())
+    return;
+
   SendAppleEventToOpenUrlToAppController(g_open_shortcut_url);
 }
 
@@ -381,6 +380,9 @@
     ASSERT_TRUE(destination != NULL);
 
     method_exchangeImplementations(original, destination);
+
+    ASSERT_TRUE(embedded_test_server()->Start());
+    g_open_shortcut_url = embedded_test_server()->GetURL("/simple.html");
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
@@ -399,203 +401,6 @@
           ->GetLastCommittedURL());
 }
 
-const char kSessionURL[] = "https://example.com/session.html";
-const char kPresetURL[] = "https://example.com/preset.html";
-const char kUrlToOpen[] = "https://example.com/shortcut.html";
-
-class AppControllerOpenShortcutOnStartupBrowserTest
-    : public AppControllerOpenShortcutBrowserTest,
-      public testing::WithParamInterface<SessionStartupPref::Type> {
- public:
-  AppControllerOpenShortcutOnStartupBrowserTest()
-      : session_startup_pref_(GetParam()) {}
-  virtual ~AppControllerOpenShortcutOnStartupBrowserTest() {}
-
-  void SetUpInProcessBrowserTestFixture() override {
-    // Don't open URL via AppControllerOpenShortcutBrowserTest in PRE test,
-    // PRE test should only prepare session-to-restore.
-    if (!base::StartsWith(
-            testing::UnitTest::GetInstance()->current_test_info()->name(),
-            "PRE_", base::CompareCase::SENSITIVE)) {
-      AppControllerOpenShortcutBrowserTest::SetUpInProcessBrowserTestFixture();
-    }
-  }
-
-  void SetUpOnMainThread() override {
-    ASSERT_TRUE(embedded_test_server()->Start());
-    SessionStartupPref pref(GetParam());
-    pref.urls.push_back(GURL(kPresetURL));
-    SessionStartupPref::SetStartupPref(browser()->profile(), pref);
-  }
-
- protected:
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    set_open_about_blank_on_browser_launch(false);
-    // Skip AppControllerOpenShortcutBrowserTest::SetUpCommandLine, which adds
-    // startup URL.
-  }
-
-  SessionStartupPref::Type GetSessionStartupPref() {
-    return session_startup_pref_;
-  }
-
- private:
-  SessionStartupPref::Type session_startup_pref_;
-
-  DISALLOW_COPY_AND_ASSIGN(AppControllerOpenShortcutOnStartupBrowserTest);
-};
-
-IN_PROC_BROWSER_TEST_P(AppControllerOpenShortcutOnStartupBrowserTest,
-                       PRE_OpenURL) {
-  // Prepare a session to restore in main test body.
-  ui_test_utils::NavigateToURL(browser(), GURL(kSessionURL));
-  ASSERT_EQ(1, browser()->tab_strip_model()->count());
-}
-
-// This test checks that when AppleEvent on opening URL is received during
-// browser starup, that URL is opened and located properly alongside restored
-// session or startup URLs.
-// Emulates opening local file or URL from other application when chromium
-// application is not started.
-IN_PROC_BROWSER_TEST_P(AppControllerOpenShortcutOnStartupBrowserTest, OpenURL) {
-  TabStripModel* tab_strip = browser()->tab_strip_model();
-  if (GetSessionStartupPref() == SessionStartupPref::DEFAULT) {
-    // Open NTP startup setting - tab opened via shortcut should replace NTP.
-    EXPECT_EQ(1, tab_strip->count());
-  } else if (GetSessionStartupPref() == SessionStartupPref::LAST) {
-    // Restore sesion on startup - expect tab of that session.
-    EXPECT_EQ(2, tab_strip->count());
-    EXPECT_EQ(kSessionURL, tab_strip->GetWebContentsAt(0)->GetURL().spec());
-  } else if (GetSessionStartupPref() == SessionStartupPref::URLS) {
-    // Open specific set of pages startup setting - expect that set of pages.
-    EXPECT_EQ(2, tab_strip->count());
-    EXPECT_EQ(kPresetURL, tab_strip->GetWebContentsAt(0)->GetURL().spec());
-  } else {
-    NOTREACHED() << "Unknown session startup pref, not covered by test.";
-  }
-  // Tab opened via shortcut should be the last tab and shold be active.
-  EXPECT_EQ(
-      g_open_shortcut_url.spec(),
-      tab_strip->GetWebContentsAt(tab_strip->count() - 1)->GetURL().spec());
-  EXPECT_EQ(tab_strip->count() - 1, tab_strip->active_index());
-}
-
-INSTANTIATE_TEST_CASE_P(AppControllerOpenShortcutOnStartupPrefsAny,
-                        AppControllerOpenShortcutOnStartupBrowserTest,
-                        testing::Values(SessionStartupPref::DEFAULT,
-                                        SessionStartupPref::LAST,
-                                        SessionStartupPref::URLS));
-
-class AppControllerOpenShortcutInBrowserTest
-    : public InProcessBrowserTest,
-      public testing::WithParamInterface<SessionStartupPref::Type> {
- public:
-  AppControllerOpenShortcutInBrowserTest()
-      : session_startup_pref_(GetParam()) {}
-
- protected:
-  SessionStartupPref::Type GetSessionStartupPref() {
-    return session_startup_pref_;
-  }
-
-  void SetUpOnMainThread() override {
-    SessionStartupPref pref(session_startup_pref_);
-    pref.urls.push_back(GURL(kPresetURL));
-    SessionStartupPref::SetStartupPref(browser()->profile(), pref);
-  }
-
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    set_open_about_blank_on_browser_launch(false);
-  }
-
- private:
-  SessionStartupPref::Type session_startup_pref_;
-
-  DISALLOW_COPY_AND_ASSIGN(AppControllerOpenShortcutInBrowserTest);
-};
-
-IN_PROC_BROWSER_TEST_P(AppControllerOpenShortcutInBrowserTest,
-                       PRE_OpenShortcutInBrowserWithWindow) {
-  // Prepare a session to restore in main test body.
-  ui_test_utils::NavigateToURL(browser(), GURL(kSessionURL));
-  ASSERT_EQ(1, browser()->tab_strip_model()->count());
-}
-
-// This test checks that when AppleEvent on opening URL is received by already
-// started browser with window, that URL opens in a tab next to existing tabs.
-// This needs to be TEST_P (with SessionStartupPref::Type) parameter to
-// ensure that opening URL logic will not by mistake restore sesiion or open
-// startup URLs.
-IN_PROC_BROWSER_TEST_P(AppControllerOpenShortcutInBrowserTest,
-                       OpenShortcutInBrowserWithWindow) {
-  ui_test_utils::NavigateToURL(browser(), GURL(kSessionURL));
-  ASSERT_EQ(1, browser()->tab_strip_model()->count());
-  SendAppleEventToOpenUrlToAppController(GURL(kUrlToOpen));
-  content::TestNavigationObserver event_navigation_observer(
-      browser()->tab_strip_model()->GetActiveWebContents());
-  event_navigation_observer.Wait();
-
-  ASSERT_EQ(2, browser()->tab_strip_model()->count());
-  TabStripModel* tab_strip = browser()->tab_strip_model();
-  EXPECT_EQ(kUrlToOpen, tab_strip->GetWebContentsAt(1)->GetURL().spec());
-  EXPECT_EQ(1, tab_strip->active_index());
-}
-
-IN_PROC_BROWSER_TEST_P(AppControllerOpenShortcutInBrowserTest,
-                       PRE_OpenShortcutInBrowserWithoutWindow) {
-  // Prepare a session to restore in main test body.
-  ui_test_utils::NavigateToURL(browser(), GURL(kSessionURL));
-  ASSERT_EQ(1, browser()->tab_strip_model()->count());
-}
-
-// This test checks that when AppleEvent on opening URL is received by
-// chromium application withot windows, that URL is opened, alongside with
-// restored session if corresponding setting is enabled.
-IN_PROC_BROWSER_TEST_P(AppControllerOpenShortcutInBrowserTest,
-                       OpenShortcutInBrowserWithoutWindow) {
-  ui_test_utils::NavigateToURL(browser(), GURL(kSessionURL));
-  content::RepeatedNotificationObserver close_observer(
-      chrome::NOTIFICATION_BROWSER_CLOSED, 1);
-  browser()->window()->Close();
-  close_observer.Wait();
-
-  content::RepeatedNotificationObserver open_observer(
-      chrome::NOTIFICATION_TAB_ADDED, 1);
-  SendAppleEventToOpenUrlToAppController(GURL(kUrlToOpen));
-
-  open_observer.Wait();
-
-  Browser* browser = BrowserList::GetInstance()->GetLastActive();
-  TabStripModel* tab_strip = browser->tab_strip_model();
-
-  if (GetSessionStartupPref() == SessionStartupPref::DEFAULT) {
-    // Open NTP startup setting - tab opened via shortcut should replace NTP.
-    ASSERT_EQ(1, tab_strip->count());
-  } else if (GetSessionStartupPref() == SessionStartupPref::LAST) {
-    // Restore sesion on startup - expect tab of that session.
-    ASSERT_EQ(2, tab_strip->count());
-    EXPECT_EQ(kSessionURL, tab_strip->GetWebContentsAt(0)->GetURL().spec());
-  } else if (GetSessionStartupPref() == SessionStartupPref::URLS) {
-    // Open specific set of pages startup setting - when Chromium application
-    // is already started, do NOT expect these pages to be opened.
-    ASSERT_EQ(1, tab_strip->count());
-  } else {
-    NOTREACHED() << "Unknown session startup pref, not covered by test.";
-  }
-
-  // Tab opened via shortcut should be the last tab and shold be active.
-  EXPECT_EQ(
-      kUrlToOpen,
-      tab_strip->GetWebContentsAt(tab_strip->count() - 1)->GetURL().spec());
-  EXPECT_EQ(tab_strip->count() - 1, tab_strip->active_index());
-}
-
-INSTANTIATE_TEST_CASE_P(AppControllerOpenShortcutInBrowserPrefsAny,
-                        AppControllerOpenShortcutInBrowserTest,
-                        testing::Values(SessionStartupPref::DEFAULT,
-                                        SessionStartupPref::LAST,
-                                        SessionStartupPref::URLS));
-
 class AppControllerReplaceNTPBrowserTest : public InProcessBrowserTest {
  protected:
   AppControllerReplaceNTPBrowserTest() {}
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
index 0cdfbea..38d453ed 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
@@ -9,9 +9,12 @@
 #include <set>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "base/callback.h"
 #include "base/metrics/user_metrics.h"
+#include "base/task_scheduler/post_task.h"
+#include "build/build_config.h"
 #include "chrome/browser/autofill/personal_data_manager_factory.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/browser_process.h"
@@ -87,10 +90,9 @@
 #if defined(OS_ANDROID)
 #include "chrome/browser/android/webapps/webapp_registry.h"
 #include "chrome/browser/offline_pages/offline_page_model_factory.h"
-#include "chrome/browser/precache/precache_manager_factory.h"
 #include "components/offline_pages/core/offline_page_feature.h"
 #include "components/offline_pages/core/offline_page_model.h"
-#include "components/precache/content/precache_manager.h"
+#include "sql/connection.h"
 #endif
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
@@ -254,6 +256,16 @@
     service->RemoveBrowsingData(data_type_mask, origin_filter);
 }
 
+#if defined(OS_ANDROID)
+void ClearPrecacheInBackground(content::BrowserContext* browser_context) {
+  // Precache code was removed in M61 but the sqlite database file could be
+  // still here.
+  base::FilePath db_path(browser_context->GetPath().Append(
+      base::FilePath(FILE_PATH_LITERAL("PrecacheDatabase"))));
+  sql::Connection::Delete(db_path);
+}
+#endif
+
 // Returned by ChromeBrowsingDataRemoverDelegate::GetOriginTypeMatcher().
 bool DoesOriginMatchEmbedderMask(int origin_type_mask,
                                  const GURL& origin,
@@ -616,18 +628,11 @@
 #endif
 
 #if defined(OS_ANDROID)
-    precache::PrecacheManager* precache_manager =
-        precache::PrecacheManagerFactory::GetForBrowserContext(profile_);
-    // |precache_manager| could be nullptr if the profile is off the record.
-    if (!precache_manager) {
-      clear_precache_history_.Start();
-      precache_manager->ClearHistory();
-      // The above calls are done on the UI thread but do their work on the DB
-      // thread. So wait for it.
-      BrowserThread::PostTaskAndReply(
-          BrowserThread::DB, FROM_HERE, base::Bind(&base::DoNothing),
-          clear_precache_history_.GetCompletionCallback());
-    }
+    clear_precache_history_.Start();
+    base::PostTaskWithTraitsAndReply(
+        FROM_HERE, {base::TaskPriority::USER_VISIBLE, base::MayBlock()},
+        base::BindOnce(&ClearPrecacheInBackground, profile_),
+        clear_precache_history_.GetCompletionCallback());
 
     // Clear the history information (last launch time and origin URL) of any
     // registered webapps.
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_factory.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_factory.cc
index b814ae2..e696dac4 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_factory.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_factory.cc
@@ -25,7 +25,6 @@
 
 #if defined(OS_ANDROID)
 #include "chrome/browser/offline_pages/offline_page_model_factory.h"
-#include "chrome/browser/precache/precache_manager_factory.h"
 #endif
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
@@ -69,7 +68,6 @@
 
 #if defined(OS_ANDROID)
   DependsOn(offline_pages::OfflinePageModelFactory::GetInstance());
-  DependsOn(precache::PrecacheManagerFactory::GetInstance());
 #endif
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 824183f..91cace2 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -3082,8 +3082,9 @@
 void ChromeContentBrowserClient::RegisterOutOfProcessServices(
       OutOfProcessServiceMap* services) {
 #if BUILDFLAG(ENABLE_PRINTING)
-  services->emplace(printing::mojom::kServiceName,
-                    base::ASCIIToUTF16("PDF Compositor Service"));
+  (*services)[printing::mojom::kServiceName] = {
+      base::ASCIIToUTF16("PDF Compositor Service"),
+      content::SANDBOX_TYPE_UTILITY};
 #endif
 }
 
diff --git a/chrome/browser/chrome_service_worker_browsertest.cc b/chrome/browser/chrome_service_worker_browsertest.cc
index c79f5a6d..c32c0c7 100644
--- a/chrome/browser/chrome_service_worker_browsertest.cc
+++ b/chrome/browser/chrome_service_worker_browsertest.cc
@@ -274,7 +274,6 @@
               "  issuedRequests.forEach(function(data) {"
               "      str += data + '\\n';"
               "    });"
-              "  window.domAutomationController.setAutomationId(0);"
               "  window.domAutomationController.send(str);"
               "}"
               "navigator.serviceWorker.addEventListener("
diff --git a/chrome/browser/chromeos/login/users/default_user_image/default_user_images.cc b/chrome/browser/chromeos/login/users/default_user_image/default_user_images.cc
index 41db15e..84b9b7c 100644
--- a/chrome/browser/chromeos/login/users/default_user_image/default_user_images.cc
+++ b/chrome/browser/chromeos/login/users/default_user_image/default_user_images.cc
@@ -6,11 +6,13 @@
 
 #include "base/logging.h"
 #include "base/macros.h"
+#include "base/memory/ptr_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/sys_info.h"
+#include "base/values.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/chromeos/resources/grit/ui_chromeos_resources.h"
@@ -261,5 +263,24 @@
   return index + 6;
 }
 
+std::unique_ptr<base::ListValue> GetAsDictionary() {
+  auto image_urls = base::MakeUnique<base::ListValue>();
+  for (int i = default_user_image::kFirstDefaultImageIndex;
+       i < default_user_image::kDefaultImagesCount; ++i) {
+    auto image_data = base::MakeUnique<base::DictionaryValue>();
+    image_data->SetString("url", default_user_image::GetDefaultImageUrl(i));
+    image_data->SetString("author",
+                          l10n_util::GetStringUTF16(
+                              default_user_image::kDefaultImageAuthorIDs[i]));
+    image_data->SetString("website",
+                          l10n_util::GetStringUTF16(
+                              default_user_image::kDefaultImageWebsiteIDs[i]));
+    image_data->SetString("title",
+                          default_user_image::GetDefaultImageDescription(i));
+    image_urls->Append(std::move(image_data));
+  }
+  return image_urls;
+}
+
 }  // namespace default_user_image
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/users/default_user_image/default_user_images.h b/chrome/browser/chromeos/login/users/default_user_image/default_user_images.h
index 517d7224..d0cf3f5 100644
--- a/chrome/browser/chromeos/login/users/default_user_image/default_user_images.h
+++ b/chrome/browser/chromeos/login/users/default_user_image/default_user_images.h
@@ -7,11 +7,16 @@
 
 #include <stddef.h>
 
+#include <memory>
 #include <string>
 
 #include "base/strings/string16.h"
 #include "chromeos/chromeos_export.h"
 
+namespace base {
+class ListValue;
+}
+
 namespace gfx {
 class ImageSkia;
 }
@@ -74,6 +79,10 @@
 // Returns the histogram value corresponding to the given default image index.
 CHROMEOS_EXPORT int GetDefaultImageHistogramValue(int index);
 
+// Returns a list of dictionary values with url, author, website, and title
+// properties set for each default user image.
+CHROMEOS_EXPORT std::unique_ptr<base::ListValue> GetAsDictionary();
+
 }  // namespace default_user_image
 }  // namespace chromeos
 
diff --git a/chrome/browser/extensions/api/developer_private/show_permissions_dialog_helper.cc b/chrome/browser/extensions/api/developer_private/show_permissions_dialog_helper.cc
index 8d04694..e2b460ab 100644
--- a/chrome/browser/extensions/api/developer_private/show_permissions_dialog_helper.cc
+++ b/chrome/browser/extensions/api/developer_private/show_permissions_dialog_helper.cc
@@ -47,8 +47,7 @@
                                 AppInfoLaunchSource::NUM_LAUNCH_SOURCES);
     }
 
-    ShowAppInfoInNativeDialog(web_contents, GetAppInfoNativeDialogSize(),
-                              profile, extension, on_complete);
+    ShowAppInfoInNativeDialog(web_contents, profile, extension, on_complete);
 
     return;  // All done.
   }
diff --git a/chrome/browser/importer/external_process_importer_client.cc b/chrome/browser/importer/external_process_importer_client.cc
index a0ff28f0..0bca7e1 100644
--- a/chrome/browser/importer/external_process_importer_client.cc
+++ b/chrome/browser/importer/external_process_importer_client.cc
@@ -294,7 +294,7 @@
       this, BrowserThread::GetTaskRunnerForThread(thread_id).get());
   utility_process_host->SetName(
       l10n_util::GetStringUTF16(IDS_UTILITY_PROCESS_PROFILE_IMPORTER_NAME));
-  utility_process_host->DisableSandbox();
+  utility_process_host->SetSandboxType(content::SANDBOX_TYPE_NO_SANDBOX);
 
 #if defined(OS_MACOSX)
   base::EnvironmentMap env;
diff --git a/chrome/browser/lifetime/browser_close_manager_browsertest.cc b/chrome/browser/lifetime/browser_close_manager_browsertest.cc
index 1897702a..40717139 100644
--- a/chrome/browser/lifetime/browser_close_manager_browsertest.cc
+++ b/chrome/browser/lifetime/browser_close_manager_browsertest.cc
@@ -52,7 +52,6 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/download_test_observer.h"
-#include "content/public/test/repeated_notification_observer.h"
 #include "content/public/test/test_navigation_observer.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/url_request/url_request_mock_http_job.h"
@@ -83,6 +82,41 @@
   GetNextDialog()->CancelAppModalDialog();
 }
 
+class RepeatedNotificationObserver : public content::NotificationObserver {
+ public:
+  explicit RepeatedNotificationObserver(int type, int count)
+      : num_outstanding_(count), running_(false) {
+    registrar_.Add(this, type, content::NotificationService::AllSources());
+  }
+
+  void Observe(int type,
+               const content::NotificationSource& source,
+               const content::NotificationDetails& details) override {
+    ASSERT_GT(num_outstanding_, 0);
+    if (!--num_outstanding_ && running_) {
+      content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
+                                       run_loop_.QuitClosure());
+    }
+  }
+
+  void Wait() {
+    if (num_outstanding_ <= 0)
+      return;
+
+    running_ = true;
+    run_loop_.Run();
+    running_ = false;
+  }
+
+ private:
+  int num_outstanding_;
+  content::NotificationRegistrar registrar_;
+  bool running_;
+  base::RunLoop run_loop_;
+
+  DISALLOW_COPY_AND_ASSIGN(RepeatedNotificationObserver);
+};
+
 class TabRestoreServiceChangesObserver
     : public sessions::TabRestoreServiceObserver {
  public:
@@ -271,7 +305,7 @@
       browser(), embedded_test_server()->GetURL("/beforeunload.html")));
   PrepareForDialog(browser());
 
-  content::RepeatedNotificationObserver cancel_observer(
+  RepeatedNotificationObserver cancel_observer(
       chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, 1);
   chrome::CloseAllBrowsersAndQuit();
   ASSERT_NO_FATAL_FAILURE(CancelClose());
@@ -279,7 +313,7 @@
   EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
   EXPECT_EQ(1, browser()->tab_strip_model()->count());
 
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, 1);
   chrome::CloseAllBrowsersAndQuit();
   ASSERT_NO_FATAL_FAILURE(AcceptClose());
@@ -295,7 +329,7 @@
       browser(), embedded_test_server()->GetURL("/beforeunload.html")));
   PrepareForDialog(browser());
 
-  content::RepeatedNotificationObserver cancel_observer(
+  RepeatedNotificationObserver cancel_observer(
       chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, 1);
   chrome::CloseAllBrowsersAndQuit();
   chrome::CloseAllBrowsersAndQuit();
@@ -304,7 +338,7 @@
   EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
   EXPECT_EQ(1, browser()->tab_strip_model()->count());
 
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, 1);
   chrome::CloseAllBrowsersAndQuit();
   chrome::CloseAllBrowsersAndQuit();
@@ -324,7 +358,7 @@
       ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUIAboutURL)));
   PrepareForDialog(browser());
 
-  content::RepeatedNotificationObserver cancel_observer(
+  RepeatedNotificationObserver cancel_observer(
       chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, 1);
   chrome::CloseAllBrowsersAndQuit();
   ASSERT_NO_FATAL_FAILURE(CancelClose());
@@ -341,7 +375,7 @@
   ASSERT_NO_FATAL_FAILURE(AcceptClose());
   navigation_observer.Wait();
 
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, 1);
   chrome::CloseAllBrowsersAndQuit();
   close_observer.Wait();
@@ -381,7 +415,7 @@
 
   // Cancel shutdown on the first beforeunload event.
   {
-    content::RepeatedNotificationObserver cancel_observer(
+    RepeatedNotificationObserver cancel_observer(
         chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, 1);
     chrome::CloseAllBrowsersAndQuit();
     ASSERT_NO_FATAL_FAILURE(CancelClose());
@@ -393,7 +427,7 @@
 
   // Cancel shutdown on the second beforeunload event.
   {
-    content::RepeatedNotificationObserver cancel_observer(
+    RepeatedNotificationObserver cancel_observer(
         chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, 2);
     chrome::CloseAllBrowsersAndQuit();
     ASSERT_NO_FATAL_FAILURE(AcceptClose());
@@ -405,7 +439,7 @@
   EXPECT_EQ(1, browsers_[1]->tab_strip_model()->count());
 
   // Allow shutdown for both beforeunload events.
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, 2);
   chrome::CloseAllBrowsersAndQuit();
   ASSERT_NO_FATAL_FAILURE(AcceptClose());
@@ -433,7 +467,7 @@
   // the dialog is guaranteed to show.
   PrepareForDialog(browsers_[0]->tab_strip_model()->GetWebContentsAt(1));
 
-  content::RepeatedNotificationObserver cancel_observer(
+  RepeatedNotificationObserver cancel_observer(
       chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, 1);
   chrome::CloseAllBrowsersAndQuit();
   ASSERT_NO_FATAL_FAILURE(CancelClose());
@@ -442,7 +476,7 @@
   // All tabs should still be open.
   EXPECT_EQ(3, browsers_[0]->tab_strip_model()->count());
 
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, 1);
   chrome::CloseAllBrowsersAndQuit();
   ASSERT_NO_FATAL_FAILURE(AcceptClose());
@@ -469,7 +503,7 @@
   // the dialog is guaranteed to show.
   PrepareForDialog(browsers_[1]);
 
-  content::RepeatedNotificationObserver cancel_observer(
+  RepeatedNotificationObserver cancel_observer(
       chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, 2);
   chrome::CloseAllBrowsersAndQuit();
   ASSERT_NO_FATAL_FAILURE(CancelClose());
@@ -480,7 +514,7 @@
   EXPECT_EQ(1, browsers_[1]->tab_strip_model()->count());
   EXPECT_EQ(1, browsers_[2]->tab_strip_model()->count());
 
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, 3);
   chrome::CloseAllBrowsersAndQuit();
   ASSERT_NO_FATAL_FAILURE(AcceptClose());
@@ -515,7 +549,7 @@
   PrepareForDialog(
       browsers_[0]->tab_strip_model()->GetWebContentsAt(kResposiveTabIndex));
 
-  content::RepeatedNotificationObserver cancel_observer(
+  RepeatedNotificationObserver cancel_observer(
       chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, 1);
   chrome::CloseAllBrowsersAndQuit();
   ASSERT_NO_FATAL_FAILURE(CancelClose());
@@ -524,7 +558,7 @@
 
   // All tabs should still be open.
   EXPECT_EQ(kTabCount, browsers_[0]->tab_strip_model()->count());
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, 1);
 
   // Quit, this time accepting close confirmation dialog.
@@ -563,7 +597,7 @@
   // the dialog is guaranteed to show.
   PrepareForDialog(browsers_[kResposiveBrowserIndex]);
 
-  content::RepeatedNotificationObserver cancel_observer(
+  RepeatedNotificationObserver cancel_observer(
       chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, kResposiveBrowserIndex + 1);
   chrome::CloseAllBrowsersAndQuit();
   ASSERT_NO_FATAL_FAILURE(CancelClose());
@@ -575,7 +609,7 @@
     EXPECT_EQ(1, browsers_[i]->tab_strip_model()->count());
 
   // Quit, this time accepting close confirmation dialog.
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, kBrowserCount);
   chrome::CloseAllBrowsersAndQuit();
   ASSERT_NO_FATAL_FAILURE(AcceptClose());
@@ -592,7 +626,7 @@
       browsers_[0], embedded_test_server()->GetURL("/beforeunload.html")));
   PrepareForDialog(browsers_[0]);
 
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, 2);
   chrome::CloseAllBrowsersAndQuit();
   browsers_.push_back(CreateBrowser(browser()->profile()));
@@ -611,7 +645,7 @@
       browsers_[0], embedded_test_server()->GetURL("/beforeunload.html")));
   PrepareForDialog(browsers_[0]);
 
-  content::RepeatedNotificationObserver cancel_observer(
+  RepeatedNotificationObserver cancel_observer(
       chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, 2);
   chrome::CloseAllBrowsersAndQuit();
   browsers_.push_back(CreateBrowser(browser()->profile()));
@@ -626,7 +660,7 @@
   EXPECT_EQ(1, browsers_[1]->tab_strip_model()->count());
 
   // Allow shutdown for both beforeunload dialogs.
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, 2);
   chrome::CloseAllBrowsersAndQuit();
   ASSERT_NO_FATAL_FAILURE(AcceptClose());
@@ -648,7 +682,7 @@
   PrepareForDialog(browsers_[0]);
   PrepareForDialog(browsers_[1]);
 
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, 2);
   chrome::CloseAllBrowsersAndQuit();
   ASSERT_NO_FATAL_FAILURE(AcceptClose());
@@ -674,7 +708,7 @@
   PrepareForDialog(browsers_[0]);
   PrepareForDialog(browsers_[1]);
 
-  content::RepeatedNotificationObserver cancel_observer(
+  RepeatedNotificationObserver cancel_observer(
       chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, 2);
   chrome::CloseAllBrowsersAndQuit();
   ASSERT_NO_FATAL_FAILURE(AcceptClose());
@@ -693,7 +727,7 @@
   EXPECT_EQ(2, browsers_[0]->tab_strip_model()->count());
   EXPECT_EQ(2, browsers_[1]->tab_strip_model()->count());
 
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, 2);
   chrome::CloseAllBrowsersAndQuit();
   ASSERT_NO_FATAL_FAILURE(AcceptClose());
@@ -818,7 +852,7 @@
       browsers_[0], embedded_test_server()->GetURL("/beforeunload.html")));
   PrepareForDialog(browsers_[0]);
 
-  content::RepeatedNotificationObserver cancel_observer(
+  RepeatedNotificationObserver cancel_observer(
       chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, 1);
   chrome::CloseAllBrowsersAndQuit();
 
@@ -834,7 +868,7 @@
   EXPECT_EQ(1, browsers_[0]->tab_strip_model()->count());
   EXPECT_EQ(1, browsers_[1]->tab_strip_model()->count());
 
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, 2);
   chrome::CloseAllBrowsersAndQuit();
   browsers_[1]->tab_strip_model()->CloseAllTabs();
@@ -853,7 +887,7 @@
       browsers_[0], embedded_test_server()->GetURL("/beforeunload.html")));
   PrepareForDialog(browsers_[0]);
 
-  content::RepeatedNotificationObserver cancel_observer(
+  RepeatedNotificationObserver cancel_observer(
       chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, 2);
   chrome::CloseAllBrowsersAndQuit();
 
@@ -869,7 +903,7 @@
   EXPECT_EQ(1, browsers_[0]->tab_strip_model()->count());
   EXPECT_EQ(1, browsers_[1]->tab_strip_model()->count());
 
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, 2);
   chrome::CloseAllBrowsersAndQuit();
   ASSERT_FALSE(browsers_[1]->ShouldCloseWindow());
@@ -892,7 +926,7 @@
   PrepareForDialog(browsers_[0]);
   PrepareForDialog(browsers_[1]);
 
-  content::RepeatedNotificationObserver cancel_observer(
+  RepeatedNotificationObserver cancel_observer(
       chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, 1);
   chrome::CloseAllBrowsersAndQuit();
 
@@ -903,7 +937,7 @@
   EXPECT_EQ(1, browsers_[0]->tab_strip_model()->count());
   EXPECT_EQ(1, browsers_[1]->tab_strip_model()->count());
 
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, 2);
   chrome::CloseAllBrowsersAndQuit();
   ASSERT_FALSE(browsers_[0]->ShouldCloseWindow());
@@ -953,7 +987,7 @@
   SetDownloadPathForProfile(browser()->profile());
   ASSERT_NO_FATAL_FAILURE(CreateStalledDownload(browser()));
 
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, 1);
 
   TestBrowserCloseManager::AttemptClose(
@@ -1002,7 +1036,7 @@
       browser()->profile())->NonMaliciousInProgressCount());
 
   // Close the browser with no user action.
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, 1);
   TestBrowserCloseManager::AttemptClose(
       TestBrowserCloseManager::NO_USER_CHOICE);
@@ -1026,7 +1060,7 @@
   EXPECT_EQ(GURL(chrome::kChromeUIDownloadsURL),
             browser()->tab_strip_model()->GetActiveWebContents()->GetURL());
 
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, 1);
 
   TestBrowserCloseManager::AttemptClose(
@@ -1048,7 +1082,7 @@
   SetDownloadPathForProfile(otr_profile);
   Browser* otr_browser = CreateBrowser(otr_profile);
   {
-    content::RepeatedNotificationObserver close_observer(
+    RepeatedNotificationObserver close_observer(
         chrome::NOTIFICATION_BROWSER_CLOSED, 1);
     browser()->window()->Close();
     close_observer.Wait();
@@ -1063,7 +1097,7 @@
   EXPECT_EQ(GURL(chrome::kChromeUIDownloadsURL),
             otr_browser->tab_strip_model()->GetActiveWebContents()->GetURL());
 
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, 1);
 
   TestBrowserCloseManager::AttemptClose(
@@ -1097,7 +1131,7 @@
   ASSERT_EQ(0, num_downloads_blocking);
 
   {
-    content::RepeatedNotificationObserver close_observer(
+    RepeatedNotificationObserver close_observer(
         chrome::NOTIFICATION_BROWSER_CLOSED, 1);
     otr_browser->window()->Close();
     close_observer.Wait();
@@ -1109,7 +1143,7 @@
   ASSERT_EQ(1, num_downloads_blocking);
 
   {
-    content::RepeatedNotificationObserver close_observer(
+    RepeatedNotificationObserver close_observer(
         chrome::NOTIFICATION_BROWSER_CLOSED, 2);
     TestBrowserCloseManager::AttemptClose(
         TestBrowserCloseManager::USER_CHOICE_USER_ALLOWS_CLOSE);
@@ -1147,7 +1181,7 @@
   SetDownloadPathForProfile(other_profile);
   ASSERT_NO_FATAL_FAILURE(CreateStalledDownload(browser()));
   {
-    content::RepeatedNotificationObserver close_observer(
+    RepeatedNotificationObserver close_observer(
         chrome::NOTIFICATION_BROWSER_CLOSED, 1);
     browser()->window()->Close();
     close_observer.Wait();
@@ -1168,7 +1202,7 @@
             other_profile_browser->tab_strip_model()->GetActiveWebContents()
                 ->GetURL());
 
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, 2);
   TestBrowserCloseManager::AttemptClose(
       TestBrowserCloseManager::USER_CHOICE_USER_ALLOWS_CLOSE);
@@ -1200,7 +1234,7 @@
   cancel_observer.Wait();
   EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
 
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, 1);
   TestBrowserCloseManager::AttemptClose(
       TestBrowserCloseManager::USER_CHOICE_USER_ALLOWS_CLOSE);
@@ -1246,7 +1280,7 @@
   std::unique_ptr<ScopedKeepAlive> tmp_keep_alive;
   Profile* profile = browser()->profile();
   {
-    content::RepeatedNotificationObserver close_observer(
+    RepeatedNotificationObserver close_observer(
         chrome::NOTIFICATION_BROWSER_CLOSED, 1);
     tmp_keep_alive.reset(new ScopedKeepAlive(KeepAliveOrigin::PANEL_VIEW,
                                              KeepAliveRestartOption::DISABLED));
@@ -1263,7 +1297,7 @@
   new_browser_observer.WaitForSingleNewBrowser();
   tmp_keep_alive.reset();
   EXPECT_FALSE(IsBackgroundModeSuspended());
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, 1);
 
   // Background mode should not be suspended when quitting.
@@ -1278,7 +1312,7 @@
 // background mode.
 IN_PROC_BROWSER_TEST_P(BrowserCloseManagerWithBackgroundModeBrowserTest,
                        DISABLED_CloseSingleBrowserWithBackgroundMode) {
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, 1);
   EXPECT_FALSE(IsBackgroundModeSuspended());
   browser()->window()->Close();
@@ -1292,7 +1326,7 @@
 // background mode but does not cause Chrome to quit.
 IN_PROC_BROWSER_TEST_P(BrowserCloseManagerWithBackgroundModeBrowserTest,
                        DISABLED_CloseAllBrowsersWithNoOpenBrowsersWithBackgroundMode) {
-  content::RepeatedNotificationObserver close_observer(
+  RepeatedNotificationObserver close_observer(
       chrome::NOTIFICATION_BROWSER_CLOSED, 1);
   EXPECT_FALSE(IsBackgroundModeSuspended());
   ScopedKeepAlive tmp_keep_alive(KeepAliveOrigin::PANEL_VIEW,
diff --git a/chrome/browser/loader/chrome_resource_dispatcher_host_delegate.cc b/chrome/browser/loader/chrome_resource_dispatcher_host_delegate.cc
index 23e8ba8..8e1a788d 100644
--- a/chrome/browser/loader/chrome_resource_dispatcher_host_delegate.cc
+++ b/chrome/browser/loader/chrome_resource_dispatcher_host_delegate.cc
@@ -982,7 +982,8 @@
     return content::PREVIEWS_OFF;
 
   // If the allowed previews are limited, ensure we honor those limits.
-  if (previews_state != content::PREVIEWS_OFF &&
+  if (previews_to_allow != content::PREVIEWS_UNSPECIFIED &&
+      previews_state != content::PREVIEWS_OFF &&
       previews_state != content::PREVIEWS_NO_TRANSFORM) {
     previews_state = previews_state & previews_to_allow;
     // If no valid previews are left, set the state explictly to PREVIEWS_OFF.
diff --git a/chrome/browser/media/android/router/media_router_dialog_controller_android.cc b/chrome/browser/media/android/router/media_router_dialog_controller_android.cc
index 08983f6..59db7ee 100644
--- a/chrome/browser/media/android/router/media_router_dialog_controller_android.cc
+++ b/chrome/browser/media/android/router/media_router_dialog_controller_android.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/media/router/media_router.h"
 #include "chrome/browser/media/router/media_router_factory.h"
 #include "chrome/browser/media/router/presentation_request.h"
+#include "chrome/browser/vr/vr_tab_helper.h"
 #include "chrome/common/media_router/media_source.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
@@ -21,10 +22,6 @@
 #include "device/vr/features/features.h"
 #include "jni/ChromeMediaRouterDialogController_jni.h"
 
-#if BUILDFLAG(ENABLE_VR)
-#include "chrome/browser/android/vr_shell/vr_tab_helper.h"
-#endif  // BUILDFLAG(ENABLE_VR)
-
 DEFINE_WEB_CONTENTS_USER_DATA_KEY(
     media_router::MediaRouterDialogControllerAndroid);
 
@@ -148,13 +145,11 @@
 }
 
 void MediaRouterDialogControllerAndroid::CreateMediaRouterDialog() {
-#if BUILDFLAG(ENABLE_VR)
   // TODO(crbug.com/736568): Re-enable dialog in VR.
-  if (vr_shell::VrTabHelper::IsInVr(initiator())) {
+  if (vr::VrTabHelper::IsInVr(initiator())) {
     CancelPresentationRequest();
     return;
   }
-#endif  // BUILDFLAG(ENABLE_VR)
 
   JNIEnv* env = base::android::AttachCurrentThread();
 
diff --git a/chrome/browser/media/encrypted_media_browsertest.cc b/chrome/browser/media/encrypted_media_browsertest.cc
index 4d7bb356..16243b2f 100644
--- a/chrome/browser/media/encrypted_media_browsertest.cc
+++ b/chrome/browser/media/encrypted_media_browsertest.cc
@@ -308,6 +308,11 @@
         scoped_feature_list_.InitWithFeatures(
             {media::kExternalClearKeyForTesting}, {});
       }
+    } else {
+      if (cdm_host_type == CdmHostType::kMojo) {
+        scoped_feature_list_.InitWithFeatures(
+            {media::kMojoCdm, media::kSupportExperimentalCdmInterface}, {});
+      }
     }
 #endif  // BUILDFLAG(ENABLE_PEPPER_CDMS)
   }
diff --git a/chrome/browser/media/media_engagement_contents_observer.cc b/chrome/browser/media/media_engagement_contents_observer.cc
index 99ca935..20ab226 100644
--- a/chrome/browser/media/media_engagement_contents_observer.cc
+++ b/chrome/browser/media/media_engagement_contents_observer.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/media/media_engagement_contents_observer.h"
 
+#include "build/build_config.h"
 #include "chrome/browser/media/media_engagement_service.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/web_contents.h"
@@ -62,6 +63,7 @@
 
   if (committed_origin_.unique())
     return;
+
   service_->RecordVisit(committed_origin_.GetURL());
 }
 
@@ -146,8 +148,12 @@
 
   // Do not record significant playback if the tab did not make
   // a sound in the last two seconds.
+#if defined(OS_ANDROID)
+// Skipping WasRecentlyAudible check on Android (not available).
+#else
   if (!web_contents()->WasRecentlyAudible())
     return;
+#endif
 
   significant_playback_recorded_ = true;
 
diff --git a/chrome/browser/media/media_engagement_contents_observer_unittest.cc b/chrome/browser/media/media_engagement_contents_observer_unittest.cc
index 3e3bebf..a66b955 100644
--- a/chrome/browser/media/media_engagement_contents_observer_unittest.cc
+++ b/chrome/browser/media/media_engagement_contents_observer_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "base/test/scoped_feature_list.h"
 #include "base/timer/mock_timer.h"
+#include "build/build_config.h"
 #include "chrome/browser/media/media_engagement_score.h"
 #include "chrome/browser/media/media_engagement_service.h"
 #include "chrome/browser/media/media_engagement_service_factory.h"
@@ -127,8 +128,12 @@
   }
 
   void SimulateAudible() {
+#if defined(OS_ANDROID)
+// WasRecentlyAudible is not available on Android.
+#else
     content::WebContentsTester::For(web_contents())
         ->SetWasRecentlyAudible(true);
+#endif
   }
 
   void SimulateInaudible() {
diff --git a/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc b/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc
index c142faad..f2be4a8 100644
--- a/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc
+++ b/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc
@@ -21,9 +21,6 @@
 #include "chrome/browser/chrome_browser_main.h"
 #include "chrome/browser/mac/bluetooth_utility.h"
 #include "chrome/browser/shell_integration.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/browser_list.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "components/flags_ui/pref_service_flags_storage.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/common/content_switches.h"
@@ -549,17 +546,7 @@
   is_screen_observer_ = true;
 
 #if !defined(OS_ANDROID)
-  const BrowserList* browser_list = BrowserList::GetInstance();
-
-  auto first_browser = browser_list->begin();
-  if (first_browser != browser_list->end()) {
-    TabStripModel* tab_strip = (*first_browser)->tab_strip_model();
-    DCHECK(tab_strip);
-    // In case this code becomes reachable with empty tab strip,
-    // startup_metric_utils::SetNonBrowserUIDisplayed() should be used.
-    DCHECK(!tab_strip->empty());
-    metrics::BeginFirstWebContentsProfiling();
-  }
+  metrics::BeginFirstWebContentsProfiling();
   metrics::TabUsageRecorder::InitializeIfNeeded();
 #endif  // !defined(OS_ANDROID)
 }
diff --git a/chrome/browser/net/chrome_network_delegate.cc b/chrome/browser/net/chrome_network_delegate.cc
index fae150e..d3643a0 100644
--- a/chrome/browser/net/chrome_network_delegate.cc
+++ b/chrome/browser/net/chrome_network_delegate.cc
@@ -64,7 +64,6 @@
 
 #if defined(OS_ANDROID)
 #include "chrome/browser/io_thread.h"
-#include "chrome/browser/precache/precache_util.h"
 #endif
 
 #if defined(OS_CHROMEOS)
@@ -377,12 +376,6 @@
   // of redirected requests.
   RecordNetworkErrorHistograms(request, net_error);
 
-  if (net_error == net::OK) {
-#if defined(OS_ANDROID)
-    precache::UpdatePrecacheMetricsAndState(request, profile_);
-#endif  // defined(OS_ANDROID)
-  }
-
   extensions_delegate_->OnCompleted(request, started, net_error);
   if (domain_reliability_monitor_)
     domain_reliability_monitor_->OnCompleted(request, started);
diff --git a/chrome/browser/ntp_tiles/ntp_tiles_browsertest.cc b/chrome/browser/ntp_tiles/ntp_tiles_browsertest.cc
index 4e49c10..7c26946 100644
--- a/chrome/browser/ntp_tiles/ntp_tiles_browsertest.cc
+++ b/chrome/browser/ntp_tiles/ntp_tiles_browsertest.cc
@@ -8,6 +8,7 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/history/core/browser/top_sites.h"
 #include "components/ntp_tiles/most_visited_sites.h"
 #include "components/ntp_tiles/ntp_tile.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -127,4 +128,26 @@
                                               TileSource::TOP_SITES))));
 }
 
+// Tests usage of MostVisitedSites mimicking Chrome Home, where an observer is
+// installed early and once and navigations follow afterwards.
+IN_PROC_BROWSER_TEST_F(NTPTilesTest, NavigateAfterSettingObserver) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  const GURL page_url = embedded_test_server()->GetURL("/simple.html");
+
+  // Register the observer before doing the navigation.
+  MostVisitedSitesWaiter waiter;
+  most_visited_sites_->SetMostVisitedURLsObserver(&waiter, /*num_sites=*/8);
+
+  ui_test_utils::NavigateToURLWithDisposition(
+      browser(), page_url, WindowOpenDisposition::CURRENT_TAB,
+      ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
+
+  // TODO(crbug.com/741431): When Refresh calls SyncWithHistory() for signed
+  // out users, replace the call below with most_visited_sites_->Refresh().
+  most_visited_sites_->top_sites()->SyncWithHistory();
+  NTPTilesVector tiles = waiter.WaitForTiles();
+  EXPECT_THAT(tiles, Contains(MatchesTile("OK", page_url.spec().c_str(),
+                                          TileSource::TOP_SITES)));
+}
+
 }  // namespace ntp_tiles
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.cc b/chrome/browser/password_manager/chrome_password_manager_client.cc
index c3446297..f93d6e5 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.cc
+++ b/chrome/browser/password_manager/chrome_password_manager_client.cc
@@ -26,6 +26,7 @@
 #include "chrome/browser/sync/profile_sync_service_factory.h"
 #include "chrome/browser/ui/autofill/password_generation_popup_controller_impl.h"
 #include "chrome/browser/ui/passwords/passwords_client_ui_delegate.h"
+#include "chrome/browser/vr/vr_tab_helper.h"
 #include "chrome/common/channel_info.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/url_constants.h"
@@ -88,11 +89,6 @@
 #include "extensions/common/constants.h"
 #endif
 
-#include "device/vr/features/features.h"
-#if BUILDFLAG(ENABLE_VR)
-#include "chrome/browser/android/vr_shell/vr_tab_helper.h"
-#endif  // BUILDFLAG(ENABLE_VR)
-
 using password_manager::ContentPasswordManagerDriverFactory;
 using password_manager::PasswordManagerInternalsService;
 using password_manager::PasswordManagerMetricsRecorder;
@@ -216,12 +212,10 @@
         entry->GetURL().host_piece() != chrome::kChromeUIChromeSigninHost;
   }
 
-#if BUILDFLAG(ENABLE_VR)
   // The password manager is disabled while VR (virtual reality) is being used,
   // as the use of conventional UI elements might harm the user experience in
   // VR.
-  is_enabled = is_enabled && !vr_shell::VrTabHelper::IsInVr(web_contents());
-#endif  // BUILDFLAG(ENABLE_VR)
+  is_enabled = is_enabled && !vr::VrTabHelper::IsInVr(web_contents());
 
   if (log_manager_->IsLoggingActive()) {
     password_manager::BrowserSavePasswordProgressLogger logger(
diff --git a/chrome/browser/permissions/permission_manager.cc b/chrome/browser/permissions/permission_manager.cc
index f2850063..bc068fc 100644
--- a/chrome/browser/permissions/permission_manager.cc
+++ b/chrome/browser/permissions/permission_manager.cc
@@ -24,6 +24,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/storage/durable_storage_permission_context.h"
 #include "chrome/browser/tab_contents/tab_util.h"
+#include "chrome/browser/vr/vr_tab_helper.h"
 #include "chrome/common/features.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "content/public/browser/browser_thread.h"
@@ -34,10 +35,6 @@
 #include "device/vr/features/features.h"
 #include "ppapi/features/features.h"
 
-#if BUILDFLAG(ENABLE_VR) && defined(OS_ANDROID)
-#include "chrome/browser/android/vr_shell/vr_tab_helper.h"
-#endif  // BUILDFLAG(ENABLE_VR) && defined(OS_ANDROID)
-
 #if BUILDFLAG(ENABLE_PLUGINS)
 #include "chrome/browser/plugins/flash_permission_context.h"
 #endif
@@ -329,13 +326,11 @@
   content::WebContents* web_contents =
       content::WebContents::FromRenderFrameHost(render_frame_host);
 
-#if BUILDFLAG(ENABLE_VR) && defined(OS_ANDROID)
-  if (vr_shell::VrTabHelper::IsInVr(web_contents)) {
+  if (vr::VrTabHelper::IsInVr(web_contents)) {
     callback.Run(
         std::vector<ContentSetting>(permissions.size(), CONTENT_SETTING_BLOCK));
     return kNoPendingOperation;
   }
-#endif  // BUILDFLAG(ENABLE_VR) && defined(OS_ANDROID)
 
   GURL embedding_origin = web_contents->GetLastCommittedURL().GetOrigin();
 
diff --git a/chrome/browser/permissions/permission_manager_unittest.cc b/chrome/browser/permissions/permission_manager_unittest.cc
index b5911d6b..212d1af 100644
--- a/chrome/browser/permissions/permission_manager_unittest.cc
+++ b/chrome/browser/permissions/permission_manager_unittest.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/permissions/permission_request_manager.h"
 #include "chrome/browser/permissions/permission_result.h"
 #include "chrome/browser/ui/permission_bubble/mock_permission_prompt_factory.h"
+#include "chrome/browser/vr/vr_tab_helper.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
@@ -19,18 +20,12 @@
 #include "device/vr/features/features.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-#if BUILDFLAG(ENABLE_VR) && defined(OS_ANDROID)
-#include "chrome/browser/android/vr_shell/vr_tab_helper.h"
-#endif  // BUILDFLAG(ENABLE_VR) && defined(OS_ANDROID)
-
 using blink::mojom::PermissionStatus;
 using content::PermissionType;
 
 namespace {
 
-#if BUILDFLAG(ENABLE_VR) && defined(OS_ANDROID)
 int kNoPendingOperation = -1;
-#endif  // BUILDFLAG(ENABLE_VR) && defined(OS_ANDROID)
 
 class PermissionManagerTestingProfile final : public TestingProfile {
  public:
@@ -406,10 +401,9 @@
   GetPermissionManager()->UnsubscribePermissionStatusChange(subscription_id);
 }
 
-#if BUILDFLAG(ENABLE_VR) && defined(OS_ANDROID)
 TEST_F(PermissionManagerTest, SuppressPermissionRequests) {
   content::WebContents* contents = web_contents();
-  vr_shell::VrTabHelper::CreateForWebContents(contents);
+  vr::VrTabHelper::CreateForWebContents(contents);
   NavigateAndCommit(url());
 
   SetPermission(CONTENT_SETTINGS_TYPE_NOTIFICATIONS, CONTENT_SETTING_ALLOW);
@@ -420,8 +414,7 @@
   EXPECT_TRUE(callback_called());
   EXPECT_EQ(PermissionStatus::GRANTED, callback_result());
 
-  vr_shell::VrTabHelper* vr_tab_helper =
-      vr_shell::VrTabHelper::FromWebContents(contents);
+  vr::VrTabHelper* vr_tab_helper = vr::VrTabHelper::FromWebContents(contents);
   vr_tab_helper->SetIsInVr(true);
   EXPECT_EQ(
       kNoPendingOperation,
@@ -440,7 +433,6 @@
   EXPECT_TRUE(callback_called());
   EXPECT_EQ(PermissionStatus::GRANTED, callback_result());
 }
-#endif  // BUILDFLAG(ENABLE_VR) && defined(OS_ANDROID)
 
 TEST_F(PermissionManagerTest, PermissionIgnoredCleanup) {
   content::WebContents* contents = web_contents();
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service.cc b/chrome/browser/policy/cloud/user_policy_signin_service.cc
index ee16133..d3e9c258 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service.cc
+++ b/chrome/browser/policy/cloud/user_policy_signin_service.cc
@@ -137,6 +137,17 @@
   callback.Run(client->dm_token(), client->client_id());
 }
 
+void UserPolicySigninService::GoogleSigninSucceeded(
+    const std::string& account_id,
+    const std::string& username) {
+  if (!oauth2_token_service_->RefreshTokenIsAvailable(account_id))
+    return;
+
+  // ProfileOAuth2TokenService now has a refresh token for the primary account
+  // so initialize the UserCloudPolicyManager.
+  TryInitializeForSignedInUser();
+}
+
 void UserPolicySigninService::OnRefreshTokenAvailable(
     const std::string& account_id) {
   // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460 is
@@ -145,6 +156,20 @@
       FROM_HERE_WITH_EXPLICIT_FUNCTION(
           "422460 UserPolicySigninService::OnRefreshTokenAvailable"));
 
+  // Ignore OAuth tokens for any account but the primary one.
+  if (account_id != signin_manager()->GetAuthenticatedAccountId())
+    return;
+
+  // ProfileOAuth2TokenService now has a refresh token for the primary account
+  // so initialize the UserCloudPolicyManager.
+  TryInitializeForSignedInUser();
+}
+
+void UserPolicySigninService::TryInitializeForSignedInUser() {
+  DCHECK(signin_manager()->IsAuthenticated());
+  DCHECK(oauth2_token_service_->RefreshTokenIsAvailable(
+      signin_manager()->GetAuthenticatedAccountId()));
+
   // If using a TestingProfile with no UserCloudPolicyManager, skip
   // initialization.
   if (!policy_manager()) {
@@ -152,12 +177,6 @@
     return;
   }
 
-  // Ignore OAuth tokens for any account but the primary one.
-  if (account_id != signin_manager()->GetAuthenticatedAccountId())
-    return;
-
-  // ProfileOAuth2TokenService now has a refresh token so initialize the
-  // UserCloudPolicyManager.
   InitializeForSignedInUser(
       signin_manager()->GetAuthenticatedAccountInfo().email,
       profile_->GetRequestContext());
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service.h b/chrome/browser/policy/cloud/user_policy_signin_service.h
index 7cd00098..819d29a0 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service.h
+++ b/chrome/browser/policy/cloud/user_policy_signin_service.h
@@ -60,6 +60,11 @@
       const std::string& account_id,
       const PolicyRegistrationCallback& callback);
 
+  // SigninManagerBase::Observer implementation:
+  // UserPolicySigninService is already an observer of SigninManagerBase.
+  void GoogleSigninSucceeded(const std::string& account_id,
+                             const std::string& username) override;
+
   // OAuth2TokenService::Observer implementation:
   void OnRefreshTokenAvailable(const std::string& account_id) override;
 
@@ -92,6 +97,11 @@
   // cloud policy.
   void ProhibitSignoutIfNeeded();
 
+  // Helper method that attempts calls |InitializeForSignedInUser| only if
+  // |policy_manager| is not-nul. Expects that there is a refresh token for
+  // the primary account.
+  void TryInitializeForSignedInUser();
+
   // Invoked when a policy registration request is complete.
   void CallPolicyRegistrationCallback(std::unique_ptr<CloudPolicyClient> client,
                                       PolicyRegistrationCallback callback);
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service_unittest.cc b/chrome/browser/policy/cloud/user_policy_signin_service_unittest.cc
index d4fa2fa..aea94a0 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service_unittest.cc
+++ b/chrome/browser/policy/cloud/user_policy_signin_service_unittest.cc
@@ -400,9 +400,39 @@
   ASSERT_FALSE(manager_->core()->service());
 }
 
-  // TODO(joaodasilva): these tests rely on issuing the OAuth2 login refresh
-  // token after signin. Revisit this after figuring how to handle that on
-  // Android.
+#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+TEST_F(UserPolicySigninServiceTest, InitRefreshTokenAvailableBeforeSignin) {
+  // Make sure user is not signed in.
+  ASSERT_FALSE(
+      SigninManagerFactory::GetForProfile(profile_.get())->IsAuthenticated());
+
+  // No oauth access token yet, so client registration should be deferred.
+  ASSERT_FALSE(IsRequestActive());
+
+  // Make oauth token available.
+  std::string account_id = AccountTrackerService::PickAccountIdForAccount(
+      profile_.get()->GetPrefs(), kTestGaiaId, kTestUser);
+  GetTokenService()->UpdateCredentials(account_id, "oauth_login_refresh_token");
+
+  // Not ssigned in yet, so client registration should be deferred.
+  ASSERT_FALSE(IsRequestActive());
+
+  // Sign in to Chrome.
+  signin_manager_->SignIn(kTestGaiaId, kTestUser, "");
+
+  // Complete initialization of the store.
+  mock_store_->NotifyStoreLoaded();
+
+  // Client registration should be in progress since we now have an oauth token
+  // for the authenticated account id.
+  EXPECT_EQ(mock_store_->signin_username(), kTestUser);
+  ASSERT_TRUE(IsRequestActive());
+}
+#endif  // !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+
+// TODO(joaodasilva): these tests rely on issuing the OAuth2 login refresh
+// token after signin. Revisit this after figuring how to handle that on
+// Android.
 #if !defined(OS_ANDROID)
 
 TEST_F(UserPolicySigninServiceSignedInTest, InitWhileSignedIn) {
diff --git a/chrome/browser/printing/background_printing_manager.cc b/chrome/browser/printing/background_printing_manager.cc
index 330ecb9..fd10907 100644
--- a/chrome/browser/printing/background_printing_manager.cc
+++ b/chrome/browser/printing/background_printing_manager.cc
@@ -113,6 +113,11 @@
   }
 }
 
+void BackgroundPrintingManager::OnPrintRequestCancelled(
+    WebContents* preview_contents) {
+  DeletePreviewContents(preview_contents);
+}
+
 void BackgroundPrintingManager::DeletePreviewContents(
     WebContents* preview_contents) {
   auto i = printing_contents_map_.find(preview_contents);
diff --git a/chrome/browser/printing/background_printing_manager.h b/chrome/browser/printing/background_printing_manager.h
index ff0bd1e..ea211ad 100644
--- a/chrome/browser/printing/background_printing_manager.h
+++ b/chrome/browser/printing/background_printing_manager.h
@@ -48,6 +48,8 @@
   void DeletePreviewContentsForBrowserContext(
       content::BrowserContext* browser_context);
 
+  void OnPrintRequestCancelled(content::WebContents* preview_dialog);
+
  private:
   // content::NotificationObserver overrides:
   void Observe(int type,
diff --git a/chrome/browser/push_messaging/push_messaging_browsertest.cc b/chrome/browser/push_messaging/push_messaging_browsertest.cc
index e1deb6d3..3f9ca09b 100644
--- a/chrome/browser/push_messaging/push_messaging_browsertest.cc
+++ b/chrome/browser/push_messaging/push_messaging_browsertest.cc
@@ -56,6 +56,7 @@
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_switches.h"
+#include "content/public/common/push_messaging_status.mojom.h"
 #include "content/public/common/push_subscription_options.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/browsing_data_remover_test_util.h"
@@ -108,8 +109,8 @@
                  const std::string& registration_id,
                  const std::vector<uint8_t>& p256dh,
                  const std::vector<uint8_t>& auth,
-                 content::PushRegistrationStatus status) {
-  EXPECT_EQ(content::PUSH_REGISTRATION_STATUS_SUCCESS_FROM_PUSH_SERVICE,
+                 content::mojom::PushRegistrationStatus status) {
+  EXPECT_EQ(content::mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE,
             status);
   done_callback.Run();
 }
@@ -1070,7 +1071,7 @@
       0 /* SERVICE_WORKER_OK */, 1);
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.DeliveryStatus",
-       content::PUSH_DELIVERY_STATUS_SUCCESS, 1);
+      static_cast<int>(content::mojom::PushDeliveryStatus::SUCCESS), 1);
 }
 
 IN_PROC_BROWSER_TEST_F(PushMessagingBrowserTest, PushEventOnShutdown) {
@@ -1172,13 +1173,16 @@
       "PushMessaging.DeliveryStatus.ServiceWorkerEvent", 0);
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.DeliveryStatus",
-      content::PUSH_DELIVERY_STATUS_NO_SERVICE_WORKER, 1);
+      static_cast<int>(content::mojom::PushDeliveryStatus::NO_SERVICE_WORKER),
+      1);
 
   // Missing Service Workers should trigger an automatic unsubscription attempt.
   EXPECT_EQ(app_id, gcm_driver_->last_deletetoken_app_id());
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_DELIVERY_NO_SERVICE_WORKER, 1);
+      static_cast<int>(
+          content::mojom::PushUnregistrationReason::DELIVERY_NO_SERVICE_WORKER),
+      1);
 
   // |app_identifier| should no longer be stored in prefs.
   PushMessagingAppIdentifier stored_app_identifier =
@@ -1202,7 +1206,9 @@
   EXPECT_EQ("unsubscribe result: true", script_result);
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_JAVASCRIPT_API, 1);
+      static_cast<int>(
+          content::mojom::PushUnregistrationReason::JAVASCRIPT_API),
+      1);
 
   gcm::IncomingMessage message;
   message.sender_id = GetTestApplicationServerKey();
@@ -1221,13 +1227,15 @@
       "PushMessaging.DeliveryStatus.ServiceWorkerEvent", 0);
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.DeliveryStatus",
-      content::PUSH_DELIVERY_STATUS_UNKNOWN_APP_ID, 1);
+      static_cast<int>(content::mojom::PushDeliveryStatus::UNKNOWN_APP_ID), 1);
 
   // Missing subscriptions should trigger an automatic unsubscription attempt.
   EXPECT_EQ(app_identifier.app_id(), gcm_driver_->last_deletetoken_app_id());
   histogram_tester_.ExpectBucketCount(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_DELIVERY_UNKNOWN_APP_ID, 1);
+      static_cast<int>(
+          content::mojom::PushUnregistrationReason::DELIVERY_UNKNOWN_APP_ID),
+      1);
 }
 
 // Tests receiving messages for an origin that does not have permission, but
@@ -1270,7 +1278,8 @@
       "PushMessaging.DeliveryStatus.ServiceWorkerEvent", 0);
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.DeliveryStatus",
-      content::PUSH_DELIVERY_STATUS_PERMISSION_DENIED, 1);
+      static_cast<int>(content::mojom::PushDeliveryStatus::PERMISSION_DENIED),
+      1);
 
   // Missing permission should trigger an automatic unsubscription attempt.
   EXPECT_EQ(app_identifier.app_id(), gcm_driver_->last_deletetoken_app_id());
@@ -1283,7 +1292,9 @@
   EXPECT_TRUE(app_identifier_afterwards.is_null());
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_DELIVERY_PERMISSION_DENIED, 1);
+      static_cast<int>(
+          content::mojom::PushUnregistrationReason::DELIVERY_PERMISSION_DENIED),
+      1);
 }
 
 IN_PROC_BROWSER_TEST_F(PushMessagingBrowserTest,
@@ -1613,14 +1624,18 @@
   EXPECT_EQ("unsubscribe result: true", script_result);
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_JAVASCRIPT_API, 1);
+      static_cast<int>(
+          content::mojom::PushUnregistrationReason::JAVASCRIPT_API),
+      1);
 
   // Resolves false if there was no longer a subscription.
   ASSERT_TRUE(RunScript("unsubscribeStoredPushSubscription()", &script_result));
   EXPECT_EQ("unsubscribe result: false", script_result);
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_JAVASCRIPT_API, 2);
+      static_cast<int>(
+          content::mojom::PushUnregistrationReason::JAVASCRIPT_API),
+      2);
 
   // TODO(johnme): Test that doesn't reject if there was a network error (should
   // deactivate subscription locally anyway).
@@ -1641,7 +1656,9 @@
   EXPECT_EQ("unsubscribe result: true", script_result);
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_JAVASCRIPT_API, 3);
+      static_cast<int>(
+          content::mojom::PushUnregistrationReason::JAVASCRIPT_API),
+      3);
 
   // Unsubscribing (with an existing reference to a PushSubscription), after
   // unregistering the Service Worker, should fail.
@@ -1663,7 +1680,9 @@
   // Unregistering should have triggered an automatic unsubscribe.
   histogram_tester_.ExpectBucketCount(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_SERVICE_WORKER_UNREGISTERED, 1);
+      static_cast<int>(content::mojom::PushUnregistrationReason::
+                           SERVICE_WORKER_UNREGISTERED),
+      1);
   histogram_tester_.ExpectTotalCount("PushMessaging.UnregistrationReason", 4);
 
   // Now manual unsubscribe should return false.
@@ -1687,14 +1706,18 @@
   EXPECT_EQ("unsubscribe result: true", script_result);
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_JAVASCRIPT_API, 1);
+      static_cast<int>(
+          content::mojom::PushUnregistrationReason::JAVASCRIPT_API),
+      1);
 
   // Resolves false if there was no longer a subscription.
   ASSERT_TRUE(RunScript("unsubscribeStoredPushSubscription()", &script_result));
   EXPECT_EQ("unsubscribe result: false", script_result);
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_JAVASCRIPT_API, 2);
+      static_cast<int>(
+          content::mojom::PushUnregistrationReason::JAVASCRIPT_API),
+      2);
 
   // Doesn't reject if there was a network error (deactivates subscription
   // locally anyway).
@@ -1706,7 +1729,9 @@
   EXPECT_EQ("unsubscribe result: true", script_result);
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_JAVASCRIPT_API, 3);
+      static_cast<int>(
+          content::mojom::PushUnregistrationReason::JAVASCRIPT_API),
+      3);
   ASSERT_TRUE(RunScript("hasSubscription()", &script_result));
   EXPECT_EQ("false - not subscribed", script_result);
 
@@ -1722,7 +1747,9 @@
   EXPECT_EQ("unsubscribe result: true", script_result);
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_JAVASCRIPT_API, 4);
+      static_cast<int>(
+          content::mojom::PushUnregistrationReason::JAVASCRIPT_API),
+      4);
 
   // Unsubscribing (with an existing reference to a PushSubscription), after
   // replacing the Service Worker, actually still works, as the Service Worker
@@ -1740,7 +1767,9 @@
   EXPECT_EQ("unsubscribe result: true", script_result);
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_JAVASCRIPT_API, 5);
+      static_cast<int>(
+          content::mojom::PushUnregistrationReason::JAVASCRIPT_API),
+      5);
 
   // Unsubscribing (with an existing reference to a PushSubscription), after
   // unregistering the Service Worker, should fail.
@@ -1764,7 +1793,9 @@
   // Unregistering should have triggered an automatic unsubscribe.
   histogram_tester_.ExpectBucketCount(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_SERVICE_WORKER_UNREGISTERED, 1);
+      static_cast<int>(content::mojom::PushUnregistrationReason::
+                           SERVICE_WORKER_UNREGISTERED),
+      1);
   histogram_tester_.ExpectTotalCount("PushMessaging.UnregistrationReason", 6);
 
   // Now manual unsubscribe should return false.
@@ -1788,7 +1819,9 @@
   EXPECT_EQ("unsubscribe result: true", script_result);
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_JAVASCRIPT_API, 1);
+      static_cast<int>(
+          content::mojom::PushUnregistrationReason::JAVASCRIPT_API),
+      1);
 
   // Since the service is offline, the network request to GCM is still being
   // retried, so the app handler shouldn't have been unregistered yet.
@@ -1820,7 +1853,9 @@
   // This should have unregistered the push subscription.
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_SERVICE_WORKER_UNREGISTERED, 1);
+      static_cast<int>(content::mojom::PushUnregistrationReason::
+                           SERVICE_WORKER_UNREGISTERED),
+      1);
 
   // We should not be able to look up the app id.
   GURL origin = https_server()->GetURL("/").GetOrigin();
@@ -1852,7 +1887,9 @@
   // This should have unregistered the push subscription.
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_SERVICE_WORKER_DATABASE_WIPED, 1);
+      static_cast<int>(content::mojom::PushUnregistrationReason::
+                           SERVICE_WORKER_DATABASE_WIPED),
+      1);
 
   // There should not be any subscriptions left.
   EXPECT_EQ(PushMessagingAppIdentifier::GetCount(GetBrowser()->profile()), 0u);
@@ -1891,7 +1928,9 @@
   // This should have unsubscribed the push subscription.
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_GET_SUBSCRIPTION_STORAGE_CORRUPT, 1);
+      static_cast<int>(content::mojom::PushUnregistrationReason::
+                           GET_SUBSCRIPTION_STORAGE_CORRUPT),
+      1);
   // We should no longer be able to look up the app id.
   PushMessagingAppIdentifier app_identifier3 =
       PushMessagingAppIdentifier::FindByServiceWorker(
@@ -1935,7 +1974,9 @@
   // This should have unsubscribed the original push subscription.
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_SUBSCRIBE_STORAGE_CORRUPT, 1);
+      static_cast<int>(
+          content::mojom::PushUnregistrationReason::SUBSCRIBE_STORAGE_CORRUPT),
+      1);
   // Looking up the app id should return a different id.
   PushMessagingAppIdentifier app_identifier3 =
       PushMessagingAppIdentifier::FindByServiceWorker(
@@ -1975,7 +2016,9 @@
 
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_PERMISSION_REVOKED, 1);
+      static_cast<int>(
+          content::mojom::PushUnregistrationReason::PERMISSION_REVOKED),
+      1);
 }
 
 IN_PROC_BROWSER_TEST_F(PushMessagingBrowserTest,
@@ -2011,7 +2054,9 @@
 
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_PERMISSION_REVOKED, 1);
+      static_cast<int>(
+          content::mojom::PushUnregistrationReason::PERMISSION_REVOKED),
+      1);
 }
 
 IN_PROC_BROWSER_TEST_F(PushMessagingBrowserTest,
@@ -2047,7 +2092,9 @@
 
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_PERMISSION_REVOKED, 1);
+      static_cast<int>(
+          content::mojom::PushUnregistrationReason::PERMISSION_REVOKED),
+      1);
 }
 
 IN_PROC_BROWSER_TEST_F(PushMessagingBrowserTest,
@@ -2080,7 +2127,9 @@
 
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_PERMISSION_REVOKED, 1);
+      static_cast<int>(
+          content::mojom::PushUnregistrationReason::PERMISSION_REVOKED),
+      1);
 }
 
 IN_PROC_BROWSER_TEST_F(PushMessagingBrowserTest,
@@ -2116,7 +2165,9 @@
 
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_PERMISSION_REVOKED, 1);
+      static_cast<int>(
+          content::mojom::PushUnregistrationReason::PERMISSION_REVOKED),
+      1);
 }
 
 IN_PROC_BROWSER_TEST_F(PushMessagingBrowserTest,
@@ -2152,7 +2203,9 @@
 
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_PERMISSION_REVOKED, 1);
+      static_cast<int>(
+          content::mojom::PushUnregistrationReason::PERMISSION_REVOKED),
+      1);
 }
 
 IN_PROC_BROWSER_TEST_F(PushMessagingBrowserTest,
@@ -2273,7 +2326,9 @@
 
   histogram_tester_.ExpectUniqueSample(
       "PushMessaging.UnregistrationReason",
-      content::PUSH_UNREGISTRATION_REASON_PERMISSION_REVOKED, 1);
+      static_cast<int>(
+          content::mojom::PushUnregistrationReason::PERMISSION_REVOKED),
+      1);
 
   base::RunLoop().RunUntilIdle();
 
diff --git a/chrome/browser/push_messaging/push_messaging_notification_manager.cc b/chrome/browser/push_messaging/push_messaging_notification_manager.cc
index b2df4e98..08e8ef7 100644
--- a/chrome/browser/push_messaging/push_messaging_notification_manager.cc
+++ b/chrome/browser/push_messaging/push_messaging_notification_manager.cc
@@ -29,6 +29,7 @@
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/notification_resources.h"
+#include "content/public/common/push_messaging_status.mojom.h"
 #include "content/public/common/url_constants.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 #include "third_party/WebKit/public/platform/modules/budget_service/budget_service.mojom.h"
@@ -54,9 +55,10 @@
 using content::WebContents;
 
 namespace {
-void RecordUserVisibleStatus(content::PushUserVisibleStatus status) {
-  UMA_HISTOGRAM_ENUMERATION("PushMessaging.UserVisibleStatus", status,
-                            content::PUSH_USER_VISIBLE_STATUS_LAST + 1);
+void RecordUserVisibleStatus(content::mojom::PushUserVisibleStatus status) {
+  UMA_HISTOGRAM_ENUMERATION(
+      "PushMessaging.UserVisibleStatus", status,
+      static_cast<int>(content::mojom::PushUserVisibleStatus::LAST) + 1);
 }
 
 content::StoragePartition* GetStoragePartition(Profile* profile,
@@ -201,13 +203,13 @@
 
   if (notification_needed && notification_shown) {
     RecordUserVisibleStatus(
-        content::PUSH_USER_VISIBLE_STATUS_REQUIRED_AND_SHOWN);
+        content::mojom::PushUserVisibleStatus::REQUIRED_AND_SHOWN);
   } else if (!notification_needed && !notification_shown) {
     RecordUserVisibleStatus(
-        content::PUSH_USER_VISIBLE_STATUS_NOT_REQUIRED_AND_NOT_SHOWN);
+        content::mojom::PushUserVisibleStatus::NOT_REQUIRED_AND_NOT_SHOWN);
   } else {
     RecordUserVisibleStatus(
-        content::PUSH_USER_VISIBLE_STATUS_NOT_REQUIRED_BUT_SHOWN);
+        content::mojom::PushUserVisibleStatus::NOT_REQUIRED_BUT_SHOWN);
   }
 
   message_handled_closure.Run();
@@ -255,14 +257,14 @@
 
   // If the origin was allowed to issue a silent push, just return.
   if (silent_push_allowed) {
-    RecordUserVisibleStatus(
-        content::PUSH_USER_VISIBLE_STATUS_REQUIRED_BUT_NOT_SHOWN_USED_GRACE);
+    RecordUserVisibleStatus(content::mojom::PushUserVisibleStatus::
+                                REQUIRED_BUT_NOT_SHOWN_USED_GRACE);
     message_handled_closure.Run();
     return;
   }
 
-  RecordUserVisibleStatus(
-      content::PUSH_USER_VISIBLE_STATUS_REQUIRED_BUT_NOT_SHOWN_GRACE_EXCEEDED);
+  RecordUserVisibleStatus(content::mojom::PushUserVisibleStatus::
+                              REQUIRED_BUT_NOT_SHOWN_GRACE_EXCEEDED);
   rappor::SampleDomainAndRegistryFromGURL(
       g_browser_process->rappor_service(),
       "PushMessaging.GenericNotificationShown.Origin", origin);
diff --git a/chrome/browser/push_messaging/push_messaging_service_impl.cc b/chrome/browser/push_messaging/push_messaging_service_impl.cc
index b1fc84d..b2262302 100644
--- a/chrome/browser/push_messaging/push_messaging_service_impl.cc
+++ b/chrome/browser/push_messaging/push_messaging_service_impl.cc
@@ -54,7 +54,7 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/child_process_host.h"
 #include "content/public/common/content_switches.h"
-#include "content/public/common/push_messaging_status.h"
+#include "content/public/common/push_messaging_status.mojom.h"
 #include "content/public/common/push_subscription_options.h"
 #include "ui/base/l10n/l10n_util.h"
 
@@ -82,14 +82,16 @@
     "pushManager.subscribe({userVisibleOnly: true}) instead. See "
     "https://goo.gl/yqv4Q4 for more details.";
 
-void RecordDeliveryStatus(content::PushDeliveryStatus status) {
-  UMA_HISTOGRAM_ENUMERATION("PushMessaging.DeliveryStatus", status,
-                            content::PUSH_DELIVERY_STATUS_LAST + 1);
+void RecordDeliveryStatus(content::mojom::PushDeliveryStatus status) {
+  UMA_HISTOGRAM_ENUMERATION(
+      "PushMessaging.DeliveryStatus", status,
+      static_cast<int>(content::mojom::PushDeliveryStatus::LAST) + 1);
 }
 
-void RecordUnsubscribeReason(content::PushUnregistrationReason reason) {
-  UMA_HISTOGRAM_ENUMERATION("PushMessaging.UnregistrationReason", reason,
-                            content::PUSH_UNREGISTRATION_REASON_LAST + 1);
+void RecordUnsubscribeReason(content::mojom::PushUnregistrationReason reason) {
+  UMA_HISTOGRAM_ENUMERATION(
+      "PushMessaging.UnregistrationReason", reason,
+      static_cast<int>(content::mojom::PushUnregistrationReason::LAST) + 1);
 }
 
 void RecordUnsubscribeGCMResult(gcm::GCMClient::Result result) {
@@ -118,8 +120,9 @@
   return blink::kWebPushPermissionStatusDenied;
 }
 
-void UnregisterCallbackToClosure(const base::Closure& closure,
-                                 content::PushUnregistrationStatus status) {
+void UnregisterCallbackToClosure(
+    const base::Closure& closure,
+    content::mojom::PushUnregistrationStatus status) {
   DCHECK(!closure.is_null());
   closure.Run();
 }
@@ -227,7 +230,7 @@
   // Delete all cached subscriptions, since they are now invalid.
   for (const auto& identifier : PushMessagingAppIdentifier::GetAll(profile_)) {
     RecordUnsubscribeReason(
-        content::PUSH_UNREGISTRATION_REASON_GCM_STORE_RESET);
+        content::mojom::PushUnregistrationReason::GCM_STORE_RESET);
     // Clear all the subscriptions in parallel, to reduce risk that shutdown
     // occurs before we finish clearing them.
     ClearPushSubscriptionId(profile_, identifier.origin(),
@@ -274,15 +277,16 @@
     DeliverMessageCallback(app_id, GURL::EmptyGURL(),
                            -1 /* kInvalidServiceWorkerRegistrationId */,
                            message, message_handled_closure,
-                           content::PUSH_DELIVERY_STATUS_UNKNOWN_APP_ID);
+                           content::mojom::PushDeliveryStatus::UNKNOWN_APP_ID);
     return;
   }
   // Drop message and unregister if |origin| has lost push permission.
   if (!IsPermissionSet(app_identifier.origin())) {
-    DeliverMessageCallback(app_id, app_identifier.origin(),
-                           app_identifier.service_worker_registration_id(),
-                           message, message_handled_closure,
-                           content::PUSH_DELIVERY_STATUS_PERMISSION_DENIED);
+    DeliverMessageCallback(
+        app_id, app_identifier.origin(),
+        app_identifier.service_worker_registration_id(), message,
+        message_handled_closure,
+        content::mojom::PushDeliveryStatus::PERMISSION_DENIED);
     return;
   }
 
@@ -320,7 +324,7 @@
     int64_t service_worker_registration_id,
     const gcm::IncomingMessage& message,
     const base::Closure& message_handled_closure,
-    content::PushDeliveryStatus status) {
+    content::mojom::PushDeliveryStatus status) {
   DCHECK_GE(in_flight_message_deliveries_.count(app_id), 1u);
 
   RecordDeliveryStatus(status);
@@ -333,8 +337,8 @@
   base::ScopedClosureRunner completion_closure_runner(completion_closure);
 
   // A reason to automatically unsubscribe. UNKNOWN means do not unsubscribe.
-  content::PushUnregistrationReason unsubscribe_reason =
-      content::PUSH_UNREGISTRATION_REASON_UNKNOWN;
+  content::mojom::PushUnregistrationReason unsubscribe_reason =
+      content::mojom::PushUnregistrationReason::UNKNOWN;
 
   // TODO(mvanouwerkerk): Show a warning in the developer console of the
   // Service Worker corresponding to app_id (and/or on an internals page).
@@ -344,9 +348,9 @@
     // the Service Worker JavaScript, even if the website's event handler failed
     // (to prevent sites deliberately failing in order to avoid having to show
     // notifications).
-    case content::PUSH_DELIVERY_STATUS_SUCCESS:
-    case content::PUSH_DELIVERY_STATUS_EVENT_WAITUNTIL_REJECTED:
-    case content::PUSH_DELIVERY_STATUS_TIMEOUT:
+    case content::mojom::PushDeliveryStatus::SUCCESS:
+    case content::mojom::PushDeliveryStatus::EVENT_WAITUNTIL_REJECTED:
+    case content::mojom::PushDeliveryStatus::TIMEOUT:
       // Only enforce the user visible requirements if this is currently running
       // as the delivery callback for the last in-flight message, and silent
       // push has not been enabled through a command line flag.
@@ -358,24 +362,24 @@
             completion_closure_runner.Release());
       }
       break;
-    case content::PUSH_DELIVERY_STATUS_SERVICE_WORKER_ERROR:
+    case content::mojom::PushDeliveryStatus::SERVICE_WORKER_ERROR:
       // Do nothing, and hope the error is transient.
       break;
-    case content::PUSH_DELIVERY_STATUS_UNKNOWN_APP_ID:
+    case content::mojom::PushDeliveryStatus::UNKNOWN_APP_ID:
       unsubscribe_reason =
-          content::PUSH_UNREGISTRATION_REASON_DELIVERY_UNKNOWN_APP_ID;
+          content::mojom::PushUnregistrationReason::DELIVERY_UNKNOWN_APP_ID;
       break;
-    case content::PUSH_DELIVERY_STATUS_PERMISSION_DENIED:
+    case content::mojom::PushDeliveryStatus::PERMISSION_DENIED:
       unsubscribe_reason =
-          content::PUSH_UNREGISTRATION_REASON_DELIVERY_PERMISSION_DENIED;
+          content::mojom::PushUnregistrationReason::DELIVERY_PERMISSION_DENIED;
       break;
-    case content::PUSH_DELIVERY_STATUS_NO_SERVICE_WORKER:
+    case content::mojom::PushDeliveryStatus::NO_SERVICE_WORKER:
       unsubscribe_reason =
-          content::PUSH_UNREGISTRATION_REASON_DELIVERY_NO_SERVICE_WORKER;
+          content::mojom::PushUnregistrationReason::DELIVERY_NO_SERVICE_WORKER;
       break;
   }
 
-  if (unsubscribe_reason != content::PUSH_UNREGISTRATION_REASON_UNKNOWN) {
+  if (unsubscribe_reason != content::mojom::PushUnregistrationReason::UNKNOWN) {
     PushMessagingAppIdentifier app_identifier =
         PushMessagingAppIdentifier::FindByAppId(profile_, app_id);
     UnsubscribeInternal(
@@ -459,8 +463,8 @@
 
   if (push_subscription_count_ + pending_push_subscription_count_ >=
       kMaxRegistrations) {
-    SubscribeEndWithError(callback,
-                          content::PUSH_REGISTRATION_STATUS_LIMIT_REACHED);
+    SubscribeEndWithError(
+        callback, content::mojom::PushRegistrationStatus::LIMIT_REACHED);
     return;
   }
 
@@ -475,8 +479,8 @@
     web_contents->GetMainFrame()->AddMessageToConsole(
         content::CONSOLE_MESSAGE_LEVEL_ERROR, kSilentPushUnsupportedMessage);
 
-    SubscribeEndWithError(callback,
-                          content::PUSH_REGISTRATION_STATUS_PERMISSION_DENIED);
+    SubscribeEndWithError(
+        callback, content::mojom::PushRegistrationStatus::PERMISSION_DENIED);
     return;
   }
 
@@ -500,8 +504,9 @@
 
   if (push_subscription_count_ + pending_push_subscription_count_ >=
       kMaxRegistrations) {
-    SubscribeEndWithError(register_callback,
-                          content::PUSH_REGISTRATION_STATUS_LIMIT_REACHED);
+    SubscribeEndWithError(
+        register_callback,
+        content::mojom::PushRegistrationStatus::LIMIT_REACHED);
     return;
   }
 
@@ -509,8 +514,9 @@
       GetPermissionStatus(requesting_origin, options.user_visible_only);
 
   if (permission_status != blink::kWebPushPermissionStatusGranted) {
-    SubscribeEndWithError(register_callback,
-                          content::PUSH_REGISTRATION_STATUS_PERMISSION_DENIED);
+    SubscribeEndWithError(
+        register_callback,
+        content::mojom::PushRegistrationStatus::PERMISSION_DENIED);
     return;
   }
 
@@ -544,8 +550,9 @@
     const RegisterCallback& register_callback,
     ContentSetting content_setting) {
   if (content_setting != CONTENT_SETTING_ALLOW) {
-    SubscribeEndWithError(register_callback,
-                          content::PUSH_REGISTRATION_STATUS_PERMISSION_DENIED);
+    SubscribeEndWithError(
+        register_callback,
+        content::mojom::PushRegistrationStatus::PERMISSION_DENIED);
     return;
   }
 
@@ -565,13 +572,13 @@
     const std::string& subscription_id,
     const std::vector<uint8_t>& p256dh,
     const std::vector<uint8_t>& auth,
-    content::PushRegistrationStatus status) {
+    content::mojom::PushRegistrationStatus status) {
   callback.Run(subscription_id, p256dh, auth, status);
 }
 
 void PushMessagingServiceImpl::SubscribeEndWithError(
     const RegisterCallback& callback,
-    content::PushRegistrationStatus status) {
+    content::mojom::PushRegistrationStatus status) {
   SubscribeEnd(callback, std::string() /* subscription_id */,
                std::vector<uint8_t>() /* p256dh */,
                std::vector<uint8_t>() /* auth */, status);
@@ -585,8 +592,8 @@
     InstanceID::Result result) {
   DecreasePushSubscriptionCount(1, true /* was_pending */);
 
-  content::PushRegistrationStatus status =
-      content::PUSH_REGISTRATION_STATUS_SERVICE_ERROR;
+  content::mojom::PushRegistrationStatus status =
+      content::mojom::PushRegistrationStatus::SERVICE_ERROR;
 
   switch (result) {
     case InstanceID::SUCCESS:
@@ -606,10 +613,10 @@
     case InstanceID::UNKNOWN_ERROR:
       DLOG(ERROR) << "Push messaging subscription failed; InstanceID::Result = "
                   << result;
-      status = content::PUSH_REGISTRATION_STATUS_SERVICE_ERROR;
+      status = content::mojom::PushRegistrationStatus::SERVICE_ERROR;
       break;
     case InstanceID::NETWORK_ERROR:
-      status = content::PUSH_REGISTRATION_STATUS_NETWORK_ERROR;
+      status = content::mojom::PushRegistrationStatus::NETWORK_ERROR;
       break;
   }
 
@@ -624,7 +631,8 @@
     const std::string& auth_secret) {
   if (p256dh.empty()) {
     SubscribeEndWithError(
-        callback, content::PUSH_REGISTRATION_STATUS_PUBLIC_KEY_UNAVAILABLE);
+        callback,
+        content::mojom::PushRegistrationStatus::PUBLIC_KEY_UNAVAILABLE);
     return;
   }
 
@@ -632,10 +640,11 @@
 
   IncreasePushSubscriptionCount(1, false /* is_pending */);
 
-  SubscribeEnd(callback, subscription_id,
-               std::vector<uint8_t>(p256dh.begin(), p256dh.end()),
-               std::vector<uint8_t>(auth_secret.begin(), auth_secret.end()),
-               content::PUSH_REGISTRATION_STATUS_SUCCESS_FROM_PUSH_SERVICE);
+  SubscribeEnd(
+      callback, subscription_id,
+      std::vector<uint8_t>(p256dh.begin(), p256dh.end()),
+      std::vector<uint8_t>(auth_secret.begin(), auth_secret.end()),
+      content::mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE);
 }
 
 // GetSubscriptionInfo methods -------------------------------------------------
@@ -701,7 +710,7 @@
 // Unsubscribe methods ---------------------------------------------------------
 
 void PushMessagingServiceImpl::Unsubscribe(
-    content::PushUnregistrationReason reason,
+    content::mojom::PushUnregistrationReason reason,
     const GURL& requesting_origin,
     int64_t service_worker_registration_id,
     const std::string& sender_id,
@@ -717,7 +726,7 @@
 }
 
 void PushMessagingServiceImpl::UnsubscribeInternal(
-    content::PushUnregistrationReason reason,
+    content::mojom::PushUnregistrationReason reason,
     const GURL& origin,
     int64_t service_worker_registration_id,
     const std::string& app_id,
@@ -745,7 +754,7 @@
 }
 
 void PushMessagingServiceImpl::DidClearPushSubscriptionId(
-    content::PushUnregistrationReason reason,
+    content::mojom::PushUnregistrationReason reason,
     const std::string& app_id,
     const std::string& sender_id,
     const UnregisterCallback& callback) {
@@ -753,7 +762,7 @@
     // Without an |app_id|, we can neither delete the subscription from the
     // PushMessagingAppIdentifier map, nor unsubscribe with the GCM Driver.
     callback.Run(
-        content::PUSH_UNREGISTRATION_STATUS_SUCCESS_WAS_NOT_REGISTERED);
+        content::mojom::PushUnregistrationStatus::SUCCESS_WAS_NOT_REGISTERED);
     return;
   }
 
@@ -776,8 +785,9 @@
   // eventually reach GCM servers even if this particular attempt fails.
   callback.Run(
       was_subscribed
-          ? content::PUSH_UNREGISTRATION_STATUS_SUCCESS_UNREGISTERED
-          : content::PUSH_UNREGISTRATION_STATUS_SUCCESS_WAS_NOT_REGISTERED);
+          ? content::mojom::PushUnregistrationStatus::SUCCESS_UNREGISTERED
+          : content::mojom::PushUnregistrationStatus::
+                SUCCESS_WAS_NOT_REGISTERED);
 
   if (PushMessagingAppIdentifier::UseInstanceID(app_id)) {
     GetInstanceIDDriver()->GetInstanceID(app_id)->DeleteID(
@@ -859,8 +869,8 @@
   // Android from GCM, as that requires a sender_id. (Ideally we'd fetch it
   // from the SWDB in some "before_unregistered" SWObserver event.)
   UnsubscribeInternal(
-      content::PUSH_UNREGISTRATION_REASON_SERVICE_WORKER_UNREGISTERED, origin,
-      service_worker_registration_id, app_identifier.app_id(),
+      content::mojom::PushUnregistrationReason::SERVICE_WORKER_UNREGISTERED,
+      origin, service_worker_registration_id, app_identifier.app_id(),
       std::string() /* sender_id */,
       base::Bind(&UnregisterCallbackToClosure,
                  service_worker_unregistered_callback_for_testing_.is_null()
@@ -890,7 +900,7 @@
     // Android from GCM, as that requires a sender_id. We can't fetch those from
     // the Service Worker database anymore as it's been deleted.
     UnsubscribeInternal(
-        content::PUSH_UNREGISTRATION_REASON_SERVICE_WORKER_DATABASE_WIPED,
+        content::mojom::PushUnregistrationReason::SERVICE_WORKER_DATABASE_WIPED,
         app_identifier.origin(),
         app_identifier.service_worker_registration_id(),
         app_identifier.app_id(), std::string() /* sender_id */,
@@ -954,7 +964,7 @@
               base::Bind(&UnregisterCallbackToClosure, barrier_closure)));
     } else {
       UnsubscribeInternal(
-          content::PUSH_UNREGISTRATION_REASON_PERMISSION_REVOKED,
+          content::mojom::PushUnregistrationReason::PERMISSION_REVOKED,
           app_identifier.origin(),
           app_identifier.service_worker_registration_id(),
           app_identifier.app_id(), std::string() /* sender_id */,
@@ -976,10 +986,10 @@
   // Android, Unsubscribe will just delete the app identifier to block future
   // messages.
   // TODO(johnme): Auto-unregister before SW DB is cleared (crbug.com/402458).
-  UnsubscribeInternal(content::PUSH_UNREGISTRATION_REASON_PERMISSION_REVOKED,
-                      app_identifier.origin(),
-                      app_identifier.service_worker_registration_id(),
-                      app_identifier.app_id(), sender_id, callback);
+  UnsubscribeInternal(
+      content::mojom::PushUnregistrationReason::PERMISSION_REVOKED,
+      app_identifier.origin(), app_identifier.service_worker_registration_id(),
+      app_identifier.app_id(), sender_id, callback);
 }
 
 void PushMessagingServiceImpl::SetContentSettingChangedCallbackForTesting(
diff --git a/chrome/browser/push_messaging/push_messaging_service_impl.h b/chrome/browser/push_messaging/push_messaging_service_impl.h
index 27c59cfa..a033285 100644
--- a/chrome/browser/push_messaging/push_messaging_service_impl.h
+++ b/chrome/browser/push_messaging/push_messaging_service_impl.h
@@ -31,7 +31,6 @@
 #include "content/public/browser/notification_registrar.h"
 #include "content/public/browser/push_messaging_service.h"
 #include "content/public/common/push_event_payload.h"
-#include "content/public/common/push_messaging_status.h"
 #include "third_party/WebKit/public/platform/modules/permissions/permission_status.mojom.h"
 #include "third_party/WebKit/public/platform/modules/push_messaging/WebPushPermissionStatus.h"
 
@@ -42,6 +41,13 @@
 class ScopedKeepAlive;
 struct PushSubscriptionOptions;
 
+namespace content {
+namespace mojom {
+enum class PushDeliveryStatus;
+enum class PushRegistrationStatus;
+}  // namespace mojom
+}  // namespace content
+
 namespace gcm {
 class GCMDriver;
 }
@@ -93,7 +99,7 @@
                            const std::string& sender_id,
                            const std::string& subscription_id,
                            const SubscriptionInfoCallback& callback) override;
-  void Unsubscribe(content::PushUnregistrationReason reason,
+  void Unsubscribe(content::mojom::PushUnregistrationReason reason,
                    const GURL& requesting_origin,
                    int64_t service_worker_registration_id,
                    const std::string& sender_id,
@@ -151,7 +157,7 @@
                               int64_t service_worker_registration_id,
                               const gcm::IncomingMessage& message,
                               const base::Closure& message_handled_closure,
-                              content::PushDeliveryStatus status);
+                              content::mojom::PushDeliveryStatus status);
 
   void DidHandleMessage(const std::string& app_id,
                         const base::Closure& completion_closure);
@@ -167,10 +173,10 @@
                     const std::string& subscription_id,
                     const std::vector<uint8_t>& p256dh,
                     const std::vector<uint8_t>& auth,
-                    content::PushRegistrationStatus status);
+                    content::mojom::PushRegistrationStatus status);
 
   void SubscribeEndWithError(const RegisterCallback& callback,
-                             content::PushRegistrationStatus status);
+                             content::mojom::PushRegistrationStatus status);
 
   void DidSubscribe(const PushMessagingAppIdentifier& app_identifier,
                     const std::string& sender_id,
@@ -202,17 +208,18 @@
   // whenever they can be obtained. It's valid for |origin| to be empty and
   // |service_worker_registration_id| to be kInvalidServiceWorkerRegistrationId,
   // or for app_id to be empty, but not both at once.
-  void UnsubscribeInternal(content::PushUnregistrationReason reason,
+  void UnsubscribeInternal(content::mojom::PushUnregistrationReason reason,
                            const GURL& origin,
                            int64_t service_worker_registration_id,
                            const std::string& app_id,
                            const std::string& sender_id,
                            const UnregisterCallback& callback);
 
-  void DidClearPushSubscriptionId(content::PushUnregistrationReason reason,
-                                  const std::string& app_id,
-                                  const std::string& sender_id,
-                                  const UnregisterCallback& callback);
+  void DidClearPushSubscriptionId(
+      content::mojom::PushUnregistrationReason reason,
+      const std::string& app_id,
+      const std::string& sender_id,
+      const UnregisterCallback& callback);
 
   void DidUnregister(bool was_subscribed, gcm::GCMClient::Result result);
   void DidDeleteID(const std::string& app_id,
diff --git a/chrome/browser/push_messaging/push_messaging_service_unittest.cc b/chrome/browser/push_messaging/push_messaging_service_unittest.cc
index ecb1026..b3abca7 100644
--- a/chrome/browser/push_messaging/push_messaging_service_unittest.cc
+++ b/chrome/browser/push_messaging/push_messaging_service_unittest.cc
@@ -27,6 +27,7 @@
 #include "components/gcm_driver/fake_gcm_client_factory.h"
 #include "components/gcm_driver/gcm_profile_service.h"
 #include "content/public/common/push_event_payload.h"
+#include "content/public/common/push_messaging_status.mojom.h"
 #include "content/public/common/push_subscription_options.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -110,8 +111,8 @@
                    const std::string& registration_id,
                    const std::vector<uint8_t>& p256dh,
                    const std::vector<uint8_t>& auth,
-                   content::PushRegistrationStatus status) {
-    EXPECT_EQ(content::PUSH_REGISTRATION_STATUS_SUCCESS_FROM_PUSH_SERVICE,
+                   content::mojom::PushRegistrationStatus status) {
+    EXPECT_EQ(content::mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE,
               status);
 
     *subscription_id_out = registration_id;
diff --git a/chrome/browser/safe_browsing/download_protection_service.cc b/chrome/browser/safe_browsing/download_protection_service.cc
index 822e3353..a8400aaf 100644
--- a/chrome/browser/safe_browsing/download_protection_service.cc
+++ b/chrome/browser/safe_browsing/download_protection_service.cc
@@ -362,6 +362,9 @@
         tab_referrer_url_(item->GetTabReferrerUrl()),
         archived_executable_(false),
         archive_is_valid_(ArchiveValid::UNSET),
+#if defined(OS_MACOSX)
+        disk_image_signature_(nullptr),
+#endif
         callback_(callback),
         service_(service),
         binary_feature_extractor_(binary_feature_extractor),
@@ -826,6 +829,11 @@
     if (!service_)
       return;
 
+    if (results.signature_blob.size() > 0) {
+      disk_image_signature_ =
+          base::MakeUnique<std::vector<uint8_t>>(results.signature_blob);
+    }
+
     // Even if !results.success, some of the DMG may have been parsed.
     archive_is_valid_ =
         (results.success ? ArchiveValid::VALID : ArchiveValid::INVALID);
@@ -1071,6 +1079,18 @@
             request.mutable_referrer_chain());
     }
 
+#if defined(OS_MACOSX)
+    UMA_HISTOGRAM_BOOLEAN(
+        "SBClientDownload."
+        "DownloadFileHasDmgSignature",
+        disk_image_signature_ != nullptr);
+
+    if (disk_image_signature_) {
+      request.set_udif_code_signature(disk_image_signature_->data(),
+                                      disk_image_signature_->size());
+    }
+#endif
+
     if (archive_is_valid_ != ArchiveValid::UNSET)
       request.set_archive_valid(archive_is_valid_ == ArchiveValid::VALID);
     request.mutable_signature()->CopyFrom(signature_info_);
@@ -1266,6 +1286,10 @@
   bool archived_executable_;
   ArchiveValid archive_is_valid_;
 
+#if defined(OS_MACOSX)
+  std::unique_ptr<std::vector<uint8_t>> disk_image_signature_;
+#endif
+
   ClientDownloadRequest_SignatureInfo signature_info_;
   std::unique_ptr<ClientDownloadRequest_ImageHeaders> image_headers_;
   google::protobuf::RepeatedPtrField<ClientDownloadRequest_ArchivedBinary>
diff --git a/chrome/browser/safe_browsing/download_protection_service_unittest.cc b/chrome/browser/safe_browsing/download_protection_service_unittest.cc
index 0976d6e..3cfa40e 100644
--- a/chrome/browser/safe_browsing/download_protection_service_unittest.cc
+++ b/chrome/browser/safe_browsing/download_protection_service_unittest.cc
@@ -1448,6 +1448,88 @@
   CheckClientDownloadReportCorruptArchive(DMG);
 }
 
+// Tests that signatures get recorded and uploaded for signed DMGs.
+TEST_F(DownloadProtectionServiceTest,
+       CheckClientDownloadReportDmgWithSignature) {
+  net::FakeURLFetcherFactory factory(NULL);
+  PrepareResponse(&factory, ClientDownloadResponse::SAFE, net::HTTP_OK,
+                  net::URLRequestStatus::SUCCESS);
+
+  base::FilePath signed_dmg;
+  EXPECT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &signed_dmg));
+  signed_dmg = signed_dmg.AppendASCII("safe_browsing")
+                   .AppendASCII("mach_o")
+                   .AppendASCII("signed-archive.dmg");
+
+  NiceMockDownloadItem item;
+  PrepareBasicDownloadItemWithFullPaths(
+      &item, {"http://www.evil.com/a.dmg"},                     // url_chain
+      "http://www.google.com/",                                 // referrer
+      signed_dmg,                                               // tmp_path
+      temp_dir_.GetPath().Append(FILE_PATH_LITERAL("a.dmg")));  // final_path
+
+  RunLoop run_loop;
+  download_service_->CheckClientDownload(
+      &item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
+                        base::Unretained(this), run_loop.QuitClosure()));
+  run_loop.Run();
+
+  ASSERT_TRUE(HasClientDownloadRequest());
+  EXPECT_TRUE(GetClientDownloadRequest()->has_udif_code_signature());
+  EXPECT_EQ(2215u, GetClientDownloadRequest()->udif_code_signature().length());
+
+  base::FilePath signed_dmg_signature;
+  EXPECT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &signed_dmg_signature));
+  signed_dmg_signature = signed_dmg_signature.AppendASCII("safe_browsing")
+                             .AppendASCII("mach_o")
+                             .AppendASCII("signed-archive-signature.data");
+
+  std::string signature;
+  base::ReadFileToString(signed_dmg_signature, &signature);
+  EXPECT_EQ(2215u, signature.length());
+  EXPECT_EQ(signature, GetClientDownloadRequest()->udif_code_signature());
+
+  ClearClientDownloadRequest();
+
+  Mock::VerifyAndClearExpectations(sb_service_.get());
+  Mock::VerifyAndClearExpectations(binary_feature_extractor_.get());
+}
+
+// Tests that no signature gets recorded and uploaded for unsigned DMGs.
+TEST_F(DownloadProtectionServiceTest,
+       CheckClientDownloadReportDmgWithoutSignature) {
+  net::FakeURLFetcherFactory factory(NULL);
+  PrepareResponse(&factory, ClientDownloadResponse::SAFE, net::HTTP_OK,
+                  net::URLRequestStatus::SUCCESS);
+
+  base::FilePath unsigned_dmg;
+  EXPECT_TRUE(PathService::Get(chrome::DIR_GEN_TEST_DATA, &unsigned_dmg));
+  unsigned_dmg = unsigned_dmg.AppendASCII("chrome")
+                     .AppendASCII("safe_browsing_dmg")
+                     .AppendASCII("mach_o_in_dmg.dmg");
+
+  NiceMockDownloadItem item;
+  PrepareBasicDownloadItemWithFullPaths(
+      &item, {"http://www.evil.com/a.dmg"},                     // url_chain
+      "http://www.google.com/",                                 // referrer
+      unsigned_dmg,                                             // tmp_path
+      temp_dir_.GetPath().Append(FILE_PATH_LITERAL("a.dmg")));  // final_path
+
+  RunLoop run_loop;
+  download_service_->CheckClientDownload(
+      &item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
+                        base::Unretained(this), run_loop.QuitClosure()));
+  run_loop.Run();
+
+  ASSERT_TRUE(HasClientDownloadRequest());
+  EXPECT_FALSE(GetClientDownloadRequest()->has_udif_code_signature());
+
+  ClearClientDownloadRequest();
+
+  Mock::VerifyAndClearExpectations(sb_service_.get());
+  Mock::VerifyAndClearExpectations(binary_feature_extractor_.get());
+}
+
 // Test that downloaded files with no disk image extension that have a 'koly'
 // trailer are treated as disk images and processed accordingly.
 TEST_F(DownloadProtectionServiceTest,
diff --git a/chrome/browser/safe_browsing/sandboxed_dmg_analyzer_mac_unittest.cc b/chrome/browser/safe_browsing/sandboxed_dmg_analyzer_mac_unittest.cc
index bbf91ba..8e8dc81e 100644
--- a/chrome/browser/safe_browsing/sandboxed_dmg_analyzer_mac_unittest.cc
+++ b/chrome/browser/safe_browsing/sandboxed_dmg_analyzer_mac_unittest.cc
@@ -9,6 +9,7 @@
 
 #include "base/bind.h"
 #include "base/files/file_path.h"
+#include "base/files/file_util.h"
 #include "base/macros.h"
 #include "base/path_service.h"
 #include "base/run_loop.h"
@@ -141,5 +142,44 @@
   EXPECT_TRUE(got_dylib);
 }
 
+TEST_F(SandboxedDMGAnalyzerTest, AnalyzeDmgNoSignature) {
+  base::FilePath unsigned_dmg;
+  ASSERT_NO_FATAL_FAILURE(unsigned_dmg = GetFilePath("mach_o_in_dmg.dmg"));
+
+  ArchiveAnalyzerResults results;
+  AnalyzeFile(unsigned_dmg, &results);
+
+  EXPECT_TRUE(results.success);
+  EXPECT_EQ(0u, results.signature_blob.size());
+  EXPECT_EQ(nullptr, results.signature_blob.data());
+}
+
+TEST_F(SandboxedDMGAnalyzerTest, AnalyzeDmgWithSignature) {
+  base::FilePath signed_dmg;
+  EXPECT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &signed_dmg));
+  signed_dmg = signed_dmg.AppendASCII("safe_browsing")
+                   .AppendASCII("mach_o")
+                   .AppendASCII("signed-archive.dmg");
+
+  ArchiveAnalyzerResults results;
+  AnalyzeFile(signed_dmg, &results);
+
+  EXPECT_TRUE(results.success);
+  EXPECT_EQ(2215u, results.signature_blob.size());
+
+  base::FilePath signed_dmg_signature;
+  EXPECT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &signed_dmg_signature));
+  signed_dmg_signature = signed_dmg_signature.AppendASCII("safe_browsing")
+                             .AppendASCII("mach_o")
+                             .AppendASCII("signed-archive-signature.data");
+
+  std::string from_file;
+  base::ReadFileToString(signed_dmg_signature, &from_file);
+  EXPECT_EQ(2215u, from_file.length());
+  std::string signature(results.signature_blob.begin(),
+                        results.signature_blob.end());
+  EXPECT_EQ(from_file, signature);
+}
+
 }  // namespace
 }  // namespace safe_browsing
diff --git a/chrome/browser/sessions/session_restore.cc b/chrome/browser/sessions/session_restore.cc
index 05a2a101..b8bf25a4 100644
--- a/chrome/browser/sessions/session_restore.cc
+++ b/chrome/browser/sessions/session_restore.cc
@@ -277,10 +277,6 @@
 
   Profile* profile() { return profile_; }
 
-  void AddURLsToOpen(const std::vector<GURL>& urls) {
-    urls_to_open_.insert(urls_to_open_.end(), urls.begin(), urls.end());
-  }
-
  private:
   // Invoked when done with creating all the tabs/browsers.
   //
@@ -853,24 +849,6 @@
 }
 
 // static
-void SessionRestore::AddURLsToOpen(const Profile* profile,
-                                   const std::vector<GURL>& urls) {
-  // TODO(eugenebng@yandex-team.ru): crbug/735958 fix callers to not
-  // call this without session restorers, or reword the NOTREACHED
-  // assertion to explain why it is OK ignore URLs to open when there
-  // are no active session restorers.
-  if (!active_session_restorers)
-    return;
-  for (auto* session_restorer : *active_session_restorers) {
-    if (session_restorer->profile() == profile) {
-      session_restorer->AddURLsToOpen(urls);
-      return;
-    }
-  }
-  NOTREACHED() << "Failed to add urls to open for session restore";
-}
-
-// static
 void SessionRestore::AddObserver(SessionRestoreObserver* observer) {
   observers().AddObserver(observer);
 }
diff --git a/chrome/browser/sessions/session_restore.h b/chrome/browser/sessions/session_restore.h
index 10dabff1..b468b25 100644
--- a/chrome/browser/sessions/session_restore.h
+++ b/chrome/browser/sessions/session_restore.h
@@ -103,11 +103,6 @@
   static CallbackSubscription RegisterOnSessionRestoredCallback(
       const base::Callback<void(int)>& callback);
 
-  // Add |urls| to URLs-to-open list, so that they will be opened after session
-  // URLs when session is restored.
-  static void AddURLsToOpen(const Profile* profile,
-                            const std::vector<GURL>& urls);
-
   // Add/remove an observer to/from this session restore.
   static void AddObserver(SessionRestoreObserver* observer);
   static void RemoveObserver(SessionRestoreObserver* observer);
diff --git a/chrome/browser/sessions/session_restore_browsertest.cc b/chrome/browser/sessions/session_restore_browsertest.cc
index 71d95be4..00f938d 100644
--- a/chrome/browser/sessions/session_restore_browsertest.cc
+++ b/chrome/browser/sessions/session_restore_browsertest.cc
@@ -4,7 +4,6 @@
 
 #include <stddef.h>
 
-#include <algorithm>
 #include <set>
 #include <vector>
 
@@ -209,9 +208,6 @@
   GURL url3_;
 
   const BrowserList* active_browser_list_;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(SessionRestoreTest);
 };
 
 // Activates the smart restore behaviour and tracks the loading of tabs.
@@ -1623,48 +1619,3 @@
     }
   }
 }
-
-namespace {
-
-const char kStartupURL[] = "http://example.com/";
-const char kSessionURL[] = "http://localhost/";
-
-}  // namespace
-
-class SessionRestoreWithStartupURLTest : public SessionRestoreTest {
- public:
-  SessionRestoreWithStartupURLTest() {}
-
- protected:
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    // Add startup URL to command line, but only in main test body in order
-    // to prevent that URL from beng opened twice.
-    if (!base::StartsWith(
-            testing::UnitTest::GetInstance()->current_test_info()->name(),
-            "PRE_", base::CompareCase::SENSITIVE)) {
-      command_line->AppendArg(kStartupURL);
-    }
-    SessionRestoreTest::SetUpCommandLine(command_line);
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(SessionRestoreWithStartupURLTest);
-};
-
-IN_PROC_BROWSER_TEST_F(SessionRestoreWithStartupURLTest,
-                       PRE_StartupURLsWithSessionRestore) {
-  // Prepare a session to restore in main test body.
-  ui_test_utils::NavigateToURL(browser(), GURL(kSessionURL));
-}
-
-IN_PROC_BROWSER_TEST_F(SessionRestoreWithStartupURLTest,
-                       StartupURLsWithSessionRestore) {
-  // Check that browser is started with restored session and
-  // tab with startup URL next to it.
-  TabStripModel* tab_strip = browser()->tab_strip_model();
-  ASSERT_EQ(2, tab_strip->count());
-  EXPECT_EQ(kSessionURL,
-            tab_strip->GetWebContentsAt(0)->GetURL().possibly_invalid_spec());
-  EXPECT_EQ(kStartupURL,
-            tab_strip->GetWebContentsAt(1)->GetURL().possibly_invalid_spec());
-}
diff --git a/chrome/browser/signin/dice_browsertest.cc b/chrome/browser/signin/dice_browsertest.cc
index 3c327e0e..e527d8bb2 100644
--- a/chrome/browser/signin/dice_browsertest.cc
+++ b/chrome/browser/signin/dice_browsertest.cc
@@ -57,6 +57,7 @@
 const char kMainEmail[] = "main_email@example.com";
 const char kMainGaiaID[] = "main_gaia_id";
 const char kOAuth2TokenExchangeURL[] = "/oauth2/v4/token";
+const char kOAuth2TokenRevokeURL[] = "/o/oauth2/revoke";
 const char kSecondaryEmail[] = "secondary_email@example.com";
 const char kSecondaryGaiaID[] = "secondary_gaia_id";
 const char kSigninURL[] = "/signin";
@@ -152,7 +153,7 @@
   std::string content =
       "{"
       "  \"access_token\":\"access_token\","
-      "  \"refresh_token\":\"refresh_token\","
+      "  \"refresh_token\":\"new_refresh_token\","
       "  \"expires_in\":9999"
       "}";
 
@@ -162,6 +163,20 @@
   return std::move(http_response);
 }
 
+// Handler for OAuth2 token revocation.
+std::unique_ptr<HttpResponse> HandleOAuth2TokenRevokeURL(
+    int* out_token_revoked_count,
+    const HttpRequest& request) {
+  if (!net::test_server::ShouldHandle(request, kOAuth2TokenRevokeURL))
+    return nullptr;
+
+  ++(*out_token_revoked_count);
+
+  std::unique_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+  http_response->AddCustomHeader("Cache-Control", "no-store");
+  return std::move(http_response);
+}
+
 }  // namespace FakeGaia
 
 class DiceBrowserTest : public InProcessBrowserTest,
@@ -172,13 +187,17 @@
   DiceBrowserTest()
       : https_server_(net::EmbeddedTestServer::TYPE_HTTPS),
         token_requested_(false),
-        refresh_token_available_(false) {
+        refresh_token_available_(false),
+        token_revoked_notification_count_(0),
+        token_revoked_count_(0) {
     https_server_.RegisterDefaultHandler(
         base::Bind(&FakeGaia::HandleSigninURL));
     https_server_.RegisterDefaultHandler(
         base::Bind(&FakeGaia::HandleSignoutURL));
     https_server_.RegisterDefaultHandler(
         base::Bind(&FakeGaia::HandleOAuth2TokenExchangeURL, &token_requested_));
+    https_server_.RegisterDefaultHandler(base::Bind(
+        &FakeGaia::HandleOAuth2TokenRevokeURL, &token_revoked_count_));
   }
 
   // Navigates to the given path on the test server.
@@ -219,7 +238,7 @@
     // Signin main account.
     SigninManager* signin_manager = GetSigninManager();
     signin_manager->StartSignInWithRefreshToken(
-        "refresh_token", kMainGaiaID, kMainEmail, "password",
+        "existing_refresh_token", kMainGaiaID, kMainEmail, "password",
         SigninManager::OAuthTokenFetchedCallback());
     ASSERT_TRUE(GetTokenService()->RefreshTokenIsAvailable(GetMainAccountID()));
     ASSERT_EQ(GetMainAccountID(), signin_manager->GetAuthenticatedAccountId());
@@ -255,6 +274,7 @@
     const GURL& base_url = https_server_.base_url();
     command_line->AppendSwitchASCII(switches::kGaiaUrl, base_url.spec());
     command_line->AppendSwitchASCII(switches::kGoogleApisUrl, base_url.spec());
+    command_line->AppendSwitchASCII(switches::kLsoUrl, base_url.spec());
     switches::EnableAccountConsistencyDiceForTesting(command_line);
   }
 
@@ -274,9 +294,15 @@
       refresh_token_available_ = true;
   }
 
+  void OnRefreshTokenRevoked(const std::string& account_id) override {
+    ++token_revoked_notification_count_;
+  }
+
   net::EmbeddedTestServer https_server_;
   bool token_requested_;
   bool refresh_token_available_;
+  int token_revoked_notification_count_;
+  int token_revoked_count_;
 };
 
 // Checks that signin on Gaia triggers the fetch for a refresh token.
@@ -329,6 +355,9 @@
   EXPECT_TRUE(refresh_token_available_);
   EXPECT_EQ(GetMainAccountID(),
             GetSigninManager()->GetAuthenticatedAccountId());
+  // Old token must be revoked silently.
+  EXPECT_EQ(0, token_revoked_notification_count_);
+  EXPECT_EQ(1, token_revoked_count_);
 }
 
 // Checks that the Dice signout flow works and deletes all tokens.
@@ -344,6 +373,8 @@
   EXPECT_FALSE(GetTokenService()->RefreshTokenIsAvailable(GetMainAccountID()));
   EXPECT_FALSE(
       GetTokenService()->RefreshTokenIsAvailable(GetSecondaryAccountID()));
+  EXPECT_EQ(2, token_revoked_notification_count_);
+  EXPECT_EQ(2, token_revoked_count_);
 }
 
 // Checks that signing out from a secondary account does not delete the main
@@ -362,6 +393,8 @@
   EXPECT_TRUE(GetTokenService()->RefreshTokenIsAvailable(GetMainAccountID()));
   EXPECT_FALSE(
       GetTokenService()->RefreshTokenIsAvailable(GetSecondaryAccountID()));
+  EXPECT_EQ(1, token_revoked_notification_count_);
+  EXPECT_EQ(1, token_revoked_count_);
 }
 
 // Checks that the Dice signout flow works and deletes all tokens.
@@ -377,4 +410,6 @@
   EXPECT_FALSE(GetTokenService()->RefreshTokenIsAvailable(GetMainAccountID()));
   EXPECT_FALSE(
       GetTokenService()->RefreshTokenIsAvailable(GetSecondaryAccountID()));
+  EXPECT_EQ(2, token_revoked_notification_count_);
+  EXPECT_EQ(2, token_revoked_count_);
 }
diff --git a/chrome/browser/signin/mutable_profile_oauth2_token_service_delegate.cc b/chrome/browser/signin/mutable_profile_oauth2_token_service_delegate.cc
index 99209a5c..d837c66 100644
--- a/chrome/browser/signin/mutable_profile_oauth2_token_service_delegate.cc
+++ b/chrome/browser/signin/mutable_profile_oauth2_token_service_delegate.cc
@@ -476,7 +476,7 @@
     if (refresh_token_present) {
       VLOG(1) << "MutablePO2TS::UpdateCredentials; Refresh Token was present. "
               << "account_id=" << account_id;
-
+      RevokeCredentialsOnServer(refresh_tokens_[account_id]->refresh_token());
       refresh_tokens_[account_id]->set_refresh_token(refresh_token);
     } else {
       VLOG(1) << "MutablePO2TS::UpdateCredentials; Refresh Token was absent. "
diff --git a/chrome/browser/signin/mutable_profile_oauth2_token_service_delegate.h b/chrome/browser/signin/mutable_profile_oauth2_token_service_delegate.h
index 216ddf47..bc597ee 100644
--- a/chrome/browser/signin/mutable_profile_oauth2_token_service_delegate.h
+++ b/chrome/browser/signin/mutable_profile_oauth2_token_service_delegate.h
@@ -108,6 +108,8 @@
   FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest,
                            PersistenceLoadCredentials);
   FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest,
+                           RevokeOnUpdate);
+  FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest,
                            GetAccounts);
   FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest,
                            RetryBackoff);
diff --git a/chrome/browser/signin/mutable_profile_oauth2_token_service_delegate_unittest.cc b/chrome/browser/signin/mutable_profile_oauth2_token_service_delegate_unittest.cc
index a70eedf0..1f853096 100644
--- a/chrome/browser/signin/mutable_profile_oauth2_token_service_delegate_unittest.cc
+++ b/chrome/browser/signin/mutable_profile_oauth2_token_service_delegate_unittest.cc
@@ -414,7 +414,40 @@
 }
 #endif  // BUILDFLAG(ENABLE_DICE_SUPPORT)
 
-TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, PersistanceNotifications) {
+// Tests that calling UpdateCredentials revokes the old token, without sending
+// the notification.
+TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, RevokeOnUpdate) {
+  // Add a token.
+  ASSERT_TRUE(oauth2_service_delegate_->server_revokes_.empty());
+  oauth2_service_delegate_->UpdateCredentials("account_id", "refresh_token");
+  EXPECT_TRUE(oauth2_service_delegate_->server_revokes_.empty());
+  ExpectOneTokenAvailableNotification();
+
+  // Update the token.
+  oauth2_service_delegate_->UpdateCredentials("account_id", "refresh_token2");
+  EXPECT_EQ(1u, oauth2_service_delegate_->server_revokes_.size());
+  ExpectOneTokenAvailableNotification();
+
+  // Flush the server revokes.
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(oauth2_service_delegate_->server_revokes_.empty());
+
+  // Set the same token again.
+  oauth2_service_delegate_->UpdateCredentials("account_id", "refresh_token2");
+  EXPECT_TRUE(oauth2_service_delegate_->server_revokes_.empty());
+  ExpectNoNotifications();
+
+  // Clear the token.
+  oauth2_service_delegate_->RevokeAllCredentials();
+  EXPECT_EQ(1u, oauth2_service_delegate_->server_revokes_.size());
+  ExpectOneTokenRevokedNotification();
+
+  // Flush the server revokes.
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(oauth2_service_delegate_->server_revokes_.empty());
+}
+
+TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, PersistenceNotifications) {
   oauth2_service_delegate_->UpdateCredentials("account_id", "refresh_token");
   ExpectOneTokenAvailableNotification();
 
diff --git a/chrome/browser/site_per_process_interactive_browsertest.cc b/chrome/browser/site_per_process_interactive_browsertest.cc
index fb73671..1d9a4052 100644
--- a/chrome/browser/site_per_process_interactive_browsertest.cc
+++ b/chrome/browser/site_per_process_interactive_browsertest.cc
@@ -205,7 +205,6 @@
   std::string result;
   std::string script =
       "function onInput(e) {"
-      "  domAutomationController.setAutomationId(0);"
       "  domAutomationController.send(getInputFieldText());"
       "}"
       "inputField = document.getElementById('text-field');"
@@ -277,7 +276,6 @@
   // have an <input>, then two <iframe> elements, then another <input>.
   std::string script =
       "function onFocus(e) {"
-      "  domAutomationController.setAutomationId(0);"
       "  domAutomationController.send(window.name + '-focused-' + e.target.id);"
       "}"
       "var input1 = document.createElement('input');"
@@ -366,7 +364,6 @@
   // iframe: 55,18;55,67
   std::string script =
       "function onFocus(e) {"
-      "  domAutomationController.setAutomationId(0);"
       "  console.log(window.name + '-focused-' + e.target.id);"
       "  domAutomationController.send(window.name + '-focused-' + e.target.id);"
       "}"
@@ -588,7 +585,6 @@
                                  const std::string& id) {
   std::string script = base::StringPrintf(
       "document.addEventListener('webkitfullscreenchange', function() {"
-      "    domAutomationController.setAutomationId(0);"
       "    domAutomationController.send('fullscreenchange %s');});",
       id.c_str());
   EXPECT_TRUE(ExecuteScript(frame, script));
diff --git a/chrome/browser/speech/chrome_speech_recognition_manager_delegate.cc b/chrome/browser/speech/chrome_speech_recognition_manager_delegate.cc
index 788cb765..d851a8bf 100644
--- a/chrome/browser/speech/chrome_speech_recognition_manager_delegate.cc
+++ b/chrome/browser/speech/chrome_speech_recognition_manager_delegate.cc
@@ -271,7 +271,7 @@
 
 void ChromeSpeechRecognitionManagerDelegate::CheckRecognitionIsAllowed(
     int session_id,
-    base::Callback<void(bool ask_user, bool is_allowed)> callback) {
+    base::OnceCallback<void(bool ask_user, bool is_allowed)> callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
   const content::SpeechRecognitionSessionContext& context =
@@ -292,11 +292,10 @@
 
   // Check that the render view type is appropriate, and whether or not we
   // need to request permission from the user.
-  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
-                          base::Bind(&CheckRenderViewType,
-                                     callback,
-                                     render_process_id,
-                                     render_view_id));
+  BrowserThread::PostTask(
+      BrowserThread::UI, FROM_HERE,
+      base::BindOnce(&CheckRenderViewType, std::move(callback),
+                     render_process_id, render_view_id));
 }
 
 content::SpeechRecognitionEventListener*
@@ -317,7 +316,7 @@
 
 // static.
 void ChromeSpeechRecognitionManagerDelegate::CheckRenderViewType(
-    base::Callback<void(bool ask_user, bool is_allowed)> callback,
+    base::OnceCallback<void(bool ask_user, bool is_allowed)> callback,
     int render_process_id,
     int render_view_id) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
@@ -331,8 +330,9 @@
     // This happens for extensions. Manifest should be checked for permission.
     allowed = true;
     check_permission = false;
-    BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
-                            base::Bind(callback, check_permission, allowed));
+    BrowserThread::PostTask(
+        BrowserThread::IO, FROM_HERE,
+        base::BindOnce(std::move(callback), check_permission, allowed));
     return;
   }
 
@@ -357,8 +357,9 @@
   check_permission = true;
 #endif
 
-  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
-                          base::Bind(callback, check_permission, allowed));
+  BrowserThread::PostTask(
+      BrowserThread::IO, FROM_HERE,
+      base::BindOnce(std::move(callback), check_permission, allowed));
 }
 
 }  // namespace speech
diff --git a/chrome/browser/speech/chrome_speech_recognition_manager_delegate.h b/chrome/browser/speech/chrome_speech_recognition_manager_delegate.h
index cf81400..a2aab11 100644
--- a/chrome/browser/speech/chrome_speech_recognition_manager_delegate.h
+++ b/chrome/browser/speech/chrome_speech_recognition_manager_delegate.h
@@ -45,7 +45,8 @@
   // SpeechRecognitionManagerDelegate methods.
   void CheckRecognitionIsAllowed(
       int session_id,
-      base::Callback<void(bool ask_user, bool is_allowed)> callback) override;
+      base::OnceCallback<void(bool ask_user, bool is_allowed)> callback)
+      override;
   content::SpeechRecognitionEventListener* GetEventListener() override;
   bool FilterProfanities(int render_process_id) override;
 
@@ -58,7 +59,7 @@
   // Checks for VIEW_TYPE_TAB_CONTENTS host in the UI thread and notifies back
   // the result in the IO thread through |callback|.
   static void CheckRenderViewType(
-      base::Callback<void(bool ask_user, bool is_allowed)> callback,
+      base::OnceCallback<void(bool ask_user, bool is_allowed)> callback,
       int render_process_id,
       int render_view_id);
 
diff --git a/chrome/browser/ssl/ssl_browser_tests.cc b/chrome/browser/ssl/ssl_browser_tests.cc
index 8a3af9f1..d2698e0 100644
--- a/chrome/browser/ssl/ssl_browser_tests.cc
+++ b/chrome/browser/ssl/ssl_browser_tests.cc
@@ -4352,6 +4352,35 @@
   CheckAuthenticatedState(tab, AuthState::NONE);
 }
 
+// Checks that a restore followed immediately by a history navigation doesn't
+// lose SSL state.
+// Disabled since this is a test for bug 738177.
+IN_PROC_BROWSER_TEST_F(SSLUITest, DISABLED_RestoreThenNavigateHasSSLState) {
+  // This race condition only happens with PlzNavigate.
+  if (!content::IsBrowserSideNavigationEnabled())
+    return;
+  ASSERT_TRUE(https_server_.Start());
+  GURL url1(https_server_.GetURL("/ssl/google.html"));
+  GURL url2(https_server_.GetURL("/ssl/page_with_refs.html"));
+  ui_test_utils::NavigateToURLWithDisposition(
+      browser(), url1, WindowOpenDisposition::NEW_FOREGROUND_TAB,
+      ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
+  ui_test_utils::NavigateToURL(browser(), url2);
+  chrome::CloseTab(browser());
+
+  content::WindowedNotificationObserver tab_added_observer(
+      chrome::NOTIFICATION_TAB_PARENTED,
+      content::NotificationService::AllSources());
+  chrome::RestoreTab(browser());
+  tab_added_observer.Wait();
+
+  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
+  content::TestNavigationManager observer(tab, url1);
+  chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
+  observer.WaitForNavigationFinished();
+  CheckAuthenticatedState(tab, AuthState::NONE);
+}
+
 // Simulate the URL changing when the user presses enter in the omnibox. This
 // could happen when the user's login is expired and the server redirects them
 // to a login page. This will be considered a SAME_PAGE navigation but we do
diff --git a/chrome/browser/ui/android/login_handler_android.cc b/chrome/browser/ui/android/login_handler_android.cc
index 65e04b5b..895e430 100644
--- a/chrome/browser/ui/android/login_handler_android.cc
+++ b/chrome/browser/ui/android/login_handler_android.cc
@@ -9,14 +9,9 @@
 #include "base/logging.h"
 #include "base/strings/string16.h"
 #include "base/strings/utf_string_conversions.h"
-
-#include "device/vr/features/features.h"
-#if BUILDFLAG(ENABLE_VR)
-#include "chrome/browser/android/vr_shell/vr_tab_helper.h"
-#endif  // BUILDFLAG(ENABLE_VR)
-
 #include "chrome/browser/ui/android/chrome_http_auth_handler.h"
 #include "chrome/browser/ui/android/view_android_helper.h"
+#include "chrome/browser/vr/vr_tab_helper.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/web_contents.h"
 #include "net/base/auth.h"
@@ -56,12 +51,10 @@
     ViewAndroidHelper* view_helper = ViewAndroidHelper::FromWebContents(
         web_contents);
 
-#if BUILDFLAG(ENABLE_VR)
-    if (vr_shell::VrTabHelper::IsInVr(web_contents)) {
+    if (vr::VrTabHelper::IsInVr(web_contents)) {
       CancelAuth();
       return;
     }
-#endif
 
     // Notify WindowAndroid that HTTP authentication is required.
     if (view_helper->GetViewAndroid() &&
diff --git a/chrome/browser/ui/android/ssl_client_certificate_request.cc b/chrome/browser/ui/android/ssl_client_certificate_request.cc
index 3d9a25e..0886180 100644
--- a/chrome/browser/ui/android/ssl_client_certificate_request.cc
+++ b/chrome/browser/ui/android/ssl_client_certificate_request.cc
@@ -16,9 +16,9 @@
 #include "base/memory/ref_counted.h"
 #include "chrome/browser/ssl/ssl_client_certificate_selector.h"
 #include "chrome/browser/ui/android/view_android_helper.h"
+#include "chrome/browser/vr/vr_tab_helper.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/client_certificate_delegate.h"
-#include "device/vr/features/features.h"
 #include "jni/SSLClientCertificateRequest_jni.h"
 #include "net/base/host_port_pair.h"
 #include "net/cert/cert_database.h"
@@ -30,10 +30,6 @@
 #include "ui/android/view_android.h"
 #include "ui/android/window_android.h"
 
-#if BUILDFLAG(ENABLE_VR)
-#include "chrome/browser/android/vr_shell/vr_tab_helper.h"
-#endif  // BUILDFLAG(ENABLE_VR)
-
 using base::android::JavaParamRef;
 using base::android::ScopedJavaLocalRef;
 
@@ -193,12 +189,11 @@
     net::SSLCertRequestInfo* cert_request_info,
     net::ClientCertIdentityList unused_client_certs,
     std::unique_ptr<content::ClientCertificateDelegate> delegate) {
-#if BUILDFLAG(ENABLE_VR)
-  if (vr_shell::VrTabHelper::IsInVr(contents)) {
+  if (vr::VrTabHelper::IsInVr(contents)) {
     delegate->ContinueWithCertificate(nullptr, nullptr);
     return;
   }
-#endif  // BUILDFLAG(ENABLE_VR)
+
   ui::WindowAndroid* window = ViewAndroidHelper::FromWebContents(contents)
       ->GetViewAndroid()->GetWindowAndroid();
   DCHECK(window);
diff --git a/chrome/browser/ui/android/usb_chooser_dialog_android.cc b/chrome/browser/ui/android/usb_chooser_dialog_android.cc
index ebe196f..3bf01d85 100644
--- a/chrome/browser/ui/android/usb_chooser_dialog_android.cc
+++ b/chrome/browser/ui/android/usb_chooser_dialog_android.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/usb/usb_chooser_context.h"
 #include "chrome/browser/usb/usb_chooser_context_factory.h"
 #include "chrome/browser/usb/web_usb_histograms.h"
+#include "chrome/browser/vr/vr_tab_helper.h"
 #include "chrome/common/url_constants.h"
 #include "components/security_state/core/security_state.h"
 #include "components/url_formatter/elide_url.h"
@@ -34,10 +35,6 @@
 #include "ui/android/window_android.h"
 #include "url/gurl.h"
 
-#if BUILDFLAG(ENABLE_VR)
-#include "chrome/browser/android/vr_shell/vr_tab_helper.h"
-#endif  // BUILDFLAG(ENABLE_VR)
-
 using device::UsbDevice;
 
 namespace {
@@ -65,14 +62,13 @@
       weak_factory_(this) {
   content::WebContents* web_contents =
       content::WebContents::FromRenderFrameHost(render_frame_host_);
-#if BUILDFLAG(ENABLE_VR)
-  if (vr_shell::VrTabHelper::IsInVr(web_contents)) {
+  if (vr::VrTabHelper::IsInVr(web_contents)) {
     DCHECK(!callback_.is_null());
     callback_.Run(nullptr);
     callback_.Reset();  // Reset |callback_| so that it is only run once.
     return;
   }
-#endif
+
   device::UsbService* usb_service =
       device::DeviceClient::Get()->GetUsbService();
   if (!usb_service)
diff --git a/chrome/browser/ui/apps/app_info_dialog.h b/chrome/browser/ui/apps/app_info_dialog.h
index 4805d6a7..698e8982 100644
--- a/chrome/browser/ui/apps/app_info_dialog.h
+++ b/chrome/browser/ui/apps/app_info_dialog.h
@@ -21,7 +21,6 @@
 
 namespace gfx {
 class Rect;
-class Size;
 }
 
 // Used for UMA to track where the App Info dialog is launched from.
@@ -36,9 +35,6 @@
 // Returns true if the app info dialog is available on the current platform.
 bool CanShowAppInfoDialog();
 
-// Returns the size of the native window container for the app info dialog.
-gfx::Size GetAppInfoNativeDialogSize();
-
 #if BUILDFLAG(ENABLE_APP_LIST)
 // Shows the chrome app information as a frameless window for the given |app|
 // and |profile| at the given |app_list_bounds|. Appears 'inside' the app list.
@@ -49,9 +45,8 @@
                           const base::Closure& close_callback);
 #endif
 
-// Shows the chrome app information in a native dialog box of the given |size|.
+// Shows the chrome app information in a native dialog box.
 void ShowAppInfoInNativeDialog(content::WebContents* web_contents,
-                               const gfx::Size& size,
                                Profile* profile,
                                const extensions::Extension* app,
                                const base::Closure& close_callback);
diff --git a/chrome/browser/ui/ash/chrome_keyboard_ui.cc b/chrome/browser/ui/ash/chrome_keyboard_ui.cc
index 4354548b..9eaa8ce 100644
--- a/chrome/browser/ui/ash/chrome_keyboard_ui.cc
+++ b/chrome/browser/ui/ash/chrome_keyboard_ui.cc
@@ -34,45 +34,8 @@
 
 namespace virtual_keyboard_private = extensions::api::virtual_keyboard_private;
 
-typedef virtual_keyboard_private::OnTextInputBoxFocused::Context Context;
-
 namespace {
 
-const char* kVirtualKeyboardExtensionID = "mppnpdlheglhdfmldimlhpnegondlapf";
-
-virtual_keyboard_private::OnTextInputBoxFocusedType
-TextInputTypeToGeneratedInputTypeEnum(ui::TextInputType type) {
-  switch (type) {
-    case ui::TEXT_INPUT_TYPE_NONE:
-      return virtual_keyboard_private::ON_TEXT_INPUT_BOX_FOCUSED_TYPE_NONE;
-    case ui::TEXT_INPUT_TYPE_PASSWORD:
-      return virtual_keyboard_private::ON_TEXT_INPUT_BOX_FOCUSED_TYPE_PASSWORD;
-    case ui::TEXT_INPUT_TYPE_EMAIL:
-      return virtual_keyboard_private::ON_TEXT_INPUT_BOX_FOCUSED_TYPE_EMAIL;
-    case ui::TEXT_INPUT_TYPE_NUMBER:
-      return virtual_keyboard_private::ON_TEXT_INPUT_BOX_FOCUSED_TYPE_NUMBER;
-    case ui::TEXT_INPUT_TYPE_TELEPHONE:
-      return virtual_keyboard_private::ON_TEXT_INPUT_BOX_FOCUSED_TYPE_TEL;
-    case ui::TEXT_INPUT_TYPE_URL:
-      return virtual_keyboard_private::ON_TEXT_INPUT_BOX_FOCUSED_TYPE_URL;
-    case ui::TEXT_INPUT_TYPE_DATE:
-      return virtual_keyboard_private::ON_TEXT_INPUT_BOX_FOCUSED_TYPE_DATE;
-    case ui::TEXT_INPUT_TYPE_TEXT:
-    case ui::TEXT_INPUT_TYPE_SEARCH:
-    case ui::TEXT_INPUT_TYPE_DATE_TIME:
-    case ui::TEXT_INPUT_TYPE_DATE_TIME_LOCAL:
-    case ui::TEXT_INPUT_TYPE_MONTH:
-    case ui::TEXT_INPUT_TYPE_TIME:
-    case ui::TEXT_INPUT_TYPE_WEEK:
-    case ui::TEXT_INPUT_TYPE_TEXT_AREA:
-    case ui::TEXT_INPUT_TYPE_CONTENT_EDITABLE:
-    case ui::TEXT_INPUT_TYPE_DATE_TIME_FIELD:
-      return virtual_keyboard_private::ON_TEXT_INPUT_BOX_FOCUSED_TYPE_TEXT;
-  }
-  NOTREACHED();
-  return virtual_keyboard_private::ON_TEXT_INPUT_BOX_FOCUSED_TYPE_NONE;
-}
-
 class AshKeyboardControllerObserver
     : public keyboard::KeyboardControllerObserver {
  public:
@@ -209,30 +172,3 @@
               ->GetContainer(ash::kShellWindowId_ImeWindowParentContainer)
               ->Contains(window);
 }
-
-void ChromeKeyboardUI::SetUpdateInputType(ui::TextInputType type) {
-  // TODO(bshe): Need to check the affected window's profile once multi-profile
-  // is supported.
-  extensions::EventRouter* router =
-      extensions::EventRouter::Get(browser_context());
-
-  if (!router->HasEventListener(
-          virtual_keyboard_private::OnTextInputBoxFocused::kEventName)) {
-    return;
-  }
-
-  std::unique_ptr<base::ListValue> event_args(new base::ListValue());
-  std::unique_ptr<base::DictionaryValue> input_context(
-      new base::DictionaryValue());
-  input_context->SetString("type",
-                           virtual_keyboard_private::ToString(
-                               TextInputTypeToGeneratedInputTypeEnum(type)));
-  event_args->Append(std::move(input_context));
-
-  auto event = base::MakeUnique<extensions::Event>(
-      extensions::events::VIRTUAL_KEYBOARD_PRIVATE_ON_TEXT_INPUT_BOX_FOCUSED,
-      virtual_keyboard_private::OnTextInputBoxFocused::kEventName,
-      std::move(event_args), browser_context());
-  router->DispatchEventToExtension(kVirtualKeyboardExtensionID,
-                                   std::move(event));
-}
diff --git a/chrome/browser/ui/ash/chrome_keyboard_ui.h b/chrome/browser/ui/ash/chrome_keyboard_ui.h
index b796029..680613d 100644
--- a/chrome/browser/ui/ash/chrome_keyboard_ui.h
+++ b/chrome/browser/ui/ash/chrome_keyboard_ui.h
@@ -45,15 +45,6 @@
   void SetController(keyboard::KeyboardController* controller) override;
   void ShowKeyboardContainer(aura::Window* container) override;
 
-  // The overridden implementation dispatches
-  // chrome.virtualKeyboardPrivate.onTextInputBoxFocused event to extension to
-  // provide the input type information. Naturally, when the virtual keyboard
-  // extension is used as an IME then chrome.input.ime.onFocus provides the
-  // information, but not when the virtual keyboard is used in conjunction with
-  // another IME. virtualKeyboardPrivate.onTextInputBoxFocused is the remedy in
-  // that case.
-  void SetUpdateInputType(ui::TextInputType type) override;
-
   // content::WebContentsObserver overrides
   void RenderViewCreated(content::RenderViewHost* render_view_host) override;
 
diff --git a/chrome/browser/ui/cocoa/new_tab_button.mm b/chrome/browser/ui/cocoa/new_tab_button.mm
index 50a829b..bd8f593 100644
--- a/chrome/browser/ui/cocoa/new_tab_button.mm
+++ b/chrome/browser/ui/cocoa/new_tab_button.mm
@@ -428,6 +428,9 @@
   [strokeColor set];
   [bezierPath stroke];
 
+  BOOL isRTL = cocoa_l10n_util::ShouldDoExperimentalRTLLayout();
+  CGFloat buttonWidth = newTabButtonImageSize.width;
+
   // Bottom edge.
   const CGFloat kBottomEdgeX = 9;
   const CGFloat kBottomEdgeY = 1.2825;
@@ -435,6 +438,10 @@
   NSPoint bottomEdgeStart = NSMakePoint(kBottomEdgeX, kBottomEdgeY);
   NSPoint bottomEdgeEnd = NSMakePoint(kBottomEdgeX + kBottomEdgeWidth,
                                       kBottomEdgeY);
+  if (isRTL) {
+    bottomEdgeStart.x = buttonWidth - bottomEdgeStart.x;
+    bottomEdgeEnd.x = buttonWidth - bottomEdgeEnd.x;
+  }
   NSBezierPath* bottomEdgePath = [NSBezierPath bezierPath];
   [bottomEdgePath moveToPoint:bottomEdgeStart];
   [bottomEdgePath lineToPoint:bottomEdgeEnd];
@@ -477,6 +484,10 @@
 
   // Shadow beneath the bottom or top edge.
   if (!NSEqualPoints(shadowStart, NSZeroPoint)) {
+    if (isRTL) {
+      shadowStart.x = buttonWidth - shadowStart.x;
+      shadowEnd.x = buttonWidth - shadowEnd.x;
+    }
     NSBezierPath* shadowPath = [NSBezierPath bezierPath];
     [shadowPath moveToPoint:shadowStart];
     [shadowPath lineToPoint:shadowEnd];
diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc
index e98dac63..2f5b3f8 100644
--- a/chrome/browser/ui/tab_helpers.cc
+++ b/chrome/browser/ui/tab_helpers.cc
@@ -57,6 +57,7 @@
 #include "chrome/browser/ui/search_engines/search_engine_tab_helper.h"
 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
 #include "chrome/browser/ui/tab_dialogs.h"
+#include "chrome/browser/vr/vr_tab_helper.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/features.h"
 #include "components/autofill/content/browser/content_autofill_driver_factory.h"
@@ -81,12 +82,6 @@
 #include "chrome/browser/android/offline_pages/recent_tab_helper.h"
 #include "chrome/browser/android/search_geolocation/search_geolocation_disclosure_tab_helper.h"
 #include "chrome/browser/android/voice_search_tab_helper.h"
-
-#include "device/vr/features/features.h"  // nogncheck
-#if BUILDFLAG(ENABLE_VR)
-#include "chrome/browser/android/vr_shell/vr_tab_helper.h"
-#endif  // BUILDFLAG(ENABLE_VR)
-
 #include "chrome/browser/android/webapps/single_tab_mode_tab_helper.h"
 #include "chrome/browser/ui/android/context_menu_helper.h"
 #include "chrome/browser/ui/android/view_android_helper.h"
@@ -228,6 +223,7 @@
   // TODO(vabr): Remove TabSpecificContentSettings from here once their function
   // is taken over by ChromeContentSettingsClient. http://crbug.com/387075
   TabSpecificContentSettings::CreateForWebContents(web_contents);
+  vr::VrTabHelper::CreateForWebContents(web_contents);
 
   // NO! Do not just add your tab helper here. This is a large alphabetized
   // block; please insert your tab helper above in alphabetical order.
@@ -246,11 +242,6 @@
   SingleTabModeTabHelper::CreateForWebContents(web_contents);
   ViewAndroidHelper::CreateForWebContents(web_contents);
   VoiceSearchTabHelper::CreateForWebContents(web_contents);
-
-#if BUILDFLAG(ENABLE_VR)
-  vr_shell::VrTabHelper::CreateForWebContents(web_contents);
-#endif
-
 #else
   BookmarkTabHelper::CreateForWebContents(web_contents);
   extensions::ChromeExtensionWebContentsObserver::CreateForWebContents(
diff --git a/chrome/browser/ui/views/accessibility/invert_bubble_view.cc b/chrome/browser/ui/views/accessibility/invert_bubble_view.cc
index d6eea48..5e58c77 100644
--- a/chrome/browser/ui/views/accessibility/invert_bubble_view.cc
+++ b/chrome/browser/ui/views/accessibility/invert_bubble_view.cc
@@ -176,9 +176,13 @@
   if (color_utils::IsInvertedColorScheme() && anchor && anchor->GetWidget() &&
       !pref_service->GetBoolean(prefs::kInvertNotificationShown)) {
     pref_service->SetBoolean(prefs::kInvertNotificationShown, true);
-    InvertBubbleView* delegate = new InvertBubbleView(browser, anchor);
-    views::BubbleDialogDelegateView::CreateBubble(delegate)->Show();
+    ShowInvertBubbleView(browser, anchor);
   }
 }
 
+void ShowInvertBubbleView(Browser* browser, views::View* anchor) {
+  InvertBubbleView* delegate = new InvertBubbleView(browser, anchor);
+  views::BubbleDialogDelegateView::CreateBubble(delegate)->Show();
+}
+
 }  // namespace chrome
diff --git a/chrome/browser/ui/views/accessibility/invert_bubble_view.h b/chrome/browser/ui/views/accessibility/invert_bubble_view.h
index c2d4dc8..b1736c65 100644
--- a/chrome/browser/ui/views/accessibility/invert_bubble_view.h
+++ b/chrome/browser/ui/views/accessibility/invert_bubble_view.h
@@ -6,6 +6,11 @@
 #define CHROME_BROWSER_UI_VIEWS_ACCESSIBILITY_INVERT_BUBBLE_VIEW_H_
 
 class BrowserView;
+class Browser;
+
+namespace views {
+class View;
+}
 
 namespace chrome {
 
@@ -15,6 +20,9 @@
 // this condition for a particular profile.
 void MaybeShowInvertBubbleView(BrowserView* browser_view);
 
+// Shows the above bubble unconditionally.
+void ShowInvertBubbleView(Browser* browser, views::View* anchor);
+
 }  // namespace chrome
 
 #endif  // CHROME_BROWSER_UI_VIEWS_ACCESSIBILITY_INVERT_BUBBLE_VIEW_H_
diff --git a/chrome/browser/ui/views/accessibility/invert_bubble_view_browsertest.cc b/chrome/browser/ui/views/accessibility/invert_bubble_view_browsertest.cc
new file mode 100644
index 0000000..c31a254
--- /dev/null
+++ b/chrome/browser/ui/views/accessibility/invert_bubble_view_browsertest.cc
@@ -0,0 +1,32 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/accessibility/invert_bubble_view.h"
+
+#include <string>
+
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/test/test_browser_dialog.h"
+#include "ui/views/view.h"
+
+class InvertBubbleViewBrowserTest : public DialogBrowserTest {
+ public:
+  InvertBubbleViewBrowserTest() {}
+
+  // DialogBrowserTest:
+  void ShowDialog(const std::string& name) override {
+    chrome::ShowInvertBubbleView(browser(), &anchor_);
+  }
+
+ private:
+  views::View anchor_;
+
+  DISALLOW_COPY_AND_ASSIGN(InvertBubbleViewBrowserTest);
+};
+
+// Invokes a bubble that asks the user if they want to install a high contrast
+// Chrome theme. See test_browser_dialog.h.
+IN_PROC_BROWSER_TEST_F(InvertBubbleViewBrowserTest, InvokeDialog_default) {
+  RunDialog();
+}
diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views.cc
index f2dbaef..3bb451d 100644
--- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views.cc
+++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views.cc
@@ -70,10 +70,6 @@
 #endif
 }
 
-gfx::Size GetAppInfoNativeDialogSize() {
-  return gfx::Size(380, 490);
-}
-
 #if BUILDFLAG(ENABLE_APP_LIST)
 void ShowAppInfoInAppList(gfx::NativeWindow parent,
                           const gfx::Rect& app_list_bounds,
@@ -91,14 +87,14 @@
 #endif
 
 void ShowAppInfoInNativeDialog(content::WebContents* web_contents,
-                               const gfx::Size& size,
                                Profile* profile,
                                const extensions::Extension* app,
                                const base::Closure& close_callback) {
   gfx::NativeWindow window = web_contents->GetTopLevelNativeWindow();
   views::View* app_info_view = new AppInfoDialog(window, profile, app);
+  constexpr gfx::Size kDialogSize = gfx::Size(380, 490);
   views::DialogDelegate* dialog =
-      CreateDialogContainerForView(app_info_view, size, close_callback);
+      CreateDialogContainerForView(app_info_view, kDialogSize, close_callback);
   views::Widget* dialog_widget;
   if (dialog->GetModalType() == ui::MODAL_TYPE_CHILD) {
     dialog_widget =
diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views_browsertest.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views_browsertest.cc
new file mode 100644
index 0000000..1bc7da8
--- /dev/null
+++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views_browsertest.cc
@@ -0,0 +1,59 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/apps/app_info_dialog.h"
+
+#include <string>
+
+#include "build/build_config.h"
+#include "chrome/browser/extensions/test_extension_environment.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"
+#include "chrome/test/base/testing_profile.h"
+
+#if defined(OS_MACOSX)
+#include "base/command_line.h"
+#include "chrome/common/chrome_switches.h"
+#endif
+
+class AppInfoDialogBrowserTest : public DialogBrowserTest {
+ public:
+  AppInfoDialogBrowserTest() {}
+
+  // DialogBrowserTest:
+  void ShowDialog(const std::string& name) override {
+    extension_environment_ =
+        base::MakeUnique<extensions::TestExtensionEnvironment>(nullptr);
+    constexpr char kTestExtensionId[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+    extension_ =
+        extension_environment_->MakePackagedApp(kTestExtensionId, true);
+    auto* web_contents = browser()->tab_strip_model()->GetActiveWebContents();
+    ShowAppInfoInNativeDialog(
+        web_contents, extension_environment_->profile(), extension_.get(),
+        base::Bind(&AppInfoDialogBrowserTest::OnDialogClosed,
+                   base::Unretained(this)));
+  }
+
+  void OnDialogClosed() { extension_environment_.reset(); }
+
+#if defined(OS_MACOSX)
+  // content::BrowserTestBase:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    command_line->AppendSwitch(switches::kEnableAppInfoDialogMac);
+  }
+#endif
+
+ private:
+  std::unique_ptr<extensions::TestExtensionEnvironment> extension_environment_;
+  scoped_refptr<extensions::Extension> extension_;
+
+  DISALLOW_COPY_AND_ASSIGN(AppInfoDialogBrowserTest);
+};
+
+// Invokes a dialog that shows details of an installed extension. See
+// test_browser_dialog.h.
+IN_PROC_BROWSER_TEST_F(AppInfoDialogBrowserTest, InvokeDialog_default) {
+  RunDialog();
+}
diff --git a/chrome/browser/ui/views/drag_and_drop_interactive_uitest.cc b/chrome/browser/ui/views/drag_and_drop_interactive_uitest.cc
index fb73c92f..a5f072b 100644
--- a/chrome/browser/ui/views/drag_and_drop_interactive_uitest.cc
+++ b/chrome/browser/ui/views/drag_and_drop_interactive_uitest.cc
@@ -1210,10 +1210,9 @@
     // the test before the event has had a chance to be reported back to the
     // browser.
     std::string expected_response = base::StringPrintf("\"i%d\"", i);
-    right_frame()->ExecuteJavaScriptWithUserGestureForTests(base::UTF8ToUTF16(
-        base::StringPrintf("domAutomationController.setAutomationId(0);\n"
-                           "domAutomationController.send(%s);\n",
-                           expected_response.c_str())));
+    right_frame()->ExecuteJavaScriptWithUserGestureForTests(
+        base::UTF8ToUTF16(base::StringPrintf(
+            "domAutomationController.send(%s);", expected_response.c_str())));
 
     // Wait until our response comes back (it might be mixed with responses
     // carrying events that are sent by event_monitoring.js).
diff --git a/chrome/browser/ui/views/hung_renderer_view_browsertest.cc b/chrome/browser/ui/views/hung_renderer_view_browsertest.cc
index e6d85b8..2a9dc909 100644
--- a/chrome/browser/ui/views/hung_renderer_view_browsertest.cc
+++ b/chrome/browser/ui/views/hung_renderer_view_browsertest.cc
@@ -6,14 +6,12 @@
 
 #include <string>
 
-#include "base/command_line.h"
 #include "chrome/browser/platform_util.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tab_dialogs.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/test/test_browser_dialog.h"
 #include "content/public/browser/web_contents_unresponsive_state.h"
-#include "ui/base/ui_base_switches.h"
 
 class HungRendererDialogViewBrowserTest : public DialogBrowserTest {
  public:
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
index ea5764be..8bd5b218 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
@@ -313,16 +313,8 @@
 }
 
 void BrowserTabStripController::UpdateLoadingAnimations() {
-  // Don't use the model count here as it's possible for this to be invoked
-  // before we've applied an update from the model (Browser::TabInsertedAt may
-  // be processed before us and invokes this).
-  for (int i = 0, tab_count = tabstrip_->tab_count(); i < tab_count; ++i) {
-    if (model_->ContainsIndex(i)) {
-      Tab* tab = tabstrip_->tab_at(i);
-      WebContents* contents = model_->GetWebContentsAt(i);
-      tab->UpdateLoadingAnimation(TabContentsNetworkState(contents));
-    }
-  }
+  for (int i = 0, tab_count = tabstrip_->tab_count(); i < tab_count; ++i)
+    tabstrip_->tab_at(i)->StepLoadingAnimation();
 }
 
 int BrowserTabStripController::HasAvailableDragActions() const {
diff --git a/chrome/browser/ui/views/tabs/tab.cc b/chrome/browser/ui/views/tabs/tab.cc
index babe43fc..55766eac 100644
--- a/chrome/browser/ui/views/tabs/tab.cc
+++ b/chrome/browser/ui/views/tabs/tab.cc
@@ -13,6 +13,7 @@
 #include "base/macros.h"
 #include "base/metrics/user_metrics.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
 #include "build/build_config.h"
 #include "cc/paint/paint_flags.h"
 #include "cc/paint/paint_recorder.h"
@@ -97,6 +98,11 @@
 
 const char kTabCloseButtonName[] = "TabCloseButton";
 
+bool ShouldShowThrobber(TabRendererData::NetworkState state) {
+  return state != TabRendererData::NETWORK_STATE_NONE &&
+         state != TabRendererData::NETWORK_STATE_ERROR;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Drawing and utility functions
 
@@ -361,11 +367,10 @@
   // Resets the times tracking when the throbber changes state.
   void ResetStartTimes();
 
- private:
   // views::View:
-  bool CanProcessEventsWithinSubtree() const override;
   void OnPaint(gfx::Canvas* canvas) override;
 
+ private:
   Tab* owner_;  // Weak. Owns |this|.
 
   // The point in time when the tab icon was first painted in the waiting state.
@@ -380,7 +385,9 @@
   DISALLOW_COPY_AND_ASSIGN(ThrobberView);
 };
 
-Tab::ThrobberView::ThrobberView(Tab* owner) : owner_(owner) {}
+Tab::ThrobberView::ThrobberView(Tab* owner) : owner_(owner) {
+  set_can_process_events_within_subtree(false);
+}
 
 void Tab::ThrobberView::ResetStartTimes() {
   waiting_start_time_ = base::TimeTicks();
@@ -388,15 +395,9 @@
   waiting_state_ = gfx::ThrobberWaitingState();
 }
 
-bool Tab::ThrobberView::CanProcessEventsWithinSubtree() const {
-  return false;
-}
-
 void Tab::ThrobberView::OnPaint(gfx::Canvas* canvas) {
   const TabRendererData::NetworkState state = owner_->data().network_state;
-  if (state == TabRendererData::NETWORK_STATE_NONE ||
-      state == TabRendererData::NETWORK_STATE_ERROR)
-    return;
+  CHECK(ShouldShowThrobber(state));
 
   const ui::ThemeProvider* tp = GetThemeProvider();
   const gfx::Rect bounds = GetLocalBounds();
@@ -534,8 +535,8 @@
     return;
 
   TabRendererData old(data_);
-  UpdateLoadingAnimation(data.network_state);
   data_ = data;
+  UpdateThrobber(old);
 
   base::string16 title = data_.title;
   if (title.empty()) {
@@ -569,17 +570,11 @@
   SchedulePaint();
 }
 
-void Tab::UpdateLoadingAnimation(TabRendererData::NetworkState state) {
-  if (state == data_.network_state &&
-      (state == TabRendererData::NETWORK_STATE_NONE ||
-       state == TabRendererData::NETWORK_STATE_ERROR)) {
-    // If the network state is none or is a network error and hasn't changed,
-    // do nothing. Otherwise we need to advance the animation frame.
+void Tab::StepLoadingAnimation() {
+  if (!throbber_->visible())
     return;
-  }
 
-  data_.network_state = state;
-  AdvanceLoadingAnimation();
+  RefreshThrobber();
 }
 
 void Tab::StartPulse() {
@@ -1273,10 +1268,8 @@
     return;
 
   // Throbber will do its own painting.
-  if (data().network_state != TabRendererData::NETWORK_STATE_NONE &&
-      data().network_state != TabRendererData::NETWORK_STATE_ERROR) {
+  if (throbber_->visible())
     return;
-  }
   // Ensure that |favicon_| is created.
   if (favicon_.isNull()) {
     ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
@@ -1306,16 +1299,47 @@
   }
 }
 
-void Tab::AdvanceLoadingAnimation() {
-  const TabRendererData::NetworkState state = data().network_state;
-  if (state == TabRendererData::NETWORK_STATE_NONE ||
-      state == TabRendererData::NETWORK_STATE_ERROR) {
+void Tab::UpdateThrobber(const TabRendererData& old) {
+  const bool should_show = ShouldShowThrobber(data_.network_state);
+  const bool is_showing = throbber_->visible();
+
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kDelayReloadStopButtonChange)) {
+    // Minimize flip-flops between showing the throbber and the favicon. Delay
+    // the switch from favicon to throbber if the switch would occur just after
+    // the a throbbing session finishes. The heuristic for "throbbing session
+    // finishing" is that the old and new URLs match and that the throbber has
+    // been shown recently. See crbug.com/734104
+    constexpr auto kSuppressChangeDuration = base::TimeDelta::FromSeconds(3);
+    if (!is_showing && should_show && old.url == data_.url &&
+        (base::TimeTicks::Now() - last_throbber_show_time_) <
+            kSuppressChangeDuration) {
+      if (!delayed_throbber_show_timer_.IsRunning()) {
+        delayed_throbber_show_timer_.Start(FROM_HERE, kSuppressChangeDuration,
+                                           this, &Tab::RefreshThrobber);
+      }
+      return;
+    }
+
+    delayed_throbber_show_timer_.Stop();
+  }
+
+  if (!is_showing && !should_show)
+    return;
+
+  RefreshThrobber();
+}
+
+void Tab::RefreshThrobber() {
+  if (!ShouldShowThrobber(data().network_state)) {
     throbber_->ResetStartTimes();
     throbber_->SetVisible(false);
     ScheduleIconPaint();
     return;
   }
 
+  last_throbber_show_time_ = base::TimeTicks::Now();
+
   // Since the throbber can animate for a long time, paint to a separate layer
   // when possible to reduce repaint overhead.
   const bool paint_to_layer = controller_->CanPaintThrobberToLayer();
diff --git a/chrome/browser/ui/views/tabs/tab.h b/chrome/browser/ui/views/tabs/tab.h
index 573ea77..e150f104 100644
--- a/chrome/browser/ui/views/tabs/tab.h
+++ b/chrome/browser/ui/views/tabs/tab.h
@@ -12,6 +12,8 @@
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
 #include "cc/paint/paint_record.h"
 #include "chrome/browser/ui/views/tabs/tab_renderer_data.h"
 #include "ui/base/layout.h"
@@ -94,8 +96,8 @@
   void SetData(const TabRendererData& data);
   const TabRendererData& data() const { return data_; }
 
-  // Sets the network state.
-  void UpdateLoadingAnimation(TabRendererData::NetworkState state);
+  // Redraws the loading animation if one is visible. Otherwise, no-op.
+  void StepLoadingAnimation();
 
   // Starts/Stops a pulse animation.
   void StartPulse();
@@ -259,8 +261,11 @@
   // Paints the favicon, mirrored for RTL if needed.
   void PaintIcon(gfx::Canvas* canvas);
 
-  // Invoked if data_.network_state changes, or the network_state is not none.
-  void AdvanceLoadingAnimation();
+  // Updates the throbber.
+  void UpdateThrobber(const TabRendererData& old);
+
+  // Sets the throbber visibility according to the state in |data_|.
+  void RefreshThrobber();
 
   // Returns the number of favicon-size elements that can fit in the tab's
   // current size.
@@ -363,6 +368,15 @@
   // and thus may be null.
   gfx::ImageSkia favicon_;
 
+  // This timer allows us to delay updating the visibility of the loading
+  // indicator so that state changes of a very brief duration aren't visually
+  // apparent to the user.
+  base::OneShotTimer delayed_throbber_show_timer_;
+
+  // The last time the throbber was visible to the user. See notes in
+  // UpdateThrobber().
+  base::TimeTicks last_throbber_show_time_;
+
   class BackgroundCache {
    public:
     BackgroundCache();
diff --git a/chrome/browser/ui/views/tabs/tab_unittest.cc b/chrome/browser/ui/views/tabs/tab_unittest.cc
index c751379..05b823f 100644
--- a/chrome/browser/ui/views/tabs/tab_unittest.cc
+++ b/chrome/browser/ui/views/tabs/tab_unittest.cc
@@ -6,12 +6,14 @@
 
 #include <stddef.h>
 
+#include "base/command_line.h"
 #include "base/macros.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ui/layout_constants.h"
 #include "chrome/browser/ui/tabs/tab_utils.h"
 #include "chrome/browser/ui/views/tabs/alert_indicator_button.h"
 #include "chrome/browser/ui/views/tabs/tab_controller.h"
+#include "chrome/common/chrome_switches.h"
 #include "chrome/grit/theme_resources.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/models/list_selection_model.h"
@@ -233,6 +235,17 @@
     }
   }
 
+  void FastForwardThrobberStateTimer(Tab* tab) {
+    ASSERT_TRUE(tab->delayed_throbber_show_timer_.IsRunning());
+    auto closure = tab->delayed_throbber_show_timer_.user_task();
+    tab->delayed_throbber_show_timer_.Stop();
+    closure.Run();
+  }
+
+  void WindBackLastThrobberShowTime(Tab* tab) {
+    tab->last_throbber_show_time_ = base::TimeTicks();
+  }
+
  protected:
   void InitWidget(Widget* widget) {
     Widget::InitParams params(CreateParams(Widget::InitParams::TYPE_WINDOW));
@@ -442,47 +455,153 @@
   tab.SetBoundsRect(gfx::Rect(Tab::GetStandardSize()));
 
   views::View* throbber = GetThrobberView(tab);
+  TabRendererData data;
+  data.url = GURL("http://example.com");
   EXPECT_FALSE(throbber->visible());
   EXPECT_EQ(TabRendererData::NETWORK_STATE_NONE, tab.data().network_state);
   EXPECT_EQ(throbber->bounds(), GetFaviconBounds(tab));
 
-  tab.UpdateLoadingAnimation(TabRendererData::NETWORK_STATE_NONE);
-  EXPECT_FALSE(throbber->visible());
-
   // Simulate a "normal" tab load: should paint to a layer.
-  tab.UpdateLoadingAnimation(TabRendererData::NETWORK_STATE_WAITING);
+  data.network_state = TabRendererData::NETWORK_STATE_WAITING;
+  tab.SetData(data);
   EXPECT_TRUE(tab_controller.CanPaintThrobberToLayer());
   EXPECT_TRUE(throbber->visible());
   EXPECT_TRUE(throbber->layer());
-  tab.UpdateLoadingAnimation(TabRendererData::NETWORK_STATE_LOADING);
+  data.network_state = TabRendererData::NETWORK_STATE_LOADING;
+  tab.SetData(data);
   EXPECT_TRUE(throbber->visible());
   EXPECT_TRUE(throbber->layer());
-  tab.UpdateLoadingAnimation(TabRendererData::NETWORK_STATE_NONE);
+  data.network_state = TabRendererData::NETWORK_STATE_NONE;
+  tab.SetData(data);
+  EXPECT_FALSE(throbber->visible());
+
+  // After loading is done, simulate another resource starting to load.
+  data.network_state = TabRendererData::NETWORK_STATE_WAITING;
+  tab.SetData(data);
+  EXPECT_TRUE(throbber->visible());
+
+  // Reset.
+  data.network_state = TabRendererData::NETWORK_STATE_NONE;
+  tab.SetData(data);
   EXPECT_FALSE(throbber->visible());
 
   // Simulate a drag started and stopped during a load: layer painting stops
   // temporarily.
-  tab.UpdateLoadingAnimation(TabRendererData::NETWORK_STATE_WAITING);
+  data.network_state = TabRendererData::NETWORK_STATE_WAITING;
+  tab.SetData(data);
   EXPECT_TRUE(throbber->visible());
   EXPECT_TRUE(throbber->layer());
   tab_controller.set_paint_throbber_to_layer(false);
-  tab.UpdateLoadingAnimation(TabRendererData::NETWORK_STATE_WAITING);
+  tab.StepLoadingAnimation();
   EXPECT_TRUE(throbber->visible());
   EXPECT_FALSE(throbber->layer());
   tab_controller.set_paint_throbber_to_layer(true);
-  tab.UpdateLoadingAnimation(TabRendererData::NETWORK_STATE_WAITING);
+  tab.StepLoadingAnimation();
   EXPECT_TRUE(throbber->visible());
   EXPECT_TRUE(throbber->layer());
-  tab.UpdateLoadingAnimation(TabRendererData::NETWORK_STATE_NONE);
+  data.network_state = TabRendererData::NETWORK_STATE_NONE;
+  tab.SetData(data);
   EXPECT_FALSE(throbber->visible());
 
   // Simulate a tab load starting and stopping during tab dragging (or with
   // stacked tabs): no layer painting.
   tab_controller.set_paint_throbber_to_layer(false);
-  tab.UpdateLoadingAnimation(TabRendererData::NETWORK_STATE_WAITING);
+  data.network_state = TabRendererData::NETWORK_STATE_WAITING;
+  tab.SetData(data);
   EXPECT_TRUE(throbber->visible());
   EXPECT_FALSE(throbber->layer());
-  tab.UpdateLoadingAnimation(TabRendererData::NETWORK_STATE_NONE);
+  data.network_state = TabRendererData::NETWORK_STATE_NONE;
+  tab.SetData(data);
+  EXPECT_FALSE(throbber->visible());
+}
+
+// As above, but tests what happens when we're heuristically delaying the switch
+// between favicon and throbber.
+TEST_F(TabTest, LayeredThrobber2) {
+  base::CommandLine::ForCurrentProcess()->AppendSwitch(
+      switches::kDelayReloadStopButtonChange);
+
+  Widget widget;
+  InitWidget(&widget);
+
+  FakeTabController tab_controller;
+  Tab tab(&tab_controller, nullptr);
+  widget.GetContentsView()->AddChildView(&tab);
+  tab.SetBoundsRect(gfx::Rect(Tab::GetStandardSize()));
+
+  views::View* throbber = GetThrobberView(tab);
+  TabRendererData data;
+  data.url = GURL("http://example.com");
+  EXPECT_FALSE(throbber->visible());
+  EXPECT_EQ(TabRendererData::NETWORK_STATE_NONE, tab.data().network_state);
+  EXPECT_EQ(throbber->bounds(), GetFaviconBounds(tab));
+
+  // Simulate a "normal" tab load: should paint to a layer.
+  data.network_state = TabRendererData::NETWORK_STATE_WAITING;
+  tab.SetData(data);
+  EXPECT_TRUE(tab_controller.CanPaintThrobberToLayer());
+  EXPECT_TRUE(throbber->visible());
+  EXPECT_TRUE(throbber->layer());
+  data.network_state = TabRendererData::NETWORK_STATE_LOADING;
+  tab.SetData(data);
+  EXPECT_TRUE(throbber->visible());
+  EXPECT_TRUE(throbber->layer());
+  data.network_state = TabRendererData::NETWORK_STATE_NONE;
+  tab.SetData(data);
+  EXPECT_FALSE(throbber->visible());
+
+  // After loading is done, simulate another resource starting to load. The
+  // throbber shouldn't immediately become visible again.
+  data.network_state = TabRendererData::NETWORK_STATE_WAITING;
+  tab.SetData(data);
+  EXPECT_FALSE(throbber->visible());
+  ASSERT_NO_FATAL_FAILURE(FastForwardThrobberStateTimer(&tab));
+  EXPECT_TRUE(throbber->visible());
+
+  // On the other hand, if a resource starts to load after the throbber has been
+  // absent for a while, jump straight to showing it.
+  data.network_state = TabRendererData::NETWORK_STATE_NONE;
+  tab.SetData(data);
+  EXPECT_FALSE(throbber->visible());
+  WindBackLastThrobberShowTime(&tab);
+  data.network_state = TabRendererData::NETWORK_STATE_WAITING;
+  tab.SetData(data);
+  EXPECT_TRUE(throbber->visible());
+
+  // Reset.
+  data.network_state = TabRendererData::NETWORK_STATE_NONE;
+  tab.SetData(data);
+  EXPECT_FALSE(throbber->visible());
+
+  // Simulate a drag started and stopped during a load: layer painting stops
+  // temporarily.
+  data.network_state = TabRendererData::NETWORK_STATE_WAITING;
+  tab.SetData(data);
+  ASSERT_NO_FATAL_FAILURE(FastForwardThrobberStateTimer(&tab));
+  EXPECT_TRUE(throbber->visible());
+  EXPECT_TRUE(throbber->layer());
+  tab_controller.set_paint_throbber_to_layer(false);
+  tab.StepLoadingAnimation();
+  EXPECT_TRUE(throbber->visible());
+  EXPECT_FALSE(throbber->layer());
+  tab_controller.set_paint_throbber_to_layer(true);
+  tab.StepLoadingAnimation();
+  EXPECT_TRUE(throbber->visible());
+  EXPECT_TRUE(throbber->layer());
+  data.network_state = TabRendererData::NETWORK_STATE_NONE;
+  tab.SetData(data);
+  EXPECT_FALSE(throbber->visible());
+
+  // Simulate a tab load starting and stopping during tab dragging (or with
+  // stacked tabs): no layer painting.
+  tab_controller.set_paint_throbber_to_layer(false);
+  data.network_state = TabRendererData::NETWORK_STATE_WAITING;
+  tab.SetData(data);
+  ASSERT_NO_FATAL_FAILURE(FastForwardThrobberStateTimer(&tab));
+  EXPECT_TRUE(throbber->visible());
+  EXPECT_FALSE(throbber->layer());
+  data.network_state = TabRendererData::NETWORK_STATE_NONE;
+  tab.SetData(data);
   EXPECT_FALSE(throbber->visible());
 }
 
diff --git a/chrome/browser/ui/webui/chromeos/login/supervised_user_creation_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/supervised_user_creation_screen_handler.cc
index 4d410499..8063753 100644
--- a/chrome/browser/ui/webui/chromeos/login/supervised_user_creation_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/supervised_user_creation_screen_handler.cc
@@ -407,23 +407,9 @@
 // TODO(antrim) : this is an explicit code duplications with UserImageScreen.
 // It should be removed by issue 251179.
 void SupervisedUserCreationScreenHandler::HandleGetImages() {
-  base::ListValue image_urls;
-  for (int i = default_user_image::kFirstDefaultImageIndex;
-       i < default_user_image::kDefaultImagesCount; ++i) {
-    std::unique_ptr<base::DictionaryValue> image_data(
-        new base::DictionaryValue);
-    image_data->SetString("url", default_user_image::GetDefaultImageUrl(i));
-    image_data->SetString("author",
-                          l10n_util::GetStringUTF16(
-                              default_user_image::kDefaultImageAuthorIDs[i]));
-    image_data->SetString("website",
-                          l10n_util::GetStringUTF16(
-                              default_user_image::kDefaultImageWebsiteIDs[i]));
-    image_data->SetString("title",
-                          default_user_image::GetDefaultImageDescription(i));
-    image_urls.Append(std::move(image_data));
-  }
-  CallJS("setDefaultImages", image_urls);
+  std::unique_ptr<base::ListValue> image_urls =
+      default_user_image::GetAsDictionary();
+  CallJS("setDefaultImages", *image_urls);
 }
 
 void SupervisedUserCreationScreenHandler::HandlePhotoTaken
diff --git a/chrome/browser/ui/webui/chromeos/login/user_image_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/user_image_screen_handler.cc
index d313a59..76761ea80 100644
--- a/chrome/browser/ui/webui/chromeos/login/user_image_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/user_image_screen_handler.cc
@@ -127,23 +127,9 @@
 
 // TODO(antrim) : It looks more like parameters for "Init" rather than callback.
 void UserImageScreenHandler::HandleGetImages() {
-  base::ListValue image_urls;
-  for (int i = default_user_image::kFirstDefaultImageIndex;
-       i < default_user_image::kDefaultImagesCount; ++i) {
-    std::unique_ptr<base::DictionaryValue> image_data(
-        new base::DictionaryValue);
-    image_data->SetString("url", default_user_image::GetDefaultImageUrl(i));
-    image_data->SetString("author",
-                          l10n_util::GetStringUTF16(
-                              default_user_image::kDefaultImageAuthorIDs[i]));
-    image_data->SetString("website",
-                          l10n_util::GetStringUTF16(
-                              default_user_image::kDefaultImageWebsiteIDs[i]));
-    image_data->SetString("title",
-                          default_user_image::GetDefaultImageDescription(i));
-    image_urls.Append(std::move(image_data));
-  }
-  CallJS("setDefaultImages", image_urls);
+  std::unique_ptr<base::ListValue> image_urls =
+      default_user_image::GetAsDictionary();
+  CallJS("setDefaultImages", *image_urls);
 }
 
 void UserImageScreenHandler::HandleScreenReady() {
diff --git a/chrome/browser/ui/webui/ntp/app_launcher_handler.cc b/chrome/browser/ui/webui/ntp/app_launcher_handler.cc
index 8a5b7f958..ad2603e6 100644
--- a/chrome/browser/ui/webui/ntp/app_launcher_handler.cc
+++ b/chrome/browser/ui/webui/ntp/app_launcher_handler.cc
@@ -630,9 +630,9 @@
                             AppInfoLaunchSource::FROM_APPS_PAGE,
                             AppInfoLaunchSource::NUM_LAUNCH_SOURCES);
 
-  ShowAppInfoInNativeDialog(
-      web_ui()->GetWebContents(), GetAppInfoNativeDialogSize(),
-      Profile::FromWebUI(web_ui()), extension, base::Closure());
+  ShowAppInfoInNativeDialog(web_ui()->GetWebContents(),
+                            Profile::FromWebUI(web_ui()), extension,
+                            base::Closure());
 }
 
 void AppLauncherHandler::HandleReorderApps(const base::ListValue* args) {
diff --git a/chrome/browser/ui/webui/ntp/ntp_resource_cache.cc b/chrome/browser/ui/webui/ntp/ntp_resource_cache.cc
index 7e7d444a..50ef4300 100644
--- a/chrome/browser/ui/webui/ntp/ntp_resource_cache.cc
+++ b/chrome/browser/ui/webui/ntp/ntp_resource_cache.cc
@@ -336,20 +336,26 @@
 
   policy::BrowserPolicyConnectorChromeOS* connector =
       g_browser_process->platform_part()->browser_policy_connector_chromeos();
-  std::string enterprise_domain = connector->GetEnterpriseDomain();
 
-  // TODO(jamescook): What about Active Directory managed devices?
-  if (!enterprise_domain.empty()) {
-    // Device is enterprise enrolled.
+  if (connector->IsEnterpriseManaged()) {
     localized_strings.SetString("enterpriseInfoVisible", "true");
-    base::string16 enterprise_info =
-        l10n_util::GetStringFUTF16(IDS_ASH_ENTERPRISE_DEVICE_MANAGED_BY,
-                                   base::UTF8ToUTF16(enterprise_domain));
-    localized_strings.SetString("enterpriseInfoMessage", enterprise_info);
     localized_strings.SetString("enterpriseLearnMore",
-        l10n_util::GetStringUTF16(IDS_LEARN_MORE));
+                                l10n_util::GetStringUTF16(IDS_LEARN_MORE));
     localized_strings.SetString("enterpriseInfoHintLink",
                                 chrome::kLearnMoreEnterpriseURL);
+    base::string16 enterprise_info;
+    if (connector->IsCloudManaged()) {
+      const std::string enterprise_domain = connector->GetEnterpriseDomain();
+      enterprise_info =
+          l10n_util::GetStringFUTF16(IDS_ASH_ENTERPRISE_DEVICE_MANAGED_BY,
+                                     base::UTF8ToUTF16(enterprise_domain));
+    } else if (connector->IsActiveDirectoryManaged()) {
+      enterprise_info =
+          l10n_util::GetStringUTF16(IDS_ASH_ENTERPRISE_DEVICE_MANAGED);
+    } else {
+      NOTREACHED() << "Unknown management type";
+    }
+    localized_strings.SetString("enterpriseInfoMessage", enterprise_info);
   } else {
     localized_strings.SetString("enterpriseInfoVisible", "false");
     localized_strings.SetString("enterpriseInfoMessage", "");
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_handler.cc b/chrome/browser/ui/webui/print_preview/print_preview_handler.cc
index 27f6272d..c52327c8 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_handler.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_handler.cc
@@ -1594,6 +1594,10 @@
   preview_callbacks_.pop();
 }
 
+void PrintPreviewHandler::OnPrintRequestCancelled() {
+  HandleCancelPendingPrintRequest(nullptr);
+}
+
 #if BUILDFLAG(ENABLE_BASIC_PRINT_DIALOG)
 void PrintPreviewHandler::ShowSystemDialog() {
   HandleShowSystemDialog(NULL);
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_handler.h b/chrome/browser/ui/webui/print_preview/print_preview_handler.h
index 6561892..c638f650 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_handler.h
+++ b/chrome/browser/ui/webui/print_preview/print_preview_handler.h
@@ -86,6 +86,9 @@
   // Called when print preview is ready.
   void OnPrintPreviewReady(int preview_uid, int request_id);
 
+  // Called when a print request is cancelled due to its initiator closing.
+  void OnPrintRequestCancelled();
+
   // Send the print preset options from the document.
   void SendPrintPresetOptions(bool disable_scaling, int copies, int duplex);
 
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_ui.cc b/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
index 5233b57b..411c574 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
@@ -533,13 +533,20 @@
 }
 
 void PrintPreviewUI::OnInitiatorClosed() {
+  // Should only get here if the initiator was still tracked by the Print
+  // Preview Dialog Controller, so the print job has not yet been sent.
   WebContents* preview_dialog = web_ui()->GetWebContents();
   printing::BackgroundPrintingManager* background_printing_manager =
       g_browser_process->background_printing_manager();
-  if (background_printing_manager->HasPrintPreviewDialog(preview_dialog))
-    web_ui()->CallJavascriptFunctionUnsafe("cancelPendingPrintRequest");
-  else
+  if (background_printing_manager->HasPrintPreviewDialog(preview_dialog)) {
+    // Dialog is hidden but is still generating the preview. Cancel the print
+    // request as it can't be completed.
+    background_printing_manager->OnPrintRequestCancelled(preview_dialog);
+    handler_->OnPrintRequestCancelled();
+  } else {
+    // Initiator was closed while print preview dialog was still open.
     OnClosePrintPreviewDialog();
+  }
 }
 
 void PrintPreviewUI::OnPrintPreviewCancelled() {
diff --git a/chrome/browser/ui/webui/settings/chromeos/change_picture_handler.cc b/chrome/browser/ui/webui/settings/chromeos/change_picture_handler.cc
index 378b08c..c21e51d 100644
--- a/chrome/browser/ui/webui/settings/chromeos/change_picture_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/change_picture_handler.cc
@@ -127,23 +127,9 @@
 }
 
 void ChangePictureHandler::SendDefaultImages() {
-  base::ListValue image_urls;
-  for (int i = default_user_image::kFirstDefaultImageIndex;
-       i < default_user_image::kDefaultImagesCount; ++i) {
-    std::unique_ptr<base::DictionaryValue> image_data(
-        new base::DictionaryValue);
-    image_data->SetString("url", default_user_image::GetDefaultImageUrl(i));
-    image_data->SetString("author",
-                          l10n_util::GetStringUTF16(
-                              default_user_image::kDefaultImageAuthorIDs[i]));
-    image_data->SetString("website",
-                          l10n_util::GetStringUTF16(
-                              default_user_image::kDefaultImageWebsiteIDs[i]));
-    image_data->SetString("title",
-                          default_user_image::GetDefaultImageDescription(i));
-    image_urls.Append(std::move(image_data));
-  }
-  FireWebUIListener("default-images-changed", image_urls);
+  std::unique_ptr<base::ListValue> image_urls =
+      default_user_image::GetAsDictionary();
+  FireWebUIListener("default-images-changed", *image_urls);
 }
 
 void ChangePictureHandler::HandleChooseFile(const base::ListValue* args) {
diff --git a/chrome/browser/android/vr_shell/vr_tab_helper.cc b/chrome/browser/vr/vr_tab_helper.cc
similarity index 80%
rename from chrome/browser/android/vr_shell/vr_tab_helper.cc
rename to chrome/browser/vr/vr_tab_helper.cc
index 20372ea..b8f14db 100644
--- a/chrome/browser/android/vr_shell/vr_tab_helper.cc
+++ b/chrome/browser/vr/vr_tab_helper.cc
@@ -2,19 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/android/vr_shell/vr_tab_helper.h"
+#include "chrome/browser/vr/vr_tab_helper.h"
 
-#include "chrome/browser/android/vr_shell/vr_shell_delegate.h"
 #include "content/public/browser/render_view_host.h"
 #include "content/public/common/web_preferences.h"
-#include "device/vr/android/gvr/gvr_delegate_provider.h"
 
 using content::WebContents;
 using content::WebPreferences;
 
-DEFINE_WEB_CONTENTS_USER_DATA_KEY(vr_shell::VrTabHelper);
+DEFINE_WEB_CONTENTS_USER_DATA_KEY(vr::VrTabHelper);
 
-namespace vr_shell {
+namespace vr {
 
 VrTabHelper::VrTabHelper(content::WebContents* contents)
     : web_contents_(contents) {}
@@ -44,4 +42,4 @@
   return vr_tab_helper->is_in_vr();
 }
 
-}  // namespace vr_shell
+}  // namespace vr
diff --git a/chrome/browser/android/vr_shell/vr_tab_helper.h b/chrome/browser/vr/vr_tab_helper.h
similarity index 79%
rename from chrome/browser/android/vr_shell/vr_tab_helper.h
rename to chrome/browser/vr/vr_tab_helper.h
index 315ce623..b24f3a0 100644
--- a/chrome/browser/android/vr_shell/vr_tab_helper.h
+++ b/chrome/browser/vr/vr_tab_helper.h
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_ANDROID_VR_SHELL_VR_TAB_HELPER_H_
-#define CHROME_BROWSER_ANDROID_VR_SHELL_VR_TAB_HELPER_H_
+#ifndef CHROME_BROWSER_VR_VR_TAB_HELPER_H_
+#define CHROME_BROWSER_VR_VR_TAB_HELPER_H_
 
 #include "base/macros.h"
 #include "content/public/browser/web_contents_user_data.h"
 
-namespace vr_shell {
+namespace vr {
 
 class VrTabHelper : public content::WebContentsUserData<VrTabHelper> {
  public:
@@ -33,6 +33,6 @@
   DISALLOW_COPY_AND_ASSIGN(VrTabHelper);
 };
 
-}  // namespace vr_shell
+}  // namespace vr
 
-#endif  // CHROME_BROWSER_ANDROID_VR_SHELL_VR_TAB_HELPER_H_
+#endif  // CHROME_BROWSER_VR_VR_TAB_HELPER_H_
diff --git a/chrome/browser/web_applications/web_app_mac_unittest.mm b/chrome/browser/web_applications/web_app_mac_unittest.mm
index 148470b..706b2ae 100644
--- a/chrome/browser/web_applications/web_app_mac_unittest.mm
+++ b/chrome/browser/web_applications/web_app_mac_unittest.mm
@@ -324,7 +324,8 @@
   EXPECT_EQ(product_logo.Height(), [image size].height);
 }
 
-TEST_F(WebAppShortcutCreatorTest, RevealAppShimInFinder) {
+// Disabled, sometimes crashes on "Mac10.10 tests". https://crbug.com/741642
+TEST_F(WebAppShortcutCreatorTest, DISABLED_RevealAppShimInFinder) {
   WebAppShortcutCreatorMock shortcut_creator(app_data_dir_, info_.get());
   EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
       .WillRepeatedly(Return(destination_dir_));
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index cd8f5638..a11cec4 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -134,7 +134,7 @@
 #if defined(OS_ANDROID)
 // Experiment to extract structured metadata for app indexing.
 const base::Feature kCopylessPaste{"CopylessPaste",
-                                   base::FEATURE_DISABLED_BY_DEFAULT};
+                                   base::FEATURE_ENABLED_BY_DEFAULT};
 #endif
 
 #if defined(OS_WIN)
diff --git a/chrome/common/safe_browsing/archive_analyzer_results.h b/chrome/common/safe_browsing/archive_analyzer_results.h
index 0728d93..a9635aa 100644
--- a/chrome/common/safe_browsing/archive_analyzer_results.h
+++ b/chrome/common/safe_browsing/archive_analyzer_results.h
@@ -11,6 +11,7 @@
 #include <vector>
 
 #include "base/files/file_path.h"
+#include "build/build_config.h"
 #include "components/safe_browsing/csd.pb.h"
 
 namespace safe_browsing {
@@ -22,6 +23,9 @@
   google::protobuf::RepeatedPtrField<ClientDownloadRequest_ArchivedBinary>
       archived_binary;
   std::vector<base::FilePath> archived_archive_filenames;
+#if defined(OS_MACOSX)
+  std::vector<uint8_t> signature_blob;
+#endif  // OS_MACOSX
   ArchiveAnalyzerResults();
   ArchiveAnalyzerResults(const ArchiveAnalyzerResults& other);
   ~ArchiveAnalyzerResults();
diff --git a/chrome/common/safe_browsing/safe_archive_analyzer_param_traits.h b/chrome/common/safe_browsing/safe_archive_analyzer_param_traits.h
index cab667f2..95f428c5f 100644
--- a/chrome/common/safe_browsing/safe_archive_analyzer_param_traits.h
+++ b/chrome/common/safe_browsing/safe_archive_analyzer_param_traits.h
@@ -8,6 +8,7 @@
 #error FULL_SAFE_BROWSING should be defined.
 #endif
 
+#include "build/build_config.h"
 #include "chrome/common/safe_browsing/archive_analyzer_results.h"
 #include "chrome/common/safe_browsing/ipc_protobuf_message_macros.h"
 #include "chrome/common/safe_browsing/protobuf_message_param_traits.h"
@@ -91,4 +92,7 @@
   IPC_STRUCT_TRAITS_MEMBER(has_archive)
   IPC_STRUCT_TRAITS_MEMBER(archived_binary)
   IPC_STRUCT_TRAITS_MEMBER(archived_archive_filenames)
+#if defined(OS_MACOSX)
+  IPC_STRUCT_TRAITS_MEMBER(signature_blob)
+#endif  // OS_MACOSX
 IPC_STRUCT_TRAITS_END()
diff --git a/chrome/installer/zucchini/BUILD.gn b/chrome/installer/zucchini/BUILD.gn
index 323b011..6f97b1b1b 100644
--- a/chrome/installer/zucchini/BUILD.gn
+++ b/chrome/installer/zucchini/BUILD.gn
@@ -13,6 +13,8 @@
     "disassembler.cc",
     "disassembler.h",
     "image_utils.h",
+    "io_utils.cc",
+    "io_utils.h",
     "suffix_array.h",
     "typed_value.h",
   ]
@@ -45,6 +47,7 @@
   sources = [
     "buffer_view_unittest.cc",
     "crc32_unittest.cc",
+    "io_utils_unittest.cc",
     "suffix_array_unittest.cc",
     "typed_value_unittest.cc",
   ]
diff --git a/chrome/installer/zucchini/io_utils.cc b/chrome/installer/zucchini/io_utils.cc
new file mode 100644
index 0000000..e889632
--- /dev/null
+++ b/chrome/installer/zucchini/io_utils.cc
@@ -0,0 +1,52 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/installer/zucchini/io_utils.h"
+
+#include <iostream>
+
+namespace zucchini {
+
+/******** LimitedOutputStream::StreamBuf ********/
+
+LimitedOutputStream::StreamBuf::StreamBuf(std::ostream& os, int limit)
+    : os_(os), limit_(limit) {}
+
+LimitedOutputStream::StreamBuf::~StreamBuf() {
+  // Display warning in case we forget to flush data with std::endl.
+  if (!str().empty()) {
+    std::cerr << "Warning: LimitedOutputStream has " << str().length()
+              << " bytes of unflushed output." << std::endl;
+  }
+}
+
+int LimitedOutputStream::StreamBuf::sync() {
+  if (full()) {
+    str("");
+    return 0;
+  }
+  os_ << str();
+  str("");
+  if (++counter_ >= limit_)
+    os_ << "(Additional output suppressed)\n";
+  os_.flush();
+  return 0;
+}
+
+/******** LimitedOutputStream ********/
+
+LimitedOutputStream::LimitedOutputStream(std::ostream& os, int limit)
+    : std::ostream(&buf_), buf_(os, limit) {}
+
+/******** PrefixSep ********/
+
+std::ostream& operator<<(std::ostream& ostr, PrefixSep& obj) {
+  if (obj.first_)
+    obj.first_ = false;
+  else
+    ostr << obj.sep_str_;
+  return ostr;
+}
+
+}  // namespace zucchini
diff --git a/chrome/installer/zucchini/io_utils.h b/chrome/installer/zucchini/io_utils.h
new file mode 100644
index 0000000..76e0075
--- /dev/null
+++ b/chrome/installer/zucchini/io_utils.h
@@ -0,0 +1,144 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_INSTALLER_ZUCCHINI_IO_UTILS_H_
+#define CHROME_INSTALLER_ZUCCHINI_IO_UTILS_H_
+
+#include <cctype>
+#include <istream>
+#include <ostream>
+#include <sstream>
+#include <string>
+
+#include "base/macros.h"
+
+namespace zucchini {
+
+// An std::ostream wrapper that that limits number of std::endl lines to output,
+// useful for preventing excessive debug message output. Usage requires some
+// work by the caller. Sample:
+//   static LimitedOutputStream los(std::cerr, 10);
+//   if (!los.full()) {
+//     ...  // Prepare message. Block may be skipped so don't do other work!
+//     los << message;
+//     los << std::endl;  // Important!
+//   }
+class LimitedOutputStream : public std::ostream {
+ private:
+  class StreamBuf : public std::stringbuf {
+   public:
+    StreamBuf(std::ostream& os, int limit);
+    ~StreamBuf();
+
+    int sync() override;
+    bool full() const { return counter_ >= limit_; }
+
+   private:
+    std::ostream& os_;
+    const int limit_;
+    int counter_ = 0;
+  };
+
+ public:
+  LimitedOutputStream(std::ostream& os, int limit);
+  bool full() const { return buf_.full(); }
+
+ private:
+  StreamBuf buf_;
+
+  DISALLOW_COPY_AND_ASSIGN(LimitedOutputStream);
+};
+
+// A class to render hexadecimal numbers for std::ostream with 0-padding. This
+// is more concise and flexible than stateful STL manipulator alternatives; so:
+//   std::ios old_fmt(nullptr);
+//   old_fmt.copyfmt(std::cout);
+//   std::cout << std::uppercase << std::hex;
+//   std::cout << std::setfill('0') << std::setw(8) << int_data << std::endl;
+//   std::cout.copyfmt(old_fmt);
+// can be expressed as:
+//   std::cout << AxHex<8>(int_data) << std::endl;
+template <int N, typename T = uint32_t>
+struct AsHex {
+  explicit AsHex(T value_in) : value(value_in) {}
+  T value;
+};
+
+template <int N, typename T>
+std::ostream& operator<<(std::ostream& os, const AsHex<N, T>& as_hex) {
+  char buf[N + 1];
+  buf[N] = '\0';
+  T value = as_hex.value;
+  for (int i = N - 1; i >= 0; --i, value >>= 4)
+    buf[i] = "0123456789ABCDEF"[static_cast<int>(value & 0x0F)];
+  if (value)
+    os << "...";  // To indicate data truncation, or negative values.
+  os << buf;
+  return os;
+}
+
+// An output manipulator to simplify printing list separators. Sample usage:
+//   PrefixSep sep(",");
+//   for (int i : {3, 1, 4, 1, 5, 9})
+//      std::cout << sep << i;
+//   std::cout << std::endl;  // Outputs "3,1,4,1,5,9\n".
+class PrefixSep {
+ public:
+  explicit PrefixSep(const std::string& sep_str) : sep_str_(sep_str) {}
+
+  friend std::ostream& operator<<(std::ostream& ostr, PrefixSep& obj);
+
+ private:
+  std::string sep_str_;
+  bool first_ = true;
+
+  DISALLOW_COPY_AND_ASSIGN(PrefixSep);
+};
+
+// An input manipulator that dictates the expected next character in
+// |std::istream|, and invalidates the stream if expectation is not met.
+class EatChar {
+ public:
+  explicit EatChar(char ch) : ch_(ch) {}
+
+  friend inline std::istream& operator>>(std::istream& istr,
+                                         const EatChar& obj) {
+    if (!istr.fail() && istr.get() != obj.ch_)
+      istr.setstate(std::ios_base::failbit);
+    return istr;
+  }
+
+ private:
+  char ch_;
+
+  DISALLOW_COPY_AND_ASSIGN(EatChar);
+};
+
+// An input manipulator that reads an unsigned integer from |std::istream|,
+// and invalidates the stream on failure. Intolerant of leading white spaces,
+template <typename T>
+class StrictUInt {
+ public:
+  explicit StrictUInt(T& var) : var_(var) {}
+  StrictUInt(const StrictUInt&) = default;
+
+  friend std::istream& operator>>(std::istream& istr, StrictUInt<T> obj) {
+    if (!istr.fail() && !::isdigit(istr.peek())) {
+      istr.setstate(std::ios_base::failbit);
+      return istr;
+    }
+    return istr >> obj.var_;
+  }
+
+ private:
+  T& var_;
+};
+
+// Stub out uint8_t: istream treats it as char, and value won't be read as int!
+template <>
+struct StrictUInt<uint8_t> {};
+
+}  // namespace zucchini
+
+#endif  // CHROME_INSTALLER_ZUCCHINI_IO_UTILS_H_
diff --git a/chrome/installer/zucchini/io_utils_unittest.cc b/chrome/installer/zucchini/io_utils_unittest.cc
new file mode 100644
index 0000000..95acbba5
--- /dev/null
+++ b/chrome/installer/zucchini/io_utils_unittest.cc
@@ -0,0 +1,159 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/installer/zucchini/io_utils.h"
+
+#include <sstream>
+#include <string>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace zucchini {
+
+TEST(IOUtilsTest, LimitedOutputStream) {
+  std::ostringstream oss;
+  LimitedOutputStream los(oss, 3);
+  EXPECT_FALSE(los.full());
+  EXPECT_EQ("", oss.str());
+  // Line 1.
+  los << "a" << 1 << "b" << 2 << "c" << 3 << std::endl;
+  EXPECT_FALSE(los.full());
+  EXPECT_EQ("a1b2c3\n", oss.str());
+  // Line 2.
+  oss.str("");
+  los << "\r\r\n\n" << std::endl;  // Manual new lines don't count.
+  EXPECT_FALSE(los.full());
+  EXPECT_EQ("\r\r\n\n\n", oss.str());
+  // Line 3.
+  oss.str("");
+  los << "blah" << 137;
+  EXPECT_FALSE(los.full());
+  los << std::endl;
+  EXPECT_TRUE(los.full());
+  EXPECT_EQ("blah137\n(Additional output suppressed)\n", oss.str());
+  // Not testing adding more lines: the behavior is undefined since we rely on
+  // caller suppressing output if |los.full()| is true.
+}
+
+TEST(IOUtilsTest, AsHex) {
+  std::ostringstream oss;
+  // Helper for single-line tests. Eats dummy std::ostream& from operator<<().
+  auto extract = [&oss](std::ostream&) -> std::string {
+    std::string ret = oss.str();
+    oss.str("");
+    return ret;
+  };
+
+  EXPECT_EQ("00000000", extract(oss << AsHex<8>(0)));
+  EXPECT_EQ("12345678", extract(oss << AsHex<8>(0x12345678U)));
+  EXPECT_EQ("9ABCDEF0", extract(oss << AsHex<8>(0x9ABCDEF0U)));
+  EXPECT_EQ("(00000064)", extract(oss << "(" << AsHex<8>(100) << ")"));
+  EXPECT_EQ("00FFFF", extract(oss << AsHex<6>(0xFFFFU)));
+  EXPECT_EQ("FFFF", extract(oss << AsHex<4>(0xFFFFU)));
+  EXPECT_EQ("...FF", extract(oss << AsHex<2>(0xFFFFU)));
+  EXPECT_EQ("...00", extract(oss << AsHex<2>(0x100U)));
+  EXPECT_EQ("FF\n", extract(oss << AsHex<2>(0xFFU) << std::endl));
+  EXPECT_EQ("132457689BACDEF0",
+            extract(oss << AsHex<16, uint64_t>(0x132457689BACDEF0LLU)));
+  EXPECT_EQ("000000000001", extract(oss << AsHex<12, uint8_t>(1)));
+  EXPECT_EQ("00000089", extract(oss << AsHex<8, int32_t>(137)));
+  EXPECT_EQ("...FFFFFFFF", extract(oss << AsHex<8, int32_t>(-1)));
+  EXPECT_EQ("7FFF", extract(oss << AsHex<4, int16_t>(0x7FFFU)));
+  EXPECT_EQ("...8000", extract(oss << AsHex<4, int16_t>(0x8000U)));
+  EXPECT_EQ("8000", extract(oss << AsHex<4, uint16_t>(0x8000U)));
+}
+
+TEST(IOUtilsTest, PrefixSep) {
+  std::ostringstream oss;
+  PrefixSep sep(",");
+  oss << sep << 3;
+  EXPECT_EQ("3", oss.str());
+  oss << sep << 1;
+  EXPECT_EQ("3,1", oss.str());
+  oss << sep << 4 << sep << 1 << sep << "59";
+  EXPECT_EQ("3,1,4,1,59", oss.str());
+}
+
+TEST(IOUtilsTest, PrefixSepAlt) {
+  std::ostringstream oss;
+  PrefixSep sep("  ");
+  oss << sep << 3;
+  EXPECT_EQ("3", oss.str());
+  oss << sep << 1;
+  EXPECT_EQ("3  1", oss.str());
+  oss << sep << 4 << sep << 1 << sep << "59";
+  EXPECT_EQ("3  1  4  1  59", oss.str());
+}
+
+TEST(IOUtilsTest, EatChar) {
+  std::istringstream main_iss;
+  // Helper for single-line tests.
+  auto iss = [&main_iss](const std::string s) -> std::istringstream& {
+    main_iss.clear();
+    main_iss.str(s);
+    return main_iss;
+  };
+
+  EXPECT_TRUE(iss("a,1") >> EatChar('a') >> EatChar(',') >> EatChar('1'));
+  EXPECT_FALSE(iss("a,a") >> EatChar('a') >> EatChar(',') >> EatChar('1'));
+  EXPECT_FALSE(iss("a") >> EatChar('a') >> EatChar(',') >> EatChar('1'));
+  EXPECT_FALSE(iss("x") >> EatChar('X'));
+  EXPECT_TRUE(iss("_\n") >> EatChar('_') >> EatChar('\n'));
+}
+
+TEST(IOUtilsTest, StrictUInt) {
+  std::istringstream main_iss;
+  // Helper for single-line tests.
+  auto iss = [&main_iss](const std::string& s) -> std::istringstream& {
+    main_iss.clear();
+    main_iss.str(s);
+    return main_iss;
+  };
+
+  uint32_t u32 = 0;
+  EXPECT_TRUE(iss("1234") >> StrictUInt<uint32_t>(u32));
+  EXPECT_EQ(uint32_t(1234), u32);
+  EXPECT_TRUE(iss("001234") >> StrictUInt<uint32_t>(u32));
+  EXPECT_EQ(uint32_t(1234), u32);
+  EXPECT_FALSE(iss("blahblah") >> StrictUInt<uint32_t>(u32));
+  EXPECT_EQ(uint32_t(1234), u32);  // No overwrite on failure.
+  EXPECT_TRUE(iss("137suffix") >> StrictUInt<uint32_t>(u32));
+  EXPECT_EQ(uint32_t(137), u32);
+  EXPECT_FALSE(iss(" 1234") >> StrictUInt<uint32_t>(u32));
+  EXPECT_FALSE(iss("-1234") >> StrictUInt<uint32_t>(u32));
+
+  uint16_t u16 = 0;
+  EXPECT_TRUE(iss("65535") >> StrictUInt<uint16_t>(u16));
+  EXPECT_EQ(uint16_t(65535), u16);
+  EXPECT_FALSE(iss("65536") >> StrictUInt<uint16_t>(u16));  // Overflow.
+
+  uint64_t u64 = 0;
+  EXPECT_TRUE(iss("1000000000001") >> StrictUInt<uint64_t>(u64));
+  EXPECT_EQ(uint64_t(1000000000001LL), u64);
+
+  // uint8_t is stubbed out, so no tests for it.
+}
+
+TEST(IOUtilsTest, ParseSimpleEquations) {
+  std::istringstream iss("123+456=579,4-3=1");
+  uint32_t a = 0;
+  uint32_t b = 0;
+  uint32_t c = 0;
+  EXPECT_TRUE(iss >> StrictUInt<uint32_t>(a) >> EatChar('+') >>
+              StrictUInt<uint32_t>(b) >> EatChar('=') >>
+              StrictUInt<uint32_t>(c));
+  EXPECT_EQ(uint32_t(123), a);
+  EXPECT_EQ(uint32_t(456), b);
+  EXPECT_EQ(uint32_t(579), c);
+  EXPECT_TRUE(iss >> EatChar(','));
+  EXPECT_TRUE(iss >> StrictUInt<uint32_t>(a) >> EatChar('-') >>
+              StrictUInt<uint32_t>(b) >> EatChar('=') >>
+              StrictUInt<uint32_t>(c));
+  EXPECT_EQ(uint32_t(4), a);
+  EXPECT_EQ(uint32_t(3), b);
+  EXPECT_EQ(uint32_t(1), c);
+}
+
+}  // namespace zucchini
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 4c185565..dd3049c3 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1534,6 +1534,7 @@
       "../browser/ui/toolbar/mock_component_toolbar_actions_factory.cc",
       "../browser/ui/toolbar/mock_component_toolbar_actions_factory.h",
       "../browser/ui/update_chrome_dialog_browsertest.cc",
+      "../browser/ui/views/apps/app_info_dialog/app_info_dialog_views_browsertest.cc",
       "../browser/ui/views/chrome_cleaner_dialog_browsertest_win.cc",
       "../browser/ui/views/extensions/chooser_dialog_view_browsertest.cc",
       "../browser/ui/views/hung_renderer_view_browsertest.cc",
@@ -2510,6 +2511,7 @@
     if (is_win) {
       sources += [
         "../browser/extensions/api/networking_private/networking_private_credentials_getter_browsertest.cc",
+        "../browser/ui/views/accessibility/invert_bubble_view_browsertest.cc",
         "../browser/ui/views/settings_reset_prompt_dialog_browsertest.cc",
       ]
     }
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/suggestions/FakeSuggestionsSource.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/suggestions/FakeSuggestionsSource.java
index 698f061..2ef79e0 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/suggestions/FakeSuggestionsSource.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/suggestions/FakeSuggestionsSource.java
@@ -9,6 +9,7 @@
 import android.graphics.Bitmap;
 
 import org.chromium.base.Callback;
+import org.chromium.base.ObserverList;
 import org.chromium.base.ThreadUtils;
 import org.chromium.chrome.browser.ntp.cards.SuggestionsCategoryInfo;
 import org.chromium.chrome.browser.ntp.snippets.CategoryInt;
@@ -28,7 +29,7 @@
  * A fake Suggestions source for use in unit and instrumentation tests.
  */
 public class FakeSuggestionsSource implements SuggestionsSource {
-    private SuggestionsSource.Observer mObserver;
+    private ObserverList<Observer> mObserverList = new ObserverList<>();
     private final List<Integer> mCategories = new ArrayList<>();
     private final Map<Integer, List<SnippetArticle>> mSuggestions = new HashMap<>();
     private final Map<Integer, Integer> mCategoryStatus = new LinkedHashMap<>();
@@ -45,6 +46,9 @@
             new HashMap<>();
     private final Map<Integer, Integer> mDismissedCategoryStatus = new LinkedHashMap<>();
     private final Map<Integer, SuggestionsCategoryInfo> mDismissedCategoryInfo = new HashMap<>();
+
+    private boolean mRemoteSuggestionsEnabled = true;
+
     /**
      * Sets the status to be returned for a given category.
      */
@@ -55,7 +59,7 @@
         } else if (!mCategories.contains(category)) {
             mCategories.add(category);
         }
-        if (mObserver != null) mObserver.onCategoryStatusChanged(category, status);
+        for (Observer observer : mObserverList) observer.onCategoryStatusChanged(category, status);
     }
 
     /**
@@ -65,7 +69,7 @@
             @CategoryInt int category, List<SnippetArticle> suggestions) {
         // Copy the suggestions list so that it can't be modified anymore.
         mSuggestions.put(category, new ArrayList<>(suggestions));
-        if (mObserver != null) mObserver.onNewSuggestions(category);
+        for (Observer observer : mObserverList) observer.onNewSuggestions(category);
     }
 
     /**
@@ -136,14 +140,16 @@
                 break;
             }
         }
-        mObserver.onSuggestionInvalidated(category, idWithinCategory);
+        for (Observer observer : mObserverList) {
+            observer.onSuggestionInvalidated(category, idWithinCategory);
+        }
     }
 
     /**
      * Notifies the observer that a full refresh is required.
      */
     public void fireFullRefreshRequired() {
-        mObserver.onFullRefreshRequired();
+        for (Observer observer : mObserverList) observer.onFullRefreshRequired();
     }
 
     /**
@@ -160,6 +166,15 @@
     public void fetchRemoteSuggestions() {}
 
     @Override
+    public boolean areRemoteSuggestionsEnabled() {
+        return mRemoteSuggestionsEnabled;
+    }
+
+    public void setRemoteSuggestionsEnabled(boolean enabled) {
+        mRemoteSuggestionsEnabled = enabled;
+    }
+
+    @Override
     public void dismissSuggestion(SnippetArticle suggestion) {
         for (List<SnippetArticle> suggestions : mSuggestions.values()) {
             suggestions.remove(suggestion);
@@ -229,8 +244,13 @@
     }
 
     @Override
-    public void setObserver(Observer observer) {
-        mObserver = observer;
+    public void addObserver(Observer observer) {
+        mObserverList.addObserver(observer);
+    }
+
+    @Override
+    public void removeObserver(Observer observer) {
+        mObserverList.removeObserver(observer);
     }
 
     @Override
diff --git a/chrome/test/base/tracing_browsertest.cc b/chrome/test/base/tracing_browsertest.cc
index 5347d14..d343a851 100644
--- a/chrome/test/base/tracing_browsertest.cc
+++ b/chrome/test/base/tracing_browsertest.cc
@@ -101,13 +101,8 @@
   }
 };
 
-// crbug.com/708487.
-#if defined(OS_MACOSX) && defined(ADDRESS_SANITIZER)
-#define MAYBE_TestMemoryInfra DISABLED_TestMemoryInfra
-#else
-#define MAYBE_TestMemoryInfra TestMemoryInfra
-#endif  // defined(OS_MACOSX) && defined(ADDRESS_SANITIZER)
-IN_PROC_BROWSER_TEST_F(TracingBrowserTest, MAYBE_TestMemoryInfra) {
+// crbug.com/708487
+IN_PROC_BROWSER_TEST_F(TracingBrowserTest, DISABLED_TestMemoryInfra) {
   PerformDumpMemoryTestActions(
       base::trace_event::TraceConfig(
           base::trace_event::TraceConfigMemoryTestUtil::
@@ -115,13 +110,8 @@
       base::trace_event::MemoryDumpLevelOfDetail::DETAILED);
 }
 
-// crbug.com/708487.
-#if defined(OS_MACOSX) && defined(ADDRESS_SANITIZER)
-#define MAYBE_TestBackgroundMemoryInfra DISABLED_TestBackgroundMemoryInfra
-#else
-#define MAYBE_TestBackgroundMemoryInfra TestBackgroundMemoryInfra
-#endif  // defined(OS_MACOSX) && defined(ADDRESS_SANITIZER)
-IN_PROC_BROWSER_TEST_F(TracingBrowserTest, MAYBE_TestBackgroundMemoryInfra) {
+// crbug.com/708487
+IN_PROC_BROWSER_TEST_F(TracingBrowserTest, DISABLED_TestBackgroundMemoryInfra) {
   PerformDumpMemoryTestActions(
       base::trace_event::TraceConfig(
           base::trace_event::TraceConfigMemoryTestUtil::
diff --git a/chrome/test/data/android/webvr_instrumentation/html/test_controller_scrolling.html b/chrome/test/data/android/webvr_instrumentation/html/test_controller_scrolling.html
new file mode 100644
index 0000000..7a15f8cd
--- /dev/null
+++ b/chrome/test/data/android/webvr_instrumentation/html/test_controller_scrolling.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<!--
+Used to test that vertical and horizontal scrolling work with the Daydream
+controller.
+-->
+<html>
+  <head>
+    <style>
+      body {
+        width: 10000px;
+        height: 10000px;
+      }
+    </style>
+  </head>
+  <body>
+    This is a test page.
+  </body>
+</html>
diff --git a/chrome/test/data/safe_browsing/mach_o/Makefile b/chrome/test/data/safe_browsing/mach_o/Makefile
index 199685b..d0a7498 100644
--- a/chrome/test/data/safe_browsing/mach_o/Makefile
+++ b/chrome/test/data/safe_browsing/mach_o/Makefile
@@ -6,6 +6,12 @@
 # This must match the commonName in codesign.cfg.
 KEYCHAIN_IDENTITY=untrusted@goat.local
 
+# Funcitons to add and remove codesigning identity to user's keychain. These 
+# are necessary since the codesign utility no longer supports the -k option,
+# which reads the identity from a file.
+pre-build = security import codesign.key && security import codesign.crt
+post-build = security delete-identity -c untrusted@goat.local
+
 executable32: src.c
 	clang -m32 -o $@ $^
 
@@ -34,58 +40,57 @@
 	openssl x509 -req -signkey codesign.key -sha256 \
 		-extfile codesign.cfg -extensions req_attrs -in $< -out $@
 
-codesign.keychain: codesign.key codesign.crt
-	security create-keychain -p $(KEYCHAIN_PASSWORD) $(PWD)/$@
-	security unlock-keychain -p $(KEYCHAIN_PASSWORD) $(PWD)/$@
-	certtool i ./codesign.crt k=$(PWD)/$@ r=./codesign.key
-
-signedexecutable32: executable32 codesign.keychain
+signedexecutable32: executable32 codesign.crt
+	$(call pre-build)
 	cp $< $@
-	security unlock-keychain -p $(KEYCHAIN_PASSWORD) \
-		$(PWD)/codesign.keychain
-	codesign -s $(KEYCHAIN_IDENTITY) --keychain $(PWD)/codesign.keychain $@
+	codesign -s $(KEYCHAIN_IDENTITY) $@
+	$(call post-build)
 
-libsigned64.dylib: lib64.dylib codesign.keychain
+libsigned64.dylib: lib64.dylib codesign.crt
+	$(call pre-build)
 	cp $< $@
-	security unlock-keychain -p $(KEYCHAIN_PASSWORD) \
-		$(PWD)/codesign.keychain
-	codesign -s $(KEYCHAIN_IDENTITY) --keychain $(PWD)/codesign.keychain $@
+	codesign -s $(KEYCHAIN_IDENTITY) $@
+	$(call post-build)
 
-signedexecutablefat: executablefat codesign.keychain
+signedexecutablefat: executablefat codesign.crt
+	$(call pre-build)
 	cp $< $@
-	security unlock-keychain -p $(KEYCHAIN_PASSWORD) \
-		$(PWD)/codesign.keychain
-	codesign -s $(KEYCHAIN_IDENTITY) --keychain $(PWD)/codesign.keychain \
-		$@ --all-architectures
+	codesign -s $(KEYCHAIN_IDENTITY) $@ --all-architectures
+	$(call post-build)
+
+signed-archive.dmg: test-bundle.app codesign.crt
+	$(call pre-build)
+	hdiutil create -srcfolder test-bundle.app -format UDZO -layout \
+		SPUD -volname "Signed Archive" -ov $@
+	codesign -s $(KEYCHAIN_IDENTITY) $@
+	$(call post-build)
 
 .PHONY: test-bundle.app
 test-bundle.app: signedexecutablefat libsigned64.dylib executable32
+	$(call pre-build)
 	ditto base-bundle.app $@
 	ditto $< $@/Contents/MacOS/test-bundle
 	ditto $(word 2,$^) $@/Contents/Frameworks/$(word 2,$^)
 	ditto $(word 3,$^) $@/Contents/Resources/$(word 3,$^)
-	security unlock-keychain -p $(KEYCHAIN_PASSWORD) \
-		$(PWD)/codesign.keychain
-	codesign -f -s $(KEYCHAIN_IDENTITY) --keychain $(PWD)/codesign.keychain \
-		$@ --all-architectures --resource-rules ResourceRules
+	codesign -f -s $(KEYCHAIN_IDENTITY) $@ --all-architectures \
+		--resource-rules ResourceRules
+	$(call post-build)
 
 .PHONY: modified-bundle.app
 modified-bundle.app: test-bundle.app lib32.dylib executable64
+	$(call pre-build)
 	ditto $< $@
 	echo "<xml/>" > $@/Contents/Resources/Base.lproj/InfoPlist.strings
-	security unlock-keychain -p $(KEYCHAIN_PASSWORD) \
-		$(PWD)/codesign.keychain
-	codesign -f -s $(KEYCHAIN_IDENTITY) --keychain $(PWD)/codesign.keychain \
-		$@ --all-architectures --resource-rules ResourceRules
+	codesign -f -s $(KEYCHAIN_IDENTITY) $@ --all-architectures \
+		--resource-rules ResourceRules
 	echo "BAD" > $@/Contents/Resources/Base.lproj/InfoPlist.strings
 	touch $@/Contents/Resources/codesign.cfg
 	ditto $(word 2,$^) $@/Contents/Frameworks/libsigned64.dylib
 	ditto $(word 3,$^) $@/Contents/Resources/executable32
 	echo "foo" >> $@/Contents/Resources/Base.lproj/MainMenu.nib
-	security unlock-keychain -p $(KEYCHAIN_PASSWORD) \
-		$(PWD)/codesign.keychain
-	codesign -f -s $(KEYCHAIN_IDENTITY) --keychain $(PWD)/codesign.keychain \
+	codesign -f -s $(KEYCHAIN_IDENTITY) \
 		$@/Contents/Resources/Base.lproj/MainMenu.nib
+	$(call post-build)
 
 .PHONY: modified-bundle-and-exec.app
 modified-bundle-and-exec.app: test-bundle.app lib32.dylib executable64
@@ -110,10 +115,10 @@
 
 .PHONY: modified-localization.app
 modified-localization.app: test-bundle.app
+	$(call pre-build)
 	ditto $< $@
 	echo "<xml/>" > $@/Contents/Resources/Base.lproj/InfoPlist.strings
-	security unlock-keychain -p $(KEYCHAIN_PASSWORD) \
-		$(PWD)/codesign.keychain
-	codesign -f -s $(KEYCHAIN_IDENTITY) --keychain $(PWD)/codesign.keychain \
-		$@ --all-architectures --resource-rules ResourceRules
+	codesign -f -s $(KEYCHAIN_IDENTITY) $@ --all-architectures \
+		--resource-rules ResourceRules
 	echo "CORRUPT" > $@/Contents/Resources/Base.lproj/InfoPlist.strings
+	$(call post-build)
diff --git a/chrome/test/data/safe_browsing/mach_o/README b/chrome/test/data/safe_browsing/mach_o/README
index 60b51d1..848fb2b 100644
--- a/chrome/test/data/safe_browsing/mach_o/README
+++ b/chrome/test/data/safe_browsing/mach_o/README
@@ -12,3 +12,8 @@
 
 The `executableppc' file has no Makefile target, as modern Macs cannot build
 for PPC. This file was created in a 10.6 VM with Xcode 3.2.6.
+
+Creating the signed binaries requires importing a temporary codesigning 
+identity into the current user's keychain. This is because the `codesign -k` 
+flag, which specifies a specific keychain file to use, is no longer respected.
+The temporary identity will be removed after the test binary is signed.
\ No newline at end of file
diff --git a/chrome/test/data/safe_browsing/mach_o/codesign.keychain b/chrome/test/data/safe_browsing/mach_o/codesign.keychain
deleted file mode 100644
index 0e4bf37..0000000
--- a/chrome/test/data/safe_browsing/mach_o/codesign.keychain
+++ /dev/null
Binary files differ
diff --git a/chrome/test/data/safe_browsing/mach_o/signed-archive-signature.data b/chrome/test/data/safe_browsing/mach_o/signed-archive-signature.data
new file mode 100644
index 0000000..f99be07
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/signed-archive-signature.data
Binary files differ
diff --git a/chrome/test/data/safe_browsing/mach_o/signed-archive.dmg b/chrome/test/data/safe_browsing/mach_o/signed-archive.dmg
new file mode 100644
index 0000000..93480e6
--- /dev/null
+++ b/chrome/test/data/safe_browsing/mach_o/signed-archive.dmg
Binary files differ
diff --git a/chrome/utility/safe_browsing/mac/dmg_analyzer.cc b/chrome/utility/safe_browsing/mac/dmg_analyzer.cc
index 09ebf17..6ba4e583 100644
--- a/chrome/utility/safe_browsing/mac/dmg_analyzer.cc
+++ b/chrome/utility/safe_browsing/mac/dmg_analyzer.cc
@@ -126,6 +126,8 @@
   if (!iterator.Open())
     return;
 
+  results->signature_blob = iterator.GetCodeSignature();
+
   while (iterator.Next()) {
     std::unique_ptr<ReadStream> stream = iterator.GetReadStream();
     if (!stream || !feature_extractor.IsMachO(stream.get()))
diff --git a/chrome/utility/safe_browsing/mac/dmg_iterator.cc b/chrome/utility/safe_browsing/mac/dmg_iterator.cc
index 75f0512..8d883aa 100644
--- a/chrome/utility/safe_browsing/mac/dmg_iterator.cc
+++ b/chrome/utility/safe_browsing/mac/dmg_iterator.cc
@@ -35,6 +35,10 @@
   return partitions_.size() > 0;
 }
 
+const std::vector<uint8_t>& DMGIterator::GetCodeSignature() {
+  return udif_.GetCodeSignature();
+}
+
 bool DMGIterator::Next() {
   // Iterate through all the HFS partitions in the DMG file.
   for (; current_partition_ < partitions_.size(); ++current_partition_) {
diff --git a/chrome/utility/safe_browsing/mac/dmg_iterator.h b/chrome/utility/safe_browsing/mac/dmg_iterator.h
index 74f17b0..2f3c022 100644
--- a/chrome/utility/safe_browsing/mac/dmg_iterator.h
+++ b/chrome/utility/safe_browsing/mac/dmg_iterator.h
@@ -37,6 +37,10 @@
   // invalid element before the first item.
   bool Open();
 
+  // Returns the raw code signature file metadata. This will be empty for DMGs
+  // that are not signed.
+  const std::vector<uint8_t>& GetCodeSignature();
+
   // Advances the iterator to the next file item. Returns true on success
   // and false on end-of-iterator.
   bool Next();
diff --git a/chrome/utility/safe_browsing/mac/udif.cc b/chrome/utility/safe_browsing/mac/udif.cc
index 0c4be3f..dea3cf9 100644
--- a/chrome/utility/safe_browsing/mac/udif.cc
+++ b/chrome/utility/safe_browsing/mac/udif.cc
@@ -348,8 +348,7 @@
     : stream_(stream),
       partition_names_(),
       blocks_(),
-      block_size_(kSectorSize) {
-}
+      block_size_(kSectorSize) {}
 
 UDIFParser::~UDIFParser() {}
 
@@ -360,6 +359,10 @@
   return true;
 }
 
+const std::vector<uint8_t>& UDIFParser::GetCodeSignature() {
+  return signature_blob_;
+}
+
 size_t UDIFParser::GetNumberOfPartitions() {
   return blocks_.size();
 }
@@ -557,6 +560,37 @@
     partition_names_.push_back(partition_name);
   }
 
+  // The offsets in the trailer could be garbage in DMGs that aren't signed.
+  // Need a sanity check that the DMG has legit values for these fields.
+  if (trailer.code_signature_length != 0 && trailer_start > 0) {
+    auto code_signature_end =
+        base::CheckedNumeric<size_t>(trailer.code_signature_offset) +
+        trailer.code_signature_length;
+    if (code_signature_end.IsValid() &&
+        code_signature_end.ValueOrDie() <=
+            base::checked_cast<size_t>(trailer_start)) {
+      signature_blob_.resize(trailer.code_signature_length);
+
+      off_t code_signature_start =
+          stream_->Seek(trailer.code_signature_offset, SEEK_SET);
+      if (code_signature_start == -1)
+        return false;
+
+      size_t bytes_read = 0;
+
+      if (!stream_->Read(signature_blob_.data(), trailer.code_signature_length,
+                         &bytes_read)) {
+        DLOG(ERROR) << "Failed to read raw signature bytes";
+        return false;
+      }
+
+      if (bytes_read != trailer.code_signature_length) {
+        DLOG(ERROR) << "Read unexpected number of raw signature bytes";
+        return false;
+      }
+    }
+  }
+
   return true;
 }
 
diff --git a/chrome/utility/safe_browsing/mac/udif.h b/chrome/utility/safe_browsing/mac/udif.h
index fbb17c9..13aee9db 100644
--- a/chrome/utility/safe_browsing/mac/udif.h
+++ b/chrome/utility/safe_browsing/mac/udif.h
@@ -51,6 +51,9 @@
   // If this returns false, it is not legal to call any other methods.
   bool Parse();
 
+  // Returns the blob of DMG signature data.
+  const std::vector<uint8_t>& GetCodeSignature();
+
   // Returns the number of partitions in this UDIF image.
   size_t GetNumberOfPartitions();
 
@@ -79,6 +82,7 @@
   // All blocks in the UDIF image.
   std::vector<std::unique_ptr<const UDIFBlock>> blocks_;
   uint16_t block_size_;  // The image's block size, in bytes.
+  std::vector<uint8_t> signature_blob_;  // DMG signature.
 
   DISALLOW_COPY_AND_ASSIGN(UDIFParser);
 };
diff --git a/chromeos/attestation/attestation_flow_unittest.cc b/chromeos/attestation/attestation_flow_unittest.cc
index e9cab9df..23ab18e6 100644
--- a/chromeos/attestation/attestation_flow_unittest.cc
+++ b/chromeos/attestation/attestation_flow_unittest.cc
@@ -6,18 +6,16 @@
 #include <utility>
 
 #include "base/bind.h"
-#include "base/location.h"
 #include "base/memory/ptr_util.h"
 #include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
 #include "base/test/scoped_task_environment.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/tick_clock.h"
 #include "base/timer/timer.h"
 #include "chromeos/attestation/mock_attestation_flow.h"
 #include "chromeos/cryptohome/cryptohome_parameters.h"
 #include "chromeos/cryptohome/mock_async_method_caller.h"
-#include "chromeos/dbus/mock_cryptohome_client.h"
+#include "chromeos/dbus/fake_cryptohome_client.h"
 #include "components/signin/core/account_id/account_id.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -37,38 +35,10 @@
 
 namespace {
 
-void DBusCallbackFalse(const BoolDBusMethodCallback& callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::Bind(callback, DBUS_METHOD_CALL_SUCCESS, false));
-}
-
-void DBusCallbackTrue(const BoolDBusMethodCallback& callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::Bind(callback, DBUS_METHOD_CALL_SUCCESS, true));
-}
-
-void DBusCallbackFail(const BoolDBusMethodCallback& callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::Bind(callback, DBUS_METHOD_CALL_FAILURE, false));
-}
-
 void AsyncCallbackFalse(cryptohome::AsyncMethodCaller::Callback callback) {
   callback.Run(false, cryptohome::MOUNT_ERROR_NONE);
 }
 
-class FakeDBusData {
- public:
-  explicit FakeDBusData(const std::string& data) : data_(data) {}
-
-  void operator() (const CryptohomeClient::DataMethodCallback& callback) {
-    base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::Bind(callback, DBUS_METHOD_CALL_SUCCESS, true, data_));
-  }
-
- private:
-  std::string data_;
-};
-
 }  // namespace
 
 class AttestationFlowTest : public testing::Test {
@@ -105,14 +75,9 @@
   Sequence flow_order;
 
   // Use DBusCallbackFalse so the full enrollment flow is triggered.
-  chromeos::MockCryptohomeClient client;
-  EXPECT_CALL(client, TpmAttestationIsEnrolled(_))
-      .InSequence(flow_order)
-      .WillRepeatedly(Invoke(DBusCallbackFalse));
-
-  EXPECT_CALL(client, TpmAttestationIsPrepared(_))
-      .InSequence(flow_order)
-      .WillOnce(Invoke(DBusCallbackTrue));
+  chromeos::FakeCryptohomeClient client;
+  client.set_tpm_attestation_is_enrolled(false);
+  client.set_tpm_attestation_is_prepared(true);
 
   // Use StrictMock when we want to verify invocation frequency.
   StrictMock<cryptohome::MockAsyncMethodCaller> async_caller;
@@ -185,17 +150,21 @@
   // Verify the order of calls in a sequence.
   Sequence flow_order;
 
-  // Use DBusCallbackFalse so the full enrollment flow is triggered.
-  chromeos::MockCryptohomeClient client;
-  EXPECT_CALL(client, TpmAttestationIsEnrolled(_))
-      .InSequence(flow_order)
-      .WillRepeatedly(Invoke(DBusCallbackFalse));
+  // Custom FakeCryptohomeClient to emulate a situation where it takes a bit
+  // for attestation to be prepared.
+  class FakeCryptohomeClient : public chromeos::FakeCryptohomeClient {
+   public:
+    void TpmAttestationIsPrepared(
+        const BoolDBusMethodCallback& callback) override {
+      chromeos::FakeCryptohomeClient::TpmAttestationIsPrepared(callback);
+      // Second call (and later), returns true.
+      set_tpm_attestation_is_prepared(true);
+    }
+  };
 
-  // It will take a bit for attestation to be prepared.
-  EXPECT_CALL(client, TpmAttestationIsPrepared(_))
-      .InSequence(flow_order)
-      .WillOnce(Invoke(DBusCallbackFalse))
-      .WillOnce(Invoke(DBusCallbackTrue));
+  FakeCryptohomeClient client;
+  client.set_tpm_attestation_is_enrolled(false);
+  client.set_tpm_attestation_is_prepared(false);
 
   // Use StrictMock when we want to verify invocation frequency.
   StrictMock<cryptohome::MockAsyncMethodCaller> async_caller;
@@ -265,12 +234,9 @@
   StrictMock<cryptohome::MockAsyncMethodCaller> async_caller;
   async_caller.SetUp(false, cryptohome::MOUNT_ERROR_NONE);
 
-  chromeos::MockCryptohomeClient client;
-  EXPECT_CALL(client, TpmAttestationIsEnrolled(_))
-      .WillRepeatedly(Invoke(DBusCallbackFalse));
-
-  EXPECT_CALL(client, TpmAttestationIsPrepared(_))
-      .WillRepeatedly(Invoke(DBusCallbackFalse));
+  chromeos::FakeCryptohomeClient client;
+  client.set_tpm_attestation_is_enrolled(false);
+  client.set_tpm_attestation_is_prepared(false);
 
   // We're not expecting any server calls in this case; StrictMock will verify.
   std::unique_ptr<MockServerProxy> proxy(new StrictMock<MockServerProxy>());
@@ -299,12 +265,9 @@
   EXPECT_CALL(async_caller, AsyncTpmAttestationCreateEnrollRequest(_, _))
       .Times(1);
 
-  chromeos::MockCryptohomeClient client;
-  EXPECT_CALL(client, TpmAttestationIsEnrolled(_))
-      .WillRepeatedly(Invoke(DBusCallbackFalse));
-
-  EXPECT_CALL(client, TpmAttestationIsPrepared(_))
-      .WillOnce(Invoke(DBusCallbackTrue));
+  chromeos::FakeCryptohomeClient client;
+  client.set_tpm_attestation_is_enrolled(false);
+  client.set_tpm_attestation_is_prepared(true);
 
   // We're not expecting any server calls in this case; StrictMock will verify.
   std::unique_ptr<MockServerProxy> proxy(new StrictMock<MockServerProxy>());
@@ -330,12 +293,9 @@
   EXPECT_CALL(async_caller, AsyncTpmAttestationCreateEnrollRequest(_, _))
       .Times(1);
 
-  chromeos::MockCryptohomeClient client;
-  EXPECT_CALL(client, TpmAttestationIsEnrolled(_))
-      .WillRepeatedly(Invoke(DBusCallbackFalse));
-
-  EXPECT_CALL(client, TpmAttestationIsPrepared(_))
-      .WillOnce(Invoke(DBusCallbackTrue));
+  chromeos::FakeCryptohomeClient client;
+  client.set_tpm_attestation_is_enrolled(false);
+  client.set_tpm_attestation_is_prepared(true);
 
   std::unique_ptr<MockServerProxy> proxy(new StrictMock<MockServerProxy>());
   proxy->DeferToFake(false);
@@ -370,12 +330,9 @@
               AsyncTpmAttestationEnroll(_, fake_enroll_response, _))
       .WillOnce(WithArgs<2>(Invoke(AsyncCallbackFalse)));
 
-  chromeos::MockCryptohomeClient client;
-  EXPECT_CALL(client, TpmAttestationIsEnrolled(_))
-      .WillRepeatedly(Invoke(DBusCallbackFalse));
-
-  EXPECT_CALL(client, TpmAttestationIsPrepared(_))
-      .WillOnce(Invoke(DBusCallbackTrue));
+  chromeos::FakeCryptohomeClient client;
+  client.set_tpm_attestation_is_enrolled(false);
+  client.set_tpm_attestation_is_prepared(true);
 
   std::unique_ptr<MockServerProxy> proxy(new StrictMock<MockServerProxy>());
   proxy->DeferToFake(true);
@@ -413,9 +370,7 @@
                   kEnterpriseMachineKey, _))
       .Times(1);
 
-  chromeos::MockCryptohomeClient client;
-  EXPECT_CALL(client, TpmAttestationIsEnrolled(_))
-      .WillRepeatedly(Invoke(DBusCallbackTrue));
+  chromeos::FakeCryptohomeClient client;
 
   std::unique_ptr<MockServerProxy> proxy(new StrictMock<MockServerProxy>());
   proxy->DeferToFake(true);
@@ -447,9 +402,7 @@
                                 cryptohome::Identification(), "", _))
       .Times(1);
 
-  chromeos::MockCryptohomeClient client;
-  EXPECT_CALL(client, TpmAttestationIsEnrolled(_))
-      .WillRepeatedly(Invoke(DBusCallbackTrue));
+  chromeos::FakeCryptohomeClient client;
 
   // We're not expecting any server calls in this case; StrictMock will verify.
   std::unique_ptr<MockServerProxy> proxy(new StrictMock<MockServerProxy>());
@@ -476,9 +429,7 @@
                                 cryptohome::Identification(), "", _))
       .Times(1);
 
-  chromeos::MockCryptohomeClient client;
-  EXPECT_CALL(client, TpmAttestationIsEnrolled(_))
-      .WillRepeatedly(Invoke(DBusCallbackTrue));
+  chromeos::FakeCryptohomeClient client;
 
   std::unique_ptr<MockServerProxy> proxy(new StrictMock<MockServerProxy>());
   proxy->DeferToFake(false);
@@ -504,9 +455,8 @@
   // We're not expecting any async calls in this case; StrictMock will verify.
   StrictMock<cryptohome::MockAsyncMethodCaller> async_caller;
 
-  chromeos::MockCryptohomeClient client;
-  EXPECT_CALL(client, TpmAttestationIsEnrolled(_))
-      .WillRepeatedly(Invoke(DBusCallbackFail));
+  chromeos::FakeCryptohomeClient client;
+  client.SetServiceIsAvailable(false);
 
   // We're not expecting any server calls in this case; StrictMock will verify.
   std::unique_ptr<MockServerProxy> proxy(new StrictMock<MockServerProxy>());
@@ -541,13 +491,7 @@
                                                    kEnterpriseUserKey, _))
       .Times(1);
 
-  chromeos::MockCryptohomeClient client;
-  EXPECT_CALL(client, TpmAttestationIsEnrolled(_))
-      .WillRepeatedly(Invoke(DBusCallbackTrue));
-  EXPECT_CALL(client,
-              TpmAttestationDoesKeyExist(KEY_USER, cryptohome::Identification(),
-                                         kEnterpriseUserKey, _))
-      .WillRepeatedly(WithArgs<3>(Invoke(DBusCallbackFalse)));
+  chromeos::FakeCryptohomeClient client;
 
   std::unique_ptr<MockServerProxy> proxy(new StrictMock<MockServerProxy>());
   proxy->DeferToFake(true);
@@ -575,17 +519,9 @@
   // We're not expecting any async calls in this case; StrictMock will verify.
   StrictMock<cryptohome::MockAsyncMethodCaller> async_caller;
 
-  chromeos::MockCryptohomeClient client;
-  EXPECT_CALL(client, TpmAttestationIsEnrolled(_))
-      .WillRepeatedly(Invoke(DBusCallbackTrue));
-  EXPECT_CALL(client,
-              TpmAttestationDoesKeyExist(KEY_USER, cryptohome::Identification(),
-                                         kEnterpriseUserKey, _))
-      .WillRepeatedly(WithArgs<3>(Invoke(DBusCallbackTrue)));
-  EXPECT_CALL(client, TpmAttestationGetCertificate(KEY_USER,
-                                                   cryptohome::Identification(),
-                                                   kEnterpriseUserKey, _))
-      .WillRepeatedly(WithArgs<3>(Invoke(FakeDBusData("fake_cert"))));
+  chromeos::FakeCryptohomeClient client;
+  client.SetTpmAttestationUserCertificate(cryptohome::Identification(),
+                                          kEnterpriseUserKey, "fake_cert");
 
   // We're not expecting any server calls in this case; StrictMock will verify.
   std::unique_ptr<MockServerProxy> proxy(new StrictMock<MockServerProxy>());
@@ -611,12 +547,9 @@
   proxy->DeferToFake(true);
   EXPECT_CALL(*proxy, GetType()).WillRepeatedly(Return(ALTERNATE_PCA));
 
-  chromeos::MockCryptohomeClient client;
-  EXPECT_CALL(client, TpmAttestationIsEnrolled(_))
-      .WillRepeatedly(Invoke(DBusCallbackFalse));
-
-  EXPECT_CALL(client, TpmAttestationIsPrepared(_))
-      .WillRepeatedly(Invoke(DBusCallbackTrue));
+  chromeos::FakeCryptohomeClient client;
+  client.set_tpm_attestation_is_enrolled(false);
+  client.set_tpm_attestation_is_prepared(true);
 
   NiceMock<cryptohome::MockAsyncMethodCaller> async_caller;
   async_caller.SetUp(true, cryptohome::MOUNT_ERROR_NONE);
diff --git a/chromeos/components/tether/ble_advertiser.cc b/chromeos/components/tether/ble_advertiser.cc
index f48087e..beb1555 100644
--- a/chromeos/components/tether/ble_advertiser.cc
+++ b/chromeos/components/tether/ble_advertiser.cc
@@ -25,12 +25,21 @@
 BleAdvertiser::IndividualAdvertisement::IndividualAdvertisement(
     const std::string& device_id,
     scoped_refptr<device::BluetoothAdapter> adapter,
-    std::unique_ptr<cryptauth::DataWithTimestamp> advertisement_data)
+    std::unique_ptr<cryptauth::DataWithTimestamp> advertisement_data,
+    const base::Closure& on_unregister_advertisement_success_callback,
+    const base::Callback<void(device::BluetoothAdvertisement::ErrorCode)>&
+        on_unregister_advertisement_error_callback,
+    std::unordered_set<std::string>* active_advertisement_device_ids_set)
     : device_id_(device_id),
       adapter_(adapter),
       advertisement_data_(std::move(advertisement_data)),
       is_initializing_advertising_(false),
       advertisement_(nullptr),
+      on_unregister_advertisement_success_callback_(
+          on_unregister_advertisement_success_callback),
+      on_unregister_advertisement_error_callback_(
+          on_unregister_advertisement_error_callback),
+      active_advertisement_device_ids_set_(active_advertisement_device_ids_set),
       weak_ptr_factory_(this) {
   adapter_->AddObserver(this);
   AdvertiseIfPossible();
@@ -38,15 +47,20 @@
 
 BleAdvertiser::IndividualAdvertisement::~IndividualAdvertisement() {
   if (advertisement_) {
-    advertisement_->Unregister(
-        base::Bind(&base::DoNothing),
-        base::Bind(&IndividualAdvertisement::OnAdvertisementUnregisterFailure,
-                   weak_ptr_factory_.GetWeakPtr()));
+    advertisement_->Unregister(on_unregister_advertisement_success_callback_,
+                               on_unregister_advertisement_error_callback_);
   }
 
   adapter_->RemoveObserver(this);
 }
 
+void BleAdvertiser::IndividualAdvertisement::
+    OnPreviousAdvertisementUnregistered() {
+  DCHECK(active_advertisement_device_ids_set_->find(device_id_) ==
+         active_advertisement_device_ids_set_->end());
+  AdvertiseIfPossible();
+}
+
 void BleAdvertiser::IndividualAdvertisement::AdapterPoweredChanged(
     device::BluetoothAdapter* adapter,
     bool powered) {
@@ -60,13 +74,21 @@
 
   // If the advertisement was released, delete it and try again. Note that this
   // situation is not expected to occur under normal circumstances.
+  advertisement_->RemoveObserver(this);
   advertisement_ = nullptr;
+  active_advertisement_device_ids_set_->erase(device_id_);
+
   AdvertiseIfPossible();
 }
 
 void BleAdvertiser::IndividualAdvertisement::AdvertiseIfPossible() {
   if (!adapter_->IsPowered() || is_initializing_advertising_ ||
-      advertisement_) {
+      advertisement_ ||
+      active_advertisement_device_ids_set_->find(device_id_) !=
+          active_advertisement_device_ids_set_->end()) {
+    // It is not possible to advertise if the adapter is not powered. Likewise,
+    // we should not try to advertise if there is an advertisement already in
+    // progress.
     return;
   }
 
@@ -90,7 +112,11 @@
 void BleAdvertiser::IndividualAdvertisement::OnAdvertisementRegisteredCallback(
     scoped_refptr<device::BluetoothAdvertisement> advertisement) {
   is_initializing_advertising_ = false;
+
   advertisement_ = advertisement;
+  advertisement_->AddObserver(this);
+  active_advertisement_device_ids_set_->insert(device_id_);
+
   PA_LOG(INFO) << "Advertisement registered. "
                << "Device ID: \""
                << cryptauth::RemoteDevice::TruncateDeviceIdForLogs(device_id_)
@@ -107,15 +133,6 @@
                 << ", Error code: " << error_code;
 }
 
-void BleAdvertiser::IndividualAdvertisement::OnAdvertisementUnregisterFailure(
-    device::BluetoothAdvertisement::ErrorCode error_code) {
-  PA_LOG(ERROR) << "Error unregistering advertisement. "
-                << "Device ID: \""
-                << cryptauth::RemoteDevice::TruncateDeviceIdForLogs(device_id_)
-                << "\", Service data: " << advertisement_data_->DataInHex()
-                << " Error code: " << error_code;
-}
-
 std::unique_ptr<device::BluetoothAdvertisement::UUIDList>
 BleAdvertiser::IndividualAdvertisement::CreateServiceUuids() const {
   std::unique_ptr<device::BluetoothAdvertisement::UUIDList> list =
@@ -162,11 +179,13 @@
     : adapter_(adapter),
       eid_generator_(std::move(eid_generator)),
       remote_beacon_seed_fetcher_(remote_beacon_seed_fetcher),
-      local_device_data_provider_(local_device_data_provider) {}
+      local_device_data_provider_(local_device_data_provider),
+      weak_ptr_factory_(this) {}
 
 bool BleAdvertiser::StartAdvertisingToDevice(
     const cryptauth::RemoteDevice& remote_device) {
-  if (device_id_to_advertisement_map_.size() >= kMaxConcurrentAdvertisements) {
+  if (device_id_to_individual_advertisement_map_.size() >=
+      kMaxConcurrentAdvertisements) {
     PA_LOG(ERROR) << "Attempted to register a device when the maximum number "
                   << "of devices have already been registered.";
     return false;
@@ -208,15 +227,52 @@
     return false;
   }
 
-  device_id_to_advertisement_map_[remote_device.GetDeviceId()] =
+  std::string device_id = remote_device.GetDeviceId();
+  device_id_to_individual_advertisement_map_[device_id] =
       base::MakeUnique<IndividualAdvertisement>(
-          remote_device.GetDeviceId(), adapter_, std::move(advertisement));
+          remote_device.GetDeviceId(), adapter_, std::move(advertisement),
+          base::Bind(&BleAdvertiser::OnUnregisterAdvertisementSuccess,
+                     weak_ptr_factory_.GetWeakPtr(), device_id),
+          base::Bind(&BleAdvertiser::OnUnregisterAdvertisementError,
+                     weak_ptr_factory_.GetWeakPtr(), device_id),
+          &active_advertisement_device_ids_set_);
   return true;
 }
 
 bool BleAdvertiser::StopAdvertisingToDevice(
     const cryptauth::RemoteDevice& remote_device) {
-  return device_id_to_advertisement_map_.erase(remote_device.GetDeviceId()) > 0;
+  return device_id_to_individual_advertisement_map_.erase(
+             remote_device.GetDeviceId()) > 0;
+}
+
+void BleAdvertiser::OnUnregisterAdvertisementSuccess(
+    const std::string& associated_device_id) {
+  RemoveAdvertisingDeviceIdAndRetry(associated_device_id);
+}
+
+void BleAdvertiser::OnUnregisterAdvertisementError(
+    const std::string& associated_device_id,
+    device::BluetoothAdvertisement::ErrorCode error_code) {
+  PA_LOG(ERROR) << "Error unregistering advertisement. "
+                << "Device ID: \""
+                << cryptauth::RemoteDevice::TruncateDeviceIdForLogs(
+                       associated_device_id)
+                << "\", Error code: " << error_code;
+
+  // Even though there was an error unregistering the advertisement, remove it
+  // from the set anyway so that it is possible to try registering the
+  // advertisement again. Note that this situation is not expected to occur
+  // since unregistering an active advertisement should always succeed.
+  RemoveAdvertisingDeviceIdAndRetry(associated_device_id);
+}
+
+void BleAdvertiser::RemoveAdvertisingDeviceIdAndRetry(
+    const std::string& device_id) {
+  active_advertisement_device_ids_set_.erase(device_id);
+
+  auto it = device_id_to_individual_advertisement_map_.find(device_id);
+  if (it != device_id_to_individual_advertisement_map_.end())
+    it->second->OnPreviousAdvertisementUnregistered();
 }
 
 }  // namespace tether
diff --git a/chromeos/components/tether/ble_advertiser.h b/chromeos/components/tether/ble_advertiser.h
index 590b86e..a1211b2 100644
--- a/chromeos/components/tether/ble_advertiser.h
+++ b/chromeos/components/tether/ble_advertiser.h
@@ -6,7 +6,9 @@
 #define CHROMEOS_COMPONENTS_TETHER_BLE_ADVERTISER_H_
 
 #include <map>
+#include <unordered_set>
 
+#include "base/callback_forward.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "components/cryptauth/foreground_eid_generator.h"
@@ -44,6 +46,17 @@
  private:
   friend class BleAdvertiserTest;
 
+  // One IndividualAdvertisement is created for each device to which
+  // BleAdvertiser should be advertising. When an IndividualAdvertisement is
+  // created, it starts trying to advertise to its associated device ID;
+  // likewise, when it is destroyed, it stops advertising if necessary.
+  //
+  // However, because unregistering an advertisement is an asynchronous
+  // operation, it is possible that if an IndividualAdvertisement for one device
+  // is created just after another IndividualAdvertisement for the same device
+  // was destroyed, the previous advertisement was not fully unregistered.
+  // If that is the case, the newly-created IndividualAdvertisement must wait
+  // until OnPreviousAdvertisementUnregistered() is called.
   class IndividualAdvertisement
       : public device::BluetoothAdapter::Observer,
         public device::BluetoothAdvertisement::Observer {
@@ -51,9 +64,17 @@
     IndividualAdvertisement(
         const std::string& device_id,
         scoped_refptr<device::BluetoothAdapter> adapter,
-        std::unique_ptr<cryptauth::DataWithTimestamp> advertisement_data);
+        std::unique_ptr<cryptauth::DataWithTimestamp> advertisement_data,
+        const base::Closure& on_unregister_advertisement_success_callback,
+        const base::Callback<void(device::BluetoothAdvertisement::ErrorCode)>&
+            on_unregister_advertisement_error_callback,
+        std::unordered_set<std::string>* active_advertisement_device_ids_set);
     ~IndividualAdvertisement() override;
 
+    // Callback for when a previously-registered advertisement corresponding to
+    // |device_id_| has been unregistered.
+    void OnPreviousAdvertisementUnregistered();
+
     // device::BluetoothAdapter::Observer
     void AdapterPoweredChanged(device::BluetoothAdapter* adapter,
                                bool powered) override;
@@ -85,6 +106,11 @@
     bool is_initializing_advertising_;
     scoped_refptr<device::BluetoothAdvertisement> advertisement_;
 
+    base::Closure on_unregister_advertisement_success_callback_;
+    base::Callback<void(device::BluetoothAdvertisement::ErrorCode)>
+        on_unregister_advertisement_error_callback_;
+    std::unordered_set<std::string>* active_advertisement_device_ids_set_;
+
     base::WeakPtrFactory<IndividualAdvertisement> weak_ptr_factory_;
 
     DISALLOW_COPY_AND_ASSIGN(IndividualAdvertisement);
@@ -96,6 +122,13 @@
       const cryptauth::RemoteBeaconSeedFetcher* remote_beacon_seed_fetcher,
       const cryptauth::LocalDeviceDataProvider* local_device_data_provider);
 
+  void OnUnregisterAdvertisementSuccess(
+      const std::string& associated_device_id);
+  void OnUnregisterAdvertisementError(
+      const std::string& associated_device_id,
+      device::BluetoothAdvertisement::ErrorCode error_code);
+  void RemoveAdvertisingDeviceIdAndRetry(const std::string& device_id);
+
   scoped_refptr<device::BluetoothAdapter> adapter_;
 
   std::unique_ptr<cryptauth::ForegroundEidGenerator> eid_generator_;
@@ -104,7 +137,10 @@
   const cryptauth::LocalDeviceDataProvider* local_device_data_provider_;
 
   std::map<std::string, std::unique_ptr<IndividualAdvertisement>>
-      device_id_to_advertisement_map_;
+      device_id_to_individual_advertisement_map_;
+  std::unordered_set<std::string> active_advertisement_device_ids_set_;
+
+  base::WeakPtrFactory<BleAdvertiser> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(BleAdvertiser);
 };
diff --git a/chromeos/components/tether/ble_advertiser_unittest.cc b/chromeos/components/tether/ble_advertiser_unittest.cc
index d9676ce8..2b2ff64c 100644
--- a/chromeos/components/tether/ble_advertiser_unittest.cc
+++ b/chromeos/components/tether/ble_advertiser_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "chromeos/components/tether/ble_advertiser.h"
 
+#include "base/bind.h"
+#include "base/callback_forward.h"
 #include "base/logging.h"
 #include "chromeos/components/tether/ble_constants.h"
 #include "components/cryptauth/mock_foreground_eid_generator.h"
@@ -11,8 +13,8 @@
 #include "components/cryptauth/mock_remote_beacon_seed_fetcher.h"
 #include "components/cryptauth/proto/cryptauth_api.pb.h"
 #include "components/cryptauth/remote_device_test_util.h"
+#include "device/bluetooth/bluetooth_advertisement.h"
 #include "device/bluetooth/test/mock_bluetooth_adapter.h"
-#include "device/bluetooth/test/mock_bluetooth_advertisement.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using testing::Invoke;
@@ -80,6 +82,33 @@
   ~MockBluetoothAdapterWithAdvertisements() override {}
 };
 
+class FakeBluetoothAdvertisement : public device::BluetoothAdvertisement {
+ public:
+  // |unregister_callback| should be called with the success callback passed to
+  // Unregister() whenever an Unregister() call occurs.
+  FakeBluetoothAdvertisement(
+      const base::Callback<
+          void(const device::BluetoothAdvertisement::SuccessCallback&)>&
+          unregister_callback)
+      : unregister_callback_(unregister_callback) {}
+
+  // BluetoothAdvertisement:
+  void Unregister(
+      const device::BluetoothAdvertisement::SuccessCallback& success_callback,
+      const device::BluetoothAdvertisement::ErrorCallback& error_callback)
+      override {
+    unregister_callback_.Run(success_callback);
+  }
+
+ private:
+  ~FakeBluetoothAdvertisement() override {}
+
+  base::Callback<void(const device::BluetoothAdvertisement::SuccessCallback&)>
+      unregister_callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeBluetoothAdvertisement);
+};
+
 std::vector<cryptauth::DataWithTimestamp> GenerateFakeAdvertisements() {
   cryptauth::DataWithTimestamp advertisement1("advertisement1", 1000L, 2000L);
   cryptauth::DataWithTimestamp advertisement2("advertisement2", 2000L, 3000L);
@@ -163,7 +192,7 @@
     ble_advertiser_.reset();
 
     // All observers should have been removed.
-    EXPECT_FALSE(individual_advertisements_.size());
+    EXPECT_TRUE(individual_advertisements_.empty());
   }
 
   void OnAdapterAddObserver(device::BluetoothAdapter::Observer* observer) {
@@ -186,11 +215,11 @@
       scoped_refptr<RegisterAdvertisementArgs> args,
       const BleAdvertiser::IndividualAdvertisement* advertisement) {
     // First, verify that the service UUID list is correct.
-    EXPECT_EQ(static_cast<size_t>(1), args->service_uuids.size());
+    EXPECT_EQ(1u, args->service_uuids.size());
     EXPECT_EQ(std::string(kAdvertisingServiceUuid), args->service_uuids[0]);
 
     // Then, verify that the service data is correct.
-    EXPECT_EQ(static_cast<size_t>(1), args->service_data.size());
+    EXPECT_EQ(1u, args->service_data.size());
     std::vector<uint8_t> service_data_from_args =
         args->service_data[std::string(kAdvertisingServiceUuid)];
     EXPECT_EQ(service_data_from_args.size(),
@@ -218,8 +247,9 @@
   }
 
   void InvokeCallback(scoped_refptr<RegisterAdvertisementArgs> args) {
-    args->callback.Run(
-        make_scoped_refptr(new device::MockBluetoothAdvertisement));
+    args->callback.Run(make_scoped_refptr(new FakeBluetoothAdvertisement(
+        base::Bind(&BleAdvertiserTest::OnAdvertisementUnregistered,
+                   base::Unretained(this)))));
   }
 
   void ReleaseAdvertisement(
@@ -228,6 +258,18 @@
         individual_advertisement->advertisement_.get());
   }
 
+  void OnAdvertisementUnregistered(
+      const device::BluetoothAdvertisement::SuccessCallback& success_callback) {
+    unregister_callbacks_.push_back(success_callback);
+  }
+
+  void InvokeLastUnregisterCallbackAndRemove() {
+    DCHECK(!unregister_callbacks_.empty());
+    unregister_callbacks_[unregister_callbacks_.size() - 1].Run();
+    unregister_callbacks_.erase(unregister_callbacks_.begin() +
+                                unregister_callbacks_.size() - 1);
+  }
+
   std::unique_ptr<BleAdvertiser> ble_advertiser_;
 
   scoped_refptr<StrictMock<MockBluetoothAdapterWithAdvertisements>>
@@ -241,6 +283,8 @@
       register_advertisement_args_;
   std::vector<BleAdvertiser::IndividualAdvertisement*>
       individual_advertisements_;
+  std::vector<device::BluetoothAdvertisement::SuccessCallback>
+      unregister_callbacks_;
 
   const std::vector<cryptauth::RemoteDevice> fake_devices_;
   const std::vector<cryptauth::DataWithTimestamp> fake_advertisements_;
@@ -299,10 +343,10 @@
       base::MakeUnique<cryptauth::DataWithTimestamp>(fake_advertisements_[0]));
 
   EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice(fake_devices_[0]));
-  EXPECT_EQ(static_cast<size_t>(1), individual_advertisements_.size());
+  EXPECT_EQ(1u, individual_advertisements_.size());
 
   // RegisterAdvertisement() should not have been called.
-  EXPECT_FALSE(register_advertisement_args_.size());
+  EXPECT_TRUE(register_advertisement_args_.empty());
 }
 
 TEST_F(BleAdvertiserTest, RegisteringAdvertisementFails) {
@@ -315,8 +359,8 @@
       base::MakeUnique<cryptauth::DataWithTimestamp>(fake_advertisements_[0]));
 
   EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice(fake_devices_[0]));
-  EXPECT_EQ(static_cast<size_t>(1), individual_advertisements_.size());
-  EXPECT_EQ(static_cast<size_t>(1), register_advertisement_args_.size());
+  EXPECT_EQ(1u, individual_advertisements_.size());
+  EXPECT_EQ(1u, register_advertisement_args_.size());
   VerifyServiceDataMatches(register_advertisement_args_[0],
                            individual_advertisements_[0]);
 
@@ -334,8 +378,8 @@
       base::MakeUnique<cryptauth::DataWithTimestamp>(fake_advertisements_[0]));
 
   EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice(fake_devices_[0]));
-  EXPECT_EQ(static_cast<size_t>(1), individual_advertisements_.size());
-  EXPECT_EQ(static_cast<size_t>(1), register_advertisement_args_.size());
+  EXPECT_EQ(1u, individual_advertisements_.size());
+  EXPECT_EQ(1u, register_advertisement_args_.size());
   VerifyServiceDataMatches(register_advertisement_args_[0],
                            individual_advertisements_[0]);
 
@@ -344,7 +388,8 @@
 
   // Now, unregister.
   EXPECT_TRUE(ble_advertiser_->StopAdvertisingToDevice(fake_devices_[0]));
-  EXPECT_FALSE(individual_advertisements_.size());
+  InvokeLastUnregisterCallbackAndRemove();
+  EXPECT_TRUE(individual_advertisements_.empty());
 }
 
 TEST_F(BleAdvertiserTest, AdvertisementRegisteredSuccessfully_TwoDevices) {
@@ -358,8 +403,8 @@
       base::MakeUnique<cryptauth::DataWithTimestamp>(fake_advertisements_[0]));
 
   EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice(fake_devices_[0]));
-  EXPECT_EQ(static_cast<size_t>(1), individual_advertisements_.size());
-  EXPECT_EQ(static_cast<size_t>(1), register_advertisement_args_.size());
+  EXPECT_EQ(1u, individual_advertisements_.size());
+  EXPECT_EQ(1u, register_advertisement_args_.size());
   VerifyServiceDataMatches(register_advertisement_args_[0],
                            individual_advertisements_[0]);
 
@@ -371,8 +416,8 @@
       base::MakeUnique<cryptauth::DataWithTimestamp>(fake_advertisements_[1]));
 
   EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice(fake_devices_[1]));
-  EXPECT_EQ(static_cast<size_t>(2), individual_advertisements_.size());
-  EXPECT_EQ(static_cast<size_t>(2), register_advertisement_args_.size());
+  EXPECT_EQ(2u, individual_advertisements_.size());
+  EXPECT_EQ(2u, register_advertisement_args_.size());
   VerifyServiceDataMatches(register_advertisement_args_[1],
                            individual_advertisements_[1]);
 
@@ -382,9 +427,11 @@
   // Now, unregister.
   EXPECT_TRUE(ble_advertiser_->StopAdvertisingToDevice(fake_devices_[0]));
   EXPECT_EQ(1u, individual_advertisements_.size());
+  InvokeLastUnregisterCallbackAndRemove();
 
   EXPECT_TRUE(ble_advertiser_->StopAdvertisingToDevice(fake_devices_[1]));
-  EXPECT_EQ(0u, individual_advertisements_.size());
+  EXPECT_TRUE(individual_advertisements_.empty());
+  InvokeLastUnregisterCallbackAndRemove();
 }
 
 TEST_F(BleAdvertiserTest, TooManyDevicesRegistered) {
@@ -424,17 +471,17 @@
       base::MakeUnique<cryptauth::DataWithTimestamp>(fake_advertisements_[0]));
 
   EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice(fake_devices_[0]));
-  EXPECT_EQ(static_cast<size_t>(1), individual_advertisements_.size());
+  EXPECT_EQ(1u, individual_advertisements_.size());
 
   // RegisterAdvertisement() should not have been called.
-  EXPECT_FALSE(register_advertisement_args_.size());
+  EXPECT_TRUE(register_advertisement_args_.empty());
 
   // Now, simulate power being changed. Since the power is now on,
   // RegisterAdvertisement() should have been called.
   individual_advertisements_[0]->AdapterPoweredChanged(mock_adapter_.get(),
                                                        true);
-  EXPECT_EQ(static_cast<size_t>(1), individual_advertisements_.size());
-  EXPECT_EQ(static_cast<size_t>(1), register_advertisement_args_.size());
+  EXPECT_EQ(1u, individual_advertisements_.size());
+  EXPECT_EQ(1u, register_advertisement_args_.size());
   VerifyServiceDataMatches(register_advertisement_args_[0],
                            individual_advertisements_[0]);
 }
@@ -449,8 +496,8 @@
       base::MakeUnique<cryptauth::DataWithTimestamp>(fake_advertisements_[0]));
 
   EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice(fake_devices_[0]));
-  EXPECT_EQ(static_cast<size_t>(1), individual_advertisements_.size());
-  EXPECT_EQ(static_cast<size_t>(1), register_advertisement_args_.size());
+  EXPECT_EQ(1u, individual_advertisements_.size());
+  EXPECT_EQ(1u, register_advertisement_args_.size());
   VerifyServiceDataMatches(register_advertisement_args_[0],
                            individual_advertisements_[0]);
 
@@ -460,7 +507,7 @@
   // Now, simulate the advertisement being released. A new advertisement should
   // have been created.
   ReleaseAdvertisement(individual_advertisements_[0]);
-  EXPECT_EQ(static_cast<size_t>(2), register_advertisement_args_.size());
+  EXPECT_EQ(2u, register_advertisement_args_.size());
   VerifyServiceDataMatches(register_advertisement_args_[1],
                            individual_advertisements_[0]);
 
@@ -469,7 +516,60 @@
 
   // Now, unregister.
   EXPECT_TRUE(ble_advertiser_->StopAdvertisingToDevice(fake_devices_[0]));
-  EXPECT_FALSE(individual_advertisements_.size());
+  InvokeLastUnregisterCallbackAndRemove();
+  EXPECT_TRUE(individual_advertisements_.empty());
+}
+
+// Regression test for crbug.com/739883. This issue arises when the following
+// occurs:
+//   (1) BleAdvertiser starts advertising to device A.
+//   (2) BleAdvertiser stops advertising to device A. The advertisement starts
+//       its asynchyronous unregistration flow.
+//   (3) BleAdvertiser starts advertising to device A again, but the previous
+//       advertisement has not yet been fully unregistered.
+// Before the fix for crbug.com/739883, this would cause an error of type
+// ERROR_ADVERTISEMENT_ALREADY_EXISTS. However, the fix for this issue ensures
+// that the new advertisement in step (3) above does not start until the
+// previous one has been finished.
+TEST_F(BleAdvertiserTest, SameAdvertisementAdded_FirstHasNotBeenUnregistered) {
+  EXPECT_CALL(*mock_adapter_, AddObserver(_)).Times(2);
+  EXPECT_CALL(*mock_adapter_, RemoveObserver(_)).Times(2);
+  EXPECT_CALL(*mock_adapter_, IsPowered()).Times(3);
+  EXPECT_CALL(*mock_adapter_, RegisterAdvertisementWithArgsStruct(_)).Times(2);
+
+  mock_eid_generator_->set_advertisement(
+      base::MakeUnique<cryptauth::DataWithTimestamp>(fake_advertisements_[0]));
+
+  EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice(fake_devices_[0]));
+  EXPECT_EQ(1u, individual_advertisements_.size());
+  EXPECT_EQ(1u, register_advertisement_args_.size());
+  VerifyServiceDataMatches(register_advertisement_args_[0],
+                           individual_advertisements_[0]);
+
+  InvokeCallback(register_advertisement_args_[0]);
+  VerifyAdvertisementHasStarted(individual_advertisements_[0]);
+
+  // Unregister, but do not invoke the last unregister callback.
+  EXPECT_TRUE(ble_advertiser_->StopAdvertisingToDevice(fake_devices_[0]));
+  EXPECT_TRUE(individual_advertisements_.empty());
+
+  // Start advertising again, to the same device. A new IndividualAdvertisement
+  // should have been created, but a call to RegisterAdvertisement() should NOT
+  // have occurred, since the previous advertisement has not yet been
+  // unregistered.
+  EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice(fake_devices_[0]));
+  EXPECT_EQ(1u, individual_advertisements_.size());
+  // Should still be only one set of RegisterAdvertisement() args.
+  EXPECT_EQ(1u, register_advertisement_args_.size());
+
+  // Now, complete the previous unregistration.
+  InvokeLastUnregisterCallbackAndRemove();
+
+  // RegisterAdvertisement() should be called for the new advertisement.
+  EXPECT_EQ(2u, register_advertisement_args_.size());
+
+  VerifyServiceDataMatches(register_advertisement_args_[1],
+                           individual_advertisements_[0]);
 }
 
 }  // namespace tether
diff --git a/chromeos/dbus/fake_cryptohome_client.cc b/chromeos/dbus/fake_cryptohome_client.cc
index 974676f..ba222d1 100644
--- a/chromeos/dbus/fake_cryptohome_client.cc
+++ b/chromeos/dbus/fake_cryptohome_client.cc
@@ -7,7 +7,7 @@
 #include <stddef.h>
 #include <stdint.h>
 
-#include <utility>
+#include <tuple>
 
 #include "base/bind.h"
 #include "base/command_line.h"
@@ -15,6 +15,7 @@
 #include "base/location.h"
 #include "base/path_service.h"
 #include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "chromeos/attestation/attestation.pb.h"
@@ -379,13 +380,17 @@
 void FakeCryptohomeClient::TpmAttestationIsPrepared(
     const BoolDBusMethodCallback& callback) {
   base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::Bind(callback, DBUS_METHOD_CALL_SUCCESS, true));
+      FROM_HERE, base::BindOnce(callback, DBUS_METHOD_CALL_SUCCESS,
+                                tpm_attestation_is_prepared_));
 }
 
 void FakeCryptohomeClient::TpmAttestationIsEnrolled(
     const BoolDBusMethodCallback& callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::Bind(callback, DBUS_METHOD_CALL_SUCCESS, true));
+  auto task = service_is_available_
+                  ? base::BindOnce(callback, DBUS_METHOD_CALL_SUCCESS,
+                                   tpm_attestation_is_enrolled_)
+                  : base::BindOnce(callback, DBUS_METHOD_CALL_FAILURE, false);
+  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, std::move(task));
 }
 
 void FakeCryptohomeClient::AsyncTpmAttestationCreateEnrollRequest(
@@ -424,8 +429,14 @@
     const cryptohome::Identification& cryptohome_id,
     const std::string& key_name,
     const BoolDBusMethodCallback& callback) {
+  bool result = false;
+  if (key_type == attestation::KEY_USER) {
+    result = base::ContainsKey(user_certificate_map_,
+                               std::make_pair(cryptohome_id, key_name));
+  }
+
   base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::Bind(callback, DBUS_METHOD_CALL_SUCCESS, false));
+      FROM_HERE, base::BindOnce(callback, DBUS_METHOD_CALL_SUCCESS, result));
 }
 
 void FakeCryptohomeClient::TpmAttestationGetCertificate(
@@ -433,9 +444,19 @@
     const cryptohome::Identification& cryptohome_id,
     const std::string& key_name,
     const DataMethodCallback& callback) {
+  bool result = false;
+  std::string certificate;
+  if (key_type == attestation::KEY_USER) {
+    const auto it = user_certificate_map_.find({cryptohome_id, key_name});
+    if (it != user_certificate_map_.end()) {
+      result = true;
+      certificate = it->second;
+    }
+  }
+
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
-      base::Bind(callback, DBUS_METHOD_CALL_SUCCESS, false, std::string()));
+      base::BindOnce(callback, DBUS_METHOD_CALL_SUCCESS, result, certificate));
 }
 
 void FakeCryptohomeClient::TpmAttestationGetPublicKey(
@@ -515,7 +536,7 @@
     const cryptohome::GetKeyDataRequest& request,
     const ProtobufMethodCallback& callback) {
   cryptohome::BaseReply reply;
-  auto it = key_data_map_.find(cryptohome_id);
+  const auto it = key_data_map_.find(cryptohome_id);
   if (it == key_data_map_.end()) {
     reply.set_error(cryptohome::CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND);
   } else {
@@ -646,6 +667,15 @@
   }
 }
 
+void FakeCryptohomeClient::SetTpmAttestationUserCertificate(
+    const cryptohome::Identification& cryptohome_id,
+    const std::string& key_name,
+    const std::string& certificate) {
+  user_certificate_map_.emplace(std::piecewise_construct,
+                                std::forward_as_tuple(cryptohome_id, key_name),
+                                std::forward_as_tuple(certificate));
+}
+
 // static
 std::vector<uint8_t> FakeCryptohomeClient::GetStubSystemSalt() {
   const char kStubSystemSalt[] = "stub_system_salt";
diff --git a/chromeos/dbus/fake_cryptohome_client.h b/chromeos/dbus/fake_cryptohome_client.h
index 32671a2d..673fe579 100644
--- a/chromeos/dbus/fake_cryptohome_client.h
+++ b/chromeos/dbus/fake_cryptohome_client.h
@@ -9,6 +9,7 @@
 
 #include <map>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "base/macros.h"
@@ -236,6 +237,19 @@
     needs_dircrypto_migration_ = needs_migration;
   }
 
+  void set_tpm_attestation_is_enrolled(bool enrolled) {
+    tpm_attestation_is_enrolled_ = enrolled;
+  }
+
+  void set_tpm_attestation_is_prepared(bool prepared) {
+    tpm_attestation_is_prepared_ = prepared;
+  }
+
+  void SetTpmAttestationUserCertificate(
+      const cryptohome::Identification& cryptohome_id,
+      const std::string& key_name,
+      const std::string& certificate);
+
  private:
   void ReturnProtobufMethodCallback(
       const cryptohome::BaseReply& reply,
@@ -276,11 +290,17 @@
 
   std::map<cryptohome::Identification, cryptohome::KeyData> key_data_map_;
 
+  // User attestation certificate mapped by cryptohome_id and key_name.
+  std::map<std::pair<cryptohome::Identification, std::string>, std::string>
+      user_certificate_map_;
+
   DircryptoMigrationProgessHandler dircrypto_migration_progress_handler_;
   base::RepeatingTimer dircrypto_migration_progress_timer_;
   uint64_t dircrypto_migration_progress_;
 
   bool needs_dircrypto_migration_ = false;
+  bool tpm_attestation_is_enrolled_ = true;
+  bool tpm_attestation_is_prepared_ = true;
 
   base::WeakPtrFactory<FakeCryptohomeClient> weak_ptr_factory_;
 
diff --git a/components/cronet/ios/BUILD.gn b/components/cronet/ios/BUILD.gn
index 3244295..3227d03 100644
--- a/components/cronet/ios/BUILD.gn
+++ b/components/cronet/ios/BUILD.gn
@@ -59,6 +59,7 @@
   "//components/prefs:prefs",
   "//ios/net:net",
   "//ios/web:user_agent",
+  "//ios/web/public/global_state",
   "//net",
   "//url",
 ]
diff --git a/components/cronet/ios/DEPS b/components/cronet/ios/DEPS
index 60c6da07..ea7d66e 100644
--- a/components/cronet/ios/DEPS
+++ b/components/cronet/ios/DEPS
@@ -1,4 +1,4 @@
 include_rules = [
   "+ios/net",
-  "+ios/web",
+  "+ios/web/public",
 ]
diff --git a/components/cronet/ios/cronet_environment.mm b/components/cronet/ios/cronet_environment.mm
index dd3c96f..1688e6d 100644
--- a/components/cronet/ios/cronet_environment.mm
+++ b/components/cronet/ios/cronet_environment.mm
@@ -22,12 +22,12 @@
 #include "base/path_service.h"
 #include "base/single_thread_task_runner.h"
 #include "base/synchronization/waitable_event.h"
-#include "base/task_scheduler/task_scheduler.h"
 #include "components/cronet/histogram_manager.h"
 #include "components/cronet/ios/version.h"
 #include "components/prefs/json_pref_store.h"
 #include "components/prefs/pref_filter.h"
 #include "ios/net/cookies/cookie_store_ios.h"
+#include "ios/web/public/global_state/ios_global_state.h"
 #include "ios/web/public/user_agent.h"
 #include "net/base/network_change_notifier.h"
 #include "net/cert/cert_verifier.h"
@@ -118,7 +118,8 @@
   if (!g_at_exit_)
     g_at_exit_ = new base::AtExitManager;
 
-  base::TaskScheduler::CreateAndStartWithDefaultParams("CronetIos");
+  ios_global_state::Create();
+  ios_global_state::StartTaskScheduler(/*init_params=*/nullptr);
 
   url::Initialize();
   base::CommandLine::Init(0, nullptr);
diff --git a/components/network_session_configurator/browser/network_session_configurator.cc b/components/network_session_configurator/browser/network_session_configurator.cc
index 865e8d84..050068e 100644
--- a/components/network_session_configurator/browser/network_session_configurator.cc
+++ b/components/network_session_configurator/browser/network_session_configurator.cc
@@ -233,9 +233,8 @@
   return 0;
 }
 
-net::QuicVersionVector GetQuicVersions(
-    const VariationParameters& quic_trial_params) {
-  return network_session_configurator::ParseQuicVersions(
+net::QuicVersion GetQuicVersion(const VariationParameters& quic_trial_params) {
+  return network_session_configurator::ParseQuicVersion(
       GetVariationParam(quic_trial_params, "quic_version"));
 }
 
@@ -307,35 +306,28 @@
 
   params->quic_user_agent_id = quic_user_agent_id;
 
-  net::QuicVersionVector supported_versions =
-      GetQuicVersions(quic_trial_params);
-  if (!supported_versions.empty())
+  net::QuicVersion version = GetQuicVersion(quic_trial_params);
+  if (version != net::QUIC_VERSION_UNSUPPORTED) {
+    net::QuicVersionVector supported_versions;
+    supported_versions.push_back(version);
     params->quic_supported_versions = supported_versions;
+  }
 }
 
 }  // anonymous namespace
 
 namespace network_session_configurator {
 
-net::QuicVersionVector ParseQuicVersions(const std::string& quic_versions) {
-  net::QuicVersionVector supported_versions;
-  net::QuicVersionVector all_supported_versions = net::AllSupportedVersions();
-
-  for (const base::StringPiece& version : base::SplitStringPiece(
-           quic_versions, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
-    auto it = all_supported_versions.begin();
-    while (it != all_supported_versions.end()) {
-      if (net::QuicVersionToString(*it) == version) {
-        supported_versions.push_back(*it);
-        // Remove the supported version to deduplicate versions extracted from
-        // |quic_versions|.
-        all_supported_versions.erase(it);
-        continue;
-      }
-      it++;
+net::QuicVersion ParseQuicVersion(const std::string& quic_version) {
+  net::QuicVersionVector supported_versions = net::AllSupportedVersions();
+  for (size_t i = 0; i < supported_versions.size(); ++i) {
+    net::QuicVersion version = supported_versions[i];
+    if (net::QuicVersionToString(version) == quic_version) {
+      return version;
     }
   }
-  return supported_versions;
+
+  return net::QUIC_VERSION_UNSUPPORTED;
 }
 
 void ParseCommandLineAndFieldTrials(const base::CommandLine& command_line,
@@ -386,11 +378,13 @@
     }
 
     if (command_line.HasSwitch(switches::kQuicVersion)) {
-      net::QuicVersionVector supported_versions =
-          network_session_configurator::ParseQuicVersions(
-              command_line.GetSwitchValueASCII(switches::kQuicVersion));
-      if (!supported_versions.empty())
+      net::QuicVersion version = network_session_configurator::ParseQuicVersion(
+          command_line.GetSwitchValueASCII(switches::kQuicVersion));
+      if (version != net::QUIC_VERSION_UNSUPPORTED) {
+        net::QuicVersionVector supported_versions;
+        supported_versions.push_back(version);
         params->quic_supported_versions = supported_versions;
+      }
     }
 
     if (command_line.HasSwitch(switches::kOriginToForceQuicOn)) {
diff --git a/components/network_session_configurator/browser/network_session_configurator.h b/components/network_session_configurator/browser/network_session_configurator.h
index 69e064bf3..7c7f25d 100644
--- a/components/network_session_configurator/browser/network_session_configurator.h
+++ b/components/network_session_configurator/browser/network_session_configurator.h
@@ -18,8 +18,9 @@
 // Helper functions to configure HttpNetworkSession::Params based on field
 // trials and command line.
 
-// Parse serialized QUIC versions string.
-net::QuicVersionVector ParseQuicVersions(const std::string& quic_versions);
+// Parse serialized QUIC version string.
+// Return QUIC_VERSION_UNSUPPORTED on failure.
+net::QuicVersion ParseQuicVersion(const std::string& quic_version);
 
 // Configure |params| based on field trials and command line,
 // and forcing (policy or other command line) arguments.
diff --git a/components/network_session_configurator/browser/network_session_configurator_unittest.cc b/components/network_session_configurator/browser/network_session_configurator_unittest.cc
index b04421d..94e81d0 100644
--- a/components/network_session_configurator/browser/network_session_configurator_unittest.cc
+++ b/components/network_session_configurator/browser/network_session_configurator_unittest.cc
@@ -306,42 +306,6 @@
 }
 
 TEST_F(NetworkSessionConfiguratorTest,
-       MultipleQuicVersionFromFieldTrialParams) {
-  std::map<std::string, std::string> field_trial_params;
-  std::string quic_versions =
-      net::QuicVersionToString(net::AllSupportedVersions().front()) + "," +
-      net::QuicVersionToString(net::AllSupportedVersions().back());
-
-  field_trial_params["quic_version"] = quic_versions;
-  variations::AssociateVariationParams("QUIC", "Enabled", field_trial_params);
-  base::FieldTrialList::CreateFieldTrial("QUIC", "Enabled");
-
-  ParseFieldTrials();
-
-  net::QuicVersionVector supported_versions;
-  supported_versions.push_back(net::AllSupportedVersions().front());
-  supported_versions.push_back(net::AllSupportedVersions().back());
-  EXPECT_EQ(supported_versions, params_.quic_supported_versions);
-}
-
-TEST_F(NetworkSessionConfiguratorTest, SameQuicVersionsFromFieldTrialParams) {
-  std::map<std::string, std::string> field_trial_params;
-  std::string quic_versions =
-      net::QuicVersionToString(net::AllSupportedVersions().front()) + "," +
-      net::QuicVersionToString(net::AllSupportedVersions().front());
-
-  field_trial_params["quic_version"] = quic_versions;
-  variations::AssociateVariationParams("QUIC", "Enabled", field_trial_params);
-  base::FieldTrialList::CreateFieldTrial("QUIC", "Enabled");
-
-  ParseFieldTrials();
-
-  net::QuicVersionVector supported_versions;
-  supported_versions.push_back(net::AllSupportedVersions().front());
-  EXPECT_EQ(supported_versions, params_.quic_supported_versions);
-}
-
-TEST_F(NetworkSessionConfiguratorTest,
        QuicConnectionOptionsFromFieldTrialParams) {
   std::map<std::string, std::string> field_trial_params;
   field_trial_params["connection_options"] = "TIME,TBBR,REJ";
diff --git a/components/payments/core/autofill_payment_instrument.cc b/components/payments/core/autofill_payment_instrument.cc
index 2b096c3..86d98f0277 100644
--- a/components/payments/core/autofill_payment_instrument.cc
+++ b/components/payments/core/autofill_payment_instrument.cc
@@ -80,7 +80,7 @@
                                                weak_ptr_factory_.GetWeakPtr());
 }
 
-bool AutofillPaymentInstrument::IsCompleteForPayment() {
+bool AutofillPaymentInstrument::IsCompleteForPayment() const {
   // COMPLETE or EXPIRED cards are considered valid for payment. The user will
   // be prompted to enter the new expiration at the CVC step.
   return autofill::GetCompletionStatusForCard(credit_card_, app_locale_,
@@ -88,17 +88,17 @@
          autofill::CREDIT_CARD_EXPIRED;
 }
 
-bool AutofillPaymentInstrument::IsExactlyMatchingMerchantRequest() {
+bool AutofillPaymentInstrument::IsExactlyMatchingMerchantRequest() const {
   return matches_merchant_card_type_exactly_;
 }
 
-base::string16 AutofillPaymentInstrument::GetMissingInfoLabel() {
+base::string16 AutofillPaymentInstrument::GetMissingInfoLabel() const {
   return autofill::GetCompletionMessageForCard(
       autofill::GetCompletionStatusForCard(credit_card_, app_locale_,
                                            billing_profiles_));
 }
 
-bool AutofillPaymentInstrument::IsValidForCanMakePayment() {
+bool AutofillPaymentInstrument::IsValidForCanMakePayment() const {
   autofill::CreditCardCompletionStatus status =
       autofill::GetCompletionStatusForCard(credit_card_, app_locale_,
                                            billing_profiles_);
diff --git a/components/payments/core/autofill_payment_instrument.h b/components/payments/core/autofill_payment_instrument.h
index 7ec5071..3600918 100644
--- a/components/payments/core/autofill_payment_instrument.h
+++ b/components/payments/core/autofill_payment_instrument.h
@@ -41,10 +41,10 @@
 
   // PaymentInstrument:
   void InvokePaymentApp(PaymentInstrument::Delegate* delegate) override;
-  bool IsCompleteForPayment() override;
-  bool IsExactlyMatchingMerchantRequest() override;
-  base::string16 GetMissingInfoLabel() override;
-  bool IsValidForCanMakePayment() override;
+  bool IsCompleteForPayment() const override;
+  bool IsExactlyMatchingMerchantRequest() const override;
+  base::string16 GetMissingInfoLabel() const override;
+  bool IsValidForCanMakePayment() const override;
   void RecordUse() override;
   base::string16 GetLabel() const override;
   base::string16 GetSublabel() const override;
diff --git a/components/payments/core/payment_instrument.h b/components/payments/core/payment_instrument.h
index ecdc99e..24b1d14 100644
--- a/components/payments/core/payment_instrument.h
+++ b/components/payments/core/payment_instrument.h
@@ -40,17 +40,17 @@
   virtual void InvokePaymentApp(Delegate* delegate) = 0;
   // Returns whether the instrument is complete to be used as a payment method
   // without further editing.
-  virtual bool IsCompleteForPayment() = 0;
+  virtual bool IsCompleteForPayment() const = 0;
   // Returns whether the instrument is exactly matching all filters provided by
   // the merchant. For example, this can return "false" for unknown card types,
   // if the merchant requested only debit cards.
-  virtual bool IsExactlyMatchingMerchantRequest() = 0;
+  virtual bool IsExactlyMatchingMerchantRequest() const = 0;
   // Returns a message to indicate to the user what's missing for the instrument
   // to be complete for payment.
-  virtual base::string16 GetMissingInfoLabel() = 0;
+  virtual base::string16 GetMissingInfoLabel() const = 0;
   // Returns whether the instrument is valid for the purposes of responding to
   // canMakePayment.
-  virtual bool IsValidForCanMakePayment() = 0;
+  virtual bool IsValidForCanMakePayment() const = 0;
   // Records the use of this payment instrument.
   virtual void RecordUse() = 0;
   // Return the sub/label of payment instrument, to be displayed to the user.
diff --git a/components/safe_browsing/csd.proto b/components/safe_browsing/csd.proto
index b561039..088d7b5 100644
--- a/components/safe_browsing/csd.proto
+++ b/components/safe_browsing/csd.proto
@@ -521,6 +521,11 @@
   // Deprecated.
   optional bool DEPRECATED_download_attribution_finch_enabled = 39
       [deprecated = true];
+
+  // The Mac disk image code signature.
+  // The underlying structure of code signature is defined at
+  // https://opensource.apple.com/source/xnu/xnu-2782.1.97/bsd/sys/codesign.h
+  optional bytes udif_code_signature = 40;
 }
 
 // Please update SafeBrowsingNavigationObserverManager::SanitizeReferrerChain()
diff --git a/components/viz/host/server_gpu_memory_buffer_manager_unittest.cc b/components/viz/host/server_gpu_memory_buffer_manager_unittest.cc
index 6732389..12fb5087 100644
--- a/components/viz/host/server_gpu_memory_buffer_manager_unittest.cc
+++ b/components/viz/host/server_gpu_memory_buffer_manager_unittest.cc
@@ -88,7 +88,7 @@
   void RequestCompleteGpuInfo(
       const RequestCompleteGpuInfoCallback& callback) override {}
 
-  void LoadedShader(const std::string& data) override {}
+  void LoadedShader(const std::string& key, const std::string& data) override {}
 
   void DestroyingVideoSurface(
       int32_t surface_id,
diff --git a/content/browser/accessibility/browser_accessibility_com_win.cc b/content/browser/accessibility/browser_accessibility_com_win.cc
index 932d213..06c4ed1 100644
--- a/content/browser/accessibility/browser_accessibility_com_win.cc
+++ b/content/browser/accessibility/browser_accessibility_com_win.cc
@@ -1068,21 +1068,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!accessible)
-    return E_INVALIDARG;
-
-  AXPlatformNodeBase* cell =
-      GetTableCell(static_cast<int>(row), static_cast<int>(column));
-  if (cell) {
-    auto* node_win = static_cast<AXPlatformNodeWin*>(cell);
-    node_win->AddRef();
-
-    *accessible = static_cast<IAccessible*>(node_win);
-    return S_OK;
-  }
-
-  *accessible = nullptr;
-  return E_INVALIDARG;
+  return AXPlatformNodeWin::get_accessibleAt(row, column, accessible);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_caption(IUnknown** accessible) {
@@ -1091,12 +1077,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!accessible)
-    return E_INVALIDARG;
-
-  // TODO(dmazzoni): implement
-  *accessible = nullptr;
-  return S_FALSE;
+  return AXPlatformNodeWin::get_caption(accessible);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_childIndex(long row,
@@ -1107,17 +1088,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!cell_index)
-    return E_INVALIDARG;
-
-  auto* cell = GetTableCell(static_cast<int>(row), static_cast<int>(column));
-  if (cell) {
-    *cell_index = static_cast<LONG>(cell->GetTableCellIndex());
-    return S_OK;
-  }
-
-  *cell_index = 0;
-  return E_INVALIDARG;
+  return AXPlatformNodeWin::get_childIndex(row, column, cell_index);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_columnDescription(
@@ -1128,38 +1099,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!description)
-    return E_INVALIDARG;
-
-  int columns = GetTableColumnCount();
-  if (column < 0 || column >= columns)
-    return E_INVALIDARG;
-
-  int rows = GetTableRowCount();
-  if (rows <= 0) {
-    *description = nullptr;
-    return S_FALSE;
-  }
-
-  for (int i = 0; i < rows; ++i) {
-    auto* cell = GetTableCell(i, column);
-    if (cell && cell->GetData().role == ui::AX_ROLE_COLUMN_HEADER) {
-      base::string16 cell_name = cell->GetString16Attribute(ui::AX_ATTR_NAME);
-      if (cell_name.size() > 0) {
-        *description = SysAllocString(cell_name.c_str());
-        return S_OK;
-      }
-
-      cell_name = cell->GetString16Attribute(ui::AX_ATTR_DESCRIPTION);
-      if (cell_name.size() > 0) {
-        *description = SysAllocString(cell_name.c_str());
-        return S_OK;
-      }
-    }
-  }
-
-  *description = nullptr;
-  return S_FALSE;
+  return AXPlatformNodeWin::get_columnDescription(column, description);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_columnExtentAt(
@@ -1171,15 +1111,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!n_columns_spanned)
-    return E_INVALIDARG;
-
-  auto* cell = GetTableCell(static_cast<int>(row), static_cast<int>(column));
-  if (!cell)
-    return E_INVALIDARG;
-
-  *n_columns_spanned = cell->GetTableColumnSpan();
-  return S_OK;
+  return AXPlatformNodeWin::get_columnExtentAt(row, column, n_columns_spanned);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_columnHeader(
@@ -1187,8 +1119,12 @@
     long* starting_row_index) {
   WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_COLUMN_HEADER);
   AddAccessibilityModeFlags(kScreenReaderAndHTMLAccessibilityModes);
-  // TODO(dmazzoni): implement
-  return E_NOTIMPL;
+
+  if (!owner())
+    return E_FAIL;
+
+  return AXPlatformNodeWin::get_columnHeader(accessible_table,
+                                             starting_row_index);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_columnIndex(long cell_index,
@@ -1198,14 +1134,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!column_index)
-    return E_INVALIDARG;
-
-  auto* cell = GetTableCell(cell_index);
-  if (!cell)
-    return E_INVALIDARG;
-  *column_index = cell->GetTableColumn();
-  return S_OK;
+  return AXPlatformNodeWin::get_columnIndex(cell_index, column_index);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_nColumns(long* column_count) {
@@ -1214,11 +1143,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!column_count)
-    return E_INVALIDARG;
-
-  *column_count = GetTableColumnCount();
-  return S_OK;
+  return AXPlatformNodeWin::get_nColumns(column_count);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_nRows(long* row_count) {
@@ -1227,11 +1152,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!row_count)
-    return E_INVALIDARG;
-
-  *row_count = GetTableRowCount();
-  return S_OK;
+  return AXPlatformNodeWin::get_nRows(row_count);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_nSelectedChildren(
@@ -1241,12 +1162,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!cell_count)
-    return E_INVALIDARG;
-
-  // TODO(dmazzoni): add support for selected cells/rows/columns in tables.
-  *cell_count = 0;
-  return S_FALSE;
+  return AXPlatformNodeWin::get_nSelectedChildren(cell_count);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_nSelectedColumns(
@@ -1256,11 +1172,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!column_count)
-    return E_INVALIDARG;
-
-  *column_count = 0;
-  return S_FALSE;
+  return AXPlatformNodeWin::get_nSelectedColumns(column_count);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_nSelectedRows(long* row_count) {
@@ -1269,11 +1181,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!row_count)
-    return E_INVALIDARG;
-
-  *row_count = 0;
-  return S_FALSE;
+  return AXPlatformNodeWin::get_nSelectedRows(row_count);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_rowDescription(long row,
@@ -1283,36 +1191,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!description)
-    return E_INVALIDARG;
-
-  if (row < 0 || row >= GetTableRowCount())
-    return E_INVALIDARG;
-
-  int columns = GetTableColumnCount();
-  if (columns <= 0) {
-    *description = nullptr;
-    return S_FALSE;
-  }
-
-  for (int i = 0; i < columns; ++i) {
-    auto* cell = GetTableCell(row, i);
-    if (cell && cell->GetData().role == ui::AX_ROLE_ROW_HEADER) {
-      base::string16 cell_name = cell->GetString16Attribute(ui::AX_ATTR_NAME);
-      if (cell_name.size() > 0) {
-        *description = SysAllocString(cell_name.c_str());
-        return S_OK;
-      }
-      cell_name = cell->GetString16Attribute(ui::AX_ATTR_DESCRIPTION);
-      if (cell_name.size() > 0) {
-        *description = SysAllocString(cell_name.c_str());
-        return S_OK;
-      }
-    }
-  }
-
-  *description = nullptr;
-  return S_FALSE;
+  return AXPlatformNodeWin::get_rowDescription(row, description);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_rowExtentAt(long row,
@@ -1323,15 +1202,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!n_rows_spanned)
-    return E_INVALIDARG;
-
-  auto* cell = GetTableCell(row, column);
-  if (!cell)
-    return E_INVALIDARG;
-
-  *n_rows_spanned = GetTableRowSpan();
-  return S_OK;
+  return AXPlatformNodeWin::get_rowExtentAt(row, column, n_rows_spanned);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_rowHeader(
@@ -1339,8 +1210,11 @@
     long* starting_column_index) {
   WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_ROW_HEADER);
   AddAccessibilityModeFlags(kScreenReaderAndHTMLAccessibilityModes);
-  // TODO(dmazzoni): implement
-  return E_NOTIMPL;
+  if (!owner())
+    return E_FAIL;
+
+  return AXPlatformNodeWin::get_rowHeader(accessible_table,
+                                          starting_column_index);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_rowIndex(long cell_index,
@@ -1350,15 +1224,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!row_index)
-    return E_INVALIDARG;
-
-  auto* cell = GetTableCell(cell_index);
-  if (!cell)
-    return E_INVALIDARG;
-
-  *row_index = cell->GetTableRow();
-  return S_OK;
+  return AXPlatformNodeWin::get_rowIndex(cell_index, row_index);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_selectedChildren(
@@ -1370,12 +1236,8 @@
   if (!owner())
     return E_FAIL;
 
-  if (!children || !n_children)
-    return E_INVALIDARG;
-
-  // TODO(dmazzoni): Implement this.
-  *n_children = 0;
-  return S_FALSE;
+  return AXPlatformNodeWin::get_selectedChildren(max_children, children,
+                                                 n_children);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_selectedColumns(long max_columns,
@@ -1386,12 +1248,8 @@
   if (!owner())
     return E_FAIL;
 
-  if (!columns || !n_columns)
-    return E_INVALIDARG;
-
-  // TODO(dmazzoni): Implement this.
-  *n_columns = 0;
-  return S_FALSE;
+  return AXPlatformNodeWin::get_selectedColumns(max_columns, columns,
+                                                n_columns);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_selectedRows(long max_rows,
@@ -1402,12 +1260,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!rows || !n_rows)
-    return E_INVALIDARG;
-
-  // TODO(dmazzoni): Implement this.
-  *n_rows = 0;
-  return S_FALSE;
+  return AXPlatformNodeWin::get_selectedRows(max_rows, rows, n_rows);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_summary(IUnknown** accessible) {
@@ -1416,12 +1269,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!accessible)
-    return E_INVALIDARG;
-
-  // TODO(dmazzoni): implement
-  *accessible = nullptr;
-  return S_FALSE;
+  return AXPlatformNodeWin::get_summary(accessible);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_isColumnSelected(
@@ -1432,12 +1280,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!is_selected)
-    return E_INVALIDARG;
-
-  // TODO(dmazzoni): Implement this.
-  *is_selected = false;
-  return S_OK;
+  return AXPlatformNodeWin::get_isColumnSelected(column, is_selected);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_isRowSelected(
@@ -1448,12 +1291,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!is_selected)
-    return E_INVALIDARG;
-
-  // TODO(dmazzoni): Implement this.
-  *is_selected = false;
-  return S_OK;
+  return AXPlatformNodeWin::get_isRowSelected(row, is_selected);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_isSelected(long row,
@@ -1464,12 +1302,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!is_selected)
-    return E_INVALIDARG;
-
-  // TODO(dmazzoni): Implement this.
-  *is_selected = false;
-  return S_OK;
+  return AXPlatformNodeWin::get_isSelected(row, column, is_selected);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_rowColumnExtentsAtIndex(
@@ -1484,49 +1317,52 @@
   if (!owner())
     return E_FAIL;
 
-  if (!row || !column || !row_extents || !column_extents || !is_selected)
-    return E_INVALIDARG;
-
-  auto* cell = GetTableCell(index);
-  if (!cell)
-    return E_INVALIDARG;
-
-  *row = cell->GetTableRow();
-  *column = cell->GetTableColumn();
-  *row_extents = GetTableRowSpan();
-  *column_extents = GetTableColumnSpan();
-  *is_selected = false;  // Not supported.
-
-  return S_OK;
+  return AXPlatformNodeWin::get_rowColumnExtentsAtIndex(
+      index, row, column, row_extents, column_extents, is_selected);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::selectRow(long row) {
   WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SELECT_ROW);
   AddAccessibilityModeFlags(kScreenReaderAndHTMLAccessibilityModes);
-  return E_NOTIMPL;
+  if (!owner())
+    return E_FAIL;
+
+  return AXPlatformNodeWin::selectRow(row);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::selectColumn(long column) {
   WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SELECT_COLUMN);
   AddAccessibilityModeFlags(kScreenReaderAndHTMLAccessibilityModes);
-  return E_NOTIMPL;
+  if (!owner())
+    return E_FAIL;
+
+  return AXPlatformNodeWin::selectColumn(column);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::unselectRow(long row) {
   WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_UNSELECT_ROW);
   AddAccessibilityModeFlags(kScreenReaderAndHTMLAccessibilityModes);
-  return E_NOTIMPL;
+  if (!owner())
+    return E_FAIL;
+
+  return AXPlatformNodeWin::unselectRow(row);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::unselectColumn(long column) {
   WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_UNSELECT_COLUMN);
   AddAccessibilityModeFlags(kScreenReaderAndHTMLAccessibilityModes);
-  return E_NOTIMPL;
+  if (!owner())
+    return E_FAIL;
+
+  return AXPlatformNodeWin::unselectColumn(column);
 }
 
 STDMETHODIMP
 BrowserAccessibilityComWin::get_modelChange(IA2TableModelChange* model_change) {
-  return E_NOTIMPL;
+  if (!owner())
+    return E_FAIL;
+
+  return AXPlatformNodeWin::get_modelChange(model_change);
 }
 
 //
@@ -1541,26 +1377,16 @@
   if (!owner())
     return E_FAIL;
 
-  if (!cell)
-    return E_INVALIDARG;
-
-  AXPlatformNodeBase* table_cell =
-      GetTableCell(static_cast<int>(row), static_cast<int>(column));
-  if (table_cell) {
-    auto* node_win = static_cast<AXPlatformNodeWin*>(table_cell);
-    node_win->AddRef();
-    *cell = static_cast<IAccessible*>(node_win);
-    return S_OK;
-  }
-
-  *cell = nullptr;
-  return E_INVALIDARG;
+  return AXPlatformNodeWin::get_cellAt(row, column, cell);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_nSelectedCells(long* cell_count) {
   WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_N_SELECTED_CELLS);
   AddAccessibilityModeFlags(kScreenReaderAndHTMLAccessibilityModes);
-  return get_nSelectedChildren(cell_count);
+  if (!owner())
+    return E_FAIL;
+
+  return AXPlatformNodeWin::get_nSelectedCells(cell_count);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_selectedCells(
@@ -1571,12 +1397,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!cells || !n_selected_cells)
-    return E_INVALIDARG;
-
-  // TODO(dmazzoni): Implement this.
-  *n_selected_cells = 0;
-  return S_OK;
+  return AXPlatformNodeWin::get_selectedCells(cells, n_selected_cells);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_selectedColumns(long** columns,
@@ -1586,12 +1407,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!columns || !n_columns)
-    return E_INVALIDARG;
-
-  // TODO(dmazzoni): Implement this.
-  *n_columns = 0;
-  return S_OK;
+  return AXPlatformNodeWin::get_selectedColumns(columns, n_columns);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_selectedRows(long** rows,
@@ -1601,12 +1417,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!rows || !n_rows)
-    return E_INVALIDARG;
-
-  // TODO(dmazzoni): Implement this.
-  *n_rows = 0;
-  return S_OK;
+  return AXPlatformNodeWin::get_selectedRows(rows, n_rows);
 }
 
 //
@@ -1620,11 +1431,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!n_columns_spanned)
-    return E_INVALIDARG;
-
-  *n_columns_spanned = GetTableColumnSpan();
-  return S_OK;
+  return AXPlatformNodeWin::get_columnExtent(n_columns_spanned);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_columnHeaderCells(
@@ -1635,43 +1442,8 @@
   if (!owner())
     return E_FAIL;
 
-  if (!cell_accessibles || !n_column_header_cells)
-    return E_INVALIDARG;
-
-  *n_column_header_cells = 0;
-  auto* table = GetTable();
-  if (!table) {
-    NOTREACHED();
-    return S_FALSE;
-  }
-
-  int column = GetTableColumn();
-  int columns = GetTableColumnCount();
-  int rows = GetTableRowCount();
-  if (columns <= 0 || rows <= 0 || column < 0 || column >= columns)
-    return S_FALSE;
-
-  for (int i = 0; i < rows; ++i) {
-    auto* cell = GetTableCell(i, column);
-    if (cell && cell->GetData().role == ui::AX_ROLE_COLUMN_HEADER)
-      (*n_column_header_cells)++;
-  }
-
-  *cell_accessibles = static_cast<IUnknown**>(
-      CoTaskMemAlloc((*n_column_header_cells) * sizeof(cell_accessibles[0])));
-  int index = 0;
-  for (int i = 0; i < rows; ++i) {
-    AXPlatformNodeBase* cell = GetTableCell(i, column);
-    if (cell && cell->GetData().role == ui::AX_ROLE_COLUMN_HEADER) {
-      auto* node_win = static_cast<AXPlatformNodeWin*>(cell);
-      node_win->AddRef();
-
-      (*cell_accessibles)[index] = static_cast<IAccessible*>(node_win);
-      ++index;
-    }
-  }
-
-  return S_OK;
+  return AXPlatformNodeWin::get_columnHeaderCells(cell_accessibles,
+                                                  n_column_header_cells);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_columnIndex(long* column_index) {
@@ -1680,11 +1452,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!column_index)
-    return E_INVALIDARG;
-
-  *column_index = GetTableColumn();
-  return S_OK;
+  return AXPlatformNodeWin::get_columnIndex(column_index);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_rowExtent(long* n_rows_spanned) {
@@ -1693,11 +1461,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!n_rows_spanned)
-    return E_INVALIDARG;
-
-  *n_rows_spanned = GetTableRowSpan();
-  return S_OK;
+  return AXPlatformNodeWin::get_rowExtent(n_rows_spanned);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_rowHeaderCells(
@@ -1708,43 +1472,8 @@
   if (!owner())
     return E_FAIL;
 
-  if (!cell_accessibles || !n_row_header_cells)
-    return E_INVALIDARG;
-
-  *n_row_header_cells = 0;
-  auto* table = GetTable();
-  if (!table) {
-    NOTREACHED();
-    return S_FALSE;
-  }
-
-  int row = GetTableRow();
-  int columns = GetTableColumnCount();
-  int rows = GetTableRowCount();
-  if (columns <= 0 || rows <= 0 || row < 0 || row >= rows)
-    return S_FALSE;
-
-  for (int i = 0; i < columns; ++i) {
-    auto* cell = GetTableCell(row, i);
-    if (cell && cell->GetData().role == ui::AX_ROLE_ROW_HEADER)
-      (*n_row_header_cells)++;
-  }
-
-  *cell_accessibles = static_cast<IUnknown**>(
-      CoTaskMemAlloc((*n_row_header_cells) * sizeof(cell_accessibles[0])));
-  int index = 0;
-  for (int i = 0; i < columns; ++i) {
-    AXPlatformNodeBase* cell = GetTableCell(row, i);
-    if (cell && cell->GetData().role == ui::AX_ROLE_ROW_HEADER) {
-      auto* node_win = static_cast<AXPlatformNodeWin*>(cell);
-      node_win->AddRef();
-
-      (*cell_accessibles)[index] = static_cast<IAccessible*>(node_win);
-      ++index;
-    }
-  }
-
-  return S_OK;
+  return AXPlatformNodeWin::get_rowHeaderCells(cell_accessibles,
+                                               n_row_header_cells);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_rowIndex(long* row_index) {
@@ -1753,11 +1482,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!row_index)
-    return E_INVALIDARG;
-
-  *row_index = GetTableRow();
-  return S_OK;
+  return AXPlatformNodeWin::get_rowIndex(row_index);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_isSelected(boolean* is_selected) {
@@ -1766,11 +1491,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!is_selected)
-    return E_INVALIDARG;
-
-  *is_selected = false;
-  return S_OK;
+  return AXPlatformNodeWin::get_isSelected(is_selected);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_rowColumnExtents(
@@ -1784,18 +1505,8 @@
   if (!owner())
     return E_FAIL;
 
-  if (!row_index || !column_index || !row_extents || !column_extents ||
-      !is_selected) {
-    return E_INVALIDARG;
-  }
-
-  *row_index = GetTableRow();
-  *column_index = GetTableColumn();
-  *row_extents = GetTableRowSpan();
-  *column_extents = GetTableColumnSpan();
-  *is_selected = false;  // Not supported.
-
-  return S_OK;
+  return AXPlatformNodeWin::get_rowColumnExtents(
+      row_index, column_index, row_extents, column_extents, is_selected);
 }
 
 STDMETHODIMP BrowserAccessibilityComWin::get_table(IUnknown** table) {
@@ -1804,22 +1515,7 @@
   if (!owner())
     return E_FAIL;
 
-  if (!table)
-    return E_INVALIDARG;
-
-  auto* find_table = GetTable();
-  if (!find_table) {
-    *table = nullptr;
-    return S_FALSE;
-  }
-
-  // The IAccessibleTable interface is still on the BrowserAccessibilityComWin
-  // class.
-  auto* node_win = static_cast<BrowserAccessibilityComWin*>(find_table);
-  node_win->AddRef();
-
-  *table = static_cast<IAccessibleTable*>(node_win);
-  return S_OK;
+  return AXPlatformNodeWin::get_table(table);
 }
 
 //
diff --git a/content/browser/accessibility/browser_accessibility_com_win.h b/content/browser/accessibility/browser_accessibility_com_win.h
index 082580b..28397d3 100644
--- a/content/browser/accessibility/browser_accessibility_com_win.h
+++ b/content/browser/accessibility/browser_accessibility_com_win.h
@@ -54,9 +54,6 @@
                                  public IAccessibleHyperlink,
                                  public IAccessibleHypertext,
                                  public IAccessibleImage,
-                                 public IAccessibleTable,
-                                 public IAccessibleTable2,
-                                 public IAccessibleTableCell,
                                  public IAccessibleValue,
                                  public ISimpleDOMDocument,
                                  public ISimpleDOMNode,
diff --git a/content/browser/background_fetch/background_fetch_context.cc b/content/browser/background_fetch/background_fetch_context.cc
index 4a26d01..32bfb9f 100644
--- a/content/browser/background_fetch/background_fetch_context.cc
+++ b/content/browser/background_fetch/background_fetch_context.cc
@@ -76,8 +76,7 @@
     const BackgroundFetchRegistrationId& registration_id,
     const BackgroundFetchOptions& options,
     blink::mojom::BackgroundFetchService::FetchCallback callback,
-    blink::mojom::BackgroundFetchError error,
-    std::vector<scoped_refptr<BackgroundFetchRequestInfo>> initial_requests) {
+    blink::mojom::BackgroundFetchError error) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
   RecordRegistrationCreatedError(error);
@@ -87,7 +86,7 @@
   }
 
   // Create the BackgroundFetchJobController, which will do the actual fetching.
-  CreateController(registration_id, options, std::move(initial_requests));
+  CreateController(registration_id, options);
 
   // Create the BackgroundFetchRegistration the renderer process will receive,
   // which enables it to resolve the promise telling the developer it worked.
@@ -142,8 +141,7 @@
 
 void BackgroundFetchContext::CreateController(
     const BackgroundFetchRegistrationId& registration_id,
-    const BackgroundFetchOptions& options,
-    std::vector<scoped_refptr<BackgroundFetchRequestInfo>> initial_requests) {
+    const BackgroundFetchOptions& options) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
   net::NetworkTrafficAnnotationTag traffic_annotation =
@@ -183,9 +181,9 @@
   // TODO(peter): We should actually be able to use Background Fetch in layout
   // tests. That requires a download manager and a request context.
   if (request_context_getter_) {
-    // Start fetching the |initial_requests| immediately. At some point in the
+    // Start fetching the first few requests immediately. At some point in the
     // future we may want a more elaborate scheduling mechanism here.
-    controller->Start(std::move(initial_requests));
+    controller->Start();
   }
 
   active_fetches_.insert(
diff --git a/content/browser/background_fetch/background_fetch_context.h b/content/browser/background_fetch/background_fetch_context.h
index 944b5bc..d96e76b 100644
--- a/content/browser/background_fetch/background_fetch_context.h
+++ b/content/browser/background_fetch/background_fetch_context.h
@@ -31,7 +31,6 @@
 class BackgroundFetchJobController;
 struct BackgroundFetchOptions;
 class BackgroundFetchRegistrationId;
-class BackgroundFetchRequestInfo;
 class BlobHandle;
 class BrowserContext;
 class ServiceWorkerContextWrapper;
@@ -84,18 +83,15 @@
 
   // Creates a new Job Controller for the given |registration_id| and |options|,
   // which will start fetching the files that are part of the registration.
-  void CreateController(
-      const BackgroundFetchRegistrationId& registration_id,
-      const BackgroundFetchOptions& options,
-      std::vector<scoped_refptr<BackgroundFetchRequestInfo>> initial_requests);
+  void CreateController(const BackgroundFetchRegistrationId& registration_id,
+                        const BackgroundFetchOptions& options);
 
   // Called when a new registration has been created by the data manager.
   void DidCreateRegistration(
       const BackgroundFetchRegistrationId& registration_id,
       const BackgroundFetchOptions& options,
       blink::mojom::BackgroundFetchService::FetchCallback callback,
-      blink::mojom::BackgroundFetchError error,
-      std::vector<scoped_refptr<BackgroundFetchRequestInfo>> initial_requests);
+      blink::mojom::BackgroundFetchError error);
 
   // Called when the given |controller| has finished processing its job.
   void DidCompleteJob(BackgroundFetchJobController* controller);
diff --git a/content/browser/background_fetch/background_fetch_data_manager.cc b/content/browser/background_fetch/background_fetch_data_manager.cc
index 52c5ae50..408dc1db 100644
--- a/content/browser/background_fetch/background_fetch_data_manager.cc
+++ b/content/browser/background_fetch/background_fetch_data_manager.cc
@@ -54,7 +54,7 @@
   bool HasPendingRequests() const { return !pending_requests_.empty(); }
 
   // Consumes a request from the queue that is to be fetched.
-  scoped_refptr<BackgroundFetchRequestInfo> GetPendingRequest() {
+  scoped_refptr<BackgroundFetchRequestInfo> PopNextPendingRequest() {
     DCHECK(!pending_requests_.empty());
 
     auto request = pending_requests_.front();
@@ -83,7 +83,7 @@
 
   // Marks the |request| as having completed. Verifies that the |request| is
   // currently active and moves it to the |completed_requests_| vector.
-  void MarkRequestAsComplete(BackgroundFetchRequestInfo* request) {
+  bool MarkRequestAsComplete(BackgroundFetchRequestInfo* request) {
     const auto iter = std::find_if(
         active_requests_.begin(), active_requests_.end(),
         [&request](scoped_refptr<BackgroundFetchRequestInfo> active_request) {
@@ -95,6 +95,10 @@
 
     completed_requests_.push_back(*iter);
     active_requests_.erase(iter);
+
+    bool has_pending_or_active_requests =
+        !pending_requests_.empty() || !active_requests_.empty();
+    return has_pending_or_active_requests;
   }
 
   // Returns the vector with all completed requests part of this registration.
@@ -146,31 +150,33 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (registrations_.find(registration_id) != registrations_.end()) {
-    std::move(callback).Run(
-        blink::mojom::BackgroundFetchError::DUPLICATED_TAG,
-        std::vector<scoped_refptr<BackgroundFetchRequestInfo>>());
+    std::move(callback).Run(blink::mojom::BackgroundFetchError::DUPLICATED_TAG);
     return;
   }
 
-  std::unique_ptr<RegistrationData> registration_data =
-      base::MakeUnique<RegistrationData>(requests, options);
-
-  // Create a vector with the initial requests to feed the Job Controller with.
-  std::vector<scoped_refptr<BackgroundFetchRequestInfo>> initial_requests;
-  for (size_t i = 0; i < kMaximumBackgroundFetchParallelRequests; ++i) {
-    if (!registration_data->HasPendingRequests())
-      break;
-
-    initial_requests.push_back(registration_data->GetPendingRequest());
-  }
-
-  // Store the created |registration_data| so that we can easily access it.
-  registrations_.insert(
-      std::make_pair(registration_id, std::move(registration_data)));
+  // Create the |RegistrationData|, and store it for easy access.
+  registrations_.insert(std::make_pair(
+      registration_id, base::MakeUnique<RegistrationData>(requests, options)));
 
   // Inform the |callback| of the newly created registration.
-  std::move(callback).Run(blink::mojom::BackgroundFetchError::NONE,
-                          std::move(initial_requests));
+  std::move(callback).Run(blink::mojom::BackgroundFetchError::NONE);
+}
+
+void BackgroundFetchDataManager::PopNextRequest(
+    const BackgroundFetchRegistrationId& registration_id,
+    NextRequestCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  auto iter = registrations_.find(registration_id);
+  DCHECK(iter != registrations_.end());
+
+  RegistrationData* registration_data = iter->second.get();
+
+  scoped_refptr<BackgroundFetchRequestInfo> next_request;
+  if (registration_data->HasPendingRequests())
+    next_request = registration_data->PopNextPendingRequest();
+
+  std::move(callback).Run(std::move(next_request));
 }
 
 void BackgroundFetchDataManager::MarkRequestAsStarted(
@@ -186,23 +192,20 @@
   registration_data->MarkRequestAsStarted(request, download_guid);
 }
 
-void BackgroundFetchDataManager::MarkRequestAsCompleteAndGetNextRequest(
+void BackgroundFetchDataManager::MarkRequestAsComplete(
     const BackgroundFetchRegistrationId& registration_id,
     BackgroundFetchRequestInfo* request,
-    NextRequestCallback callback) {
+    MarkedCompleteCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   auto iter = registrations_.find(registration_id);
   DCHECK(iter != registrations_.end());
 
   RegistrationData* registration_data = iter->second.get();
-  registration_data->MarkRequestAsComplete(request);
+  bool has_pending_or_active_requests =
+      registration_data->MarkRequestAsComplete(request);
 
-  scoped_refptr<BackgroundFetchRequestInfo> next_request;
-  if (registration_data->HasPendingRequests())
-    next_request = registration_data->GetPendingRequest();
-
-  std::move(callback).Run(std::move(next_request));
+  std::move(callback).Run(has_pending_or_active_requests);
 }
 
 void BackgroundFetchDataManager::GetSettledFetchesForRegistration(
diff --git a/content/browser/background_fetch/background_fetch_data_manager.h b/content/browser/background_fetch/background_fetch_data_manager.h
index ab17f48..424d5be 100644
--- a/content/browser/background_fetch/background_fetch_data_manager.h
+++ b/content/browser/background_fetch/background_fetch_data_manager.h
@@ -33,13 +33,14 @@
 // which will keep the metadata up to date.
 class CONTENT_EXPORT BackgroundFetchDataManager {
  public:
-  using CreateRegistrationCallback = base::OnceCallback<void(
-      blink::mojom::BackgroundFetchError,
-      std::vector<scoped_refptr<BackgroundFetchRequestInfo>>)>;
+  using CreateRegistrationCallback =
+      base::OnceCallback<void(blink::mojom::BackgroundFetchError)>;
   using DeleteRegistrationCallback =
       base::OnceCallback<void(blink::mojom::BackgroundFetchError)>;
   using NextRequestCallback =
       base::OnceCallback<void(scoped_refptr<BackgroundFetchRequestInfo>)>;
+  using MarkedCompleteCallback =
+      base::OnceCallback<void(bool /* has_pending_or_active_requests */)>;
   using SettledFetchesCallback =
       base::OnceCallback<void(blink::mojom::BackgroundFetchError,
                               bool /* background_fetch_succeeded */,
@@ -58,6 +59,11 @@
       const BackgroundFetchOptions& options,
       CreateRegistrationCallback callback);
 
+  // Removes the next request, if any, from the pending requests queue, and
+  // invokes the |callback| with that request, else a null request.
+  void PopNextRequest(const BackgroundFetchRegistrationId& registration_id,
+                      NextRequestCallback callback);
+
   // Marks that the |request|, part of the Background Fetch identified by
   // |registration_id|, has been started as |download_guid|.
   void MarkRequestAsStarted(
@@ -66,12 +72,11 @@
       const std::string& download_guid);
 
   // Marks that the |request|, part of the Background Fetch identified by
-  // |registration_id|, has completed. Will invoke the |callback| with the
-  // next request, if any, when the operation has completed.
-  void MarkRequestAsCompleteAndGetNextRequest(
+  // |registration_id|, has completed.
+  void MarkRequestAsComplete(
       const BackgroundFetchRegistrationId& registration_id,
       BackgroundFetchRequestInfo* request,
-      NextRequestCallback callback);
+      MarkedCompleteCallback callback);
 
   // Reads all settled fetches for the given |registration_id|. Both the Request
   // and Response objects will be initialised based on the stored data. Will
diff --git a/content/browser/background_fetch/background_fetch_data_manager_unittest.cc b/content/browser/background_fetch/background_fetch_data_manager_unittest.cc
index 9e4eb46..9a9e71a 100644
--- a/content/browser/background_fetch/background_fetch_data_manager_unittest.cc
+++ b/content/browser/background_fetch/background_fetch_data_manager_unittest.cc
@@ -35,9 +35,7 @@
       const BackgroundFetchRegistrationId& registration_id,
       const std::vector<ServiceWorkerFetchRequest>& requests,
       const BackgroundFetchOptions& options,
-      blink::mojom::BackgroundFetchError* out_error,
-      std::vector<scoped_refptr<BackgroundFetchRequestInfo>>*
-          out_initial_requests) {
+      blink::mojom::BackgroundFetchError* out_error) {
     DCHECK(out_error);
 
     base::RunLoop run_loop;
@@ -45,7 +43,7 @@
         registration_id, requests, options,
         base::BindOnce(&BackgroundFetchDataManagerTest::DidCreateRegistration,
                        base::Unretained(this), run_loop.QuitClosure(),
-                       out_error, out_initial_requests));
+                       out_error));
     run_loop.Run();
   }
 
@@ -64,15 +62,10 @@
   }
 
  private:
-  void DidCreateRegistration(
-      base::Closure quit_closure,
-      blink::mojom::BackgroundFetchError* out_error,
-      std::vector<scoped_refptr<BackgroundFetchRequestInfo>>*
-          out_initial_requests,
-      blink::mojom::BackgroundFetchError error,
-      std::vector<scoped_refptr<BackgroundFetchRequestInfo>> initial_requests) {
+  void DidCreateRegistration(base::Closure quit_closure,
+                             blink::mojom::BackgroundFetchError* out_error,
+                             blink::mojom::BackgroundFetchError error) {
     *out_error = error;
-    *out_initial_requests = std::move(initial_requests);
 
     quit_closure.Run();
   }
@@ -100,20 +93,19 @@
   BackgroundFetchOptions options;
 
   blink::mojom::BackgroundFetchError error;
-  std::vector<scoped_refptr<BackgroundFetchRequestInfo>> initial_requests;
 
   // Deleting the not-yet-created registration should fail.
   ASSERT_NO_FATAL_FAILURE(DeleteRegistration(registration_id, &error));
   EXPECT_EQ(error, blink::mojom::BackgroundFetchError::INVALID_TAG);
 
   // Creating the initial registration should succeed.
-  ASSERT_NO_FATAL_FAILURE(CreateRegistration(registration_id, requests, options,
-                                             &error, &initial_requests));
+  ASSERT_NO_FATAL_FAILURE(
+      CreateRegistration(registration_id, requests, options, &error));
   EXPECT_EQ(error, blink::mojom::BackgroundFetchError::NONE);
 
   // Attempting to create it again should yield an error.
-  ASSERT_NO_FATAL_FAILURE(CreateRegistration(registration_id, requests, options,
-                                             &error, &initial_requests));
+  ASSERT_NO_FATAL_FAILURE(
+      CreateRegistration(registration_id, requests, options, &error));
   EXPECT_EQ(error, blink::mojom::BackgroundFetchError::DUPLICATED_TAG);
 
   // Deleting the registration should succeed.
@@ -121,8 +113,8 @@
   EXPECT_EQ(error, blink::mojom::BackgroundFetchError::NONE);
 
   // And then recreating the registration again should work fine.
-  ASSERT_NO_FATAL_FAILURE(CreateRegistration(registration_id, requests, options,
-                                             &error, &initial_requests));
+  ASSERT_NO_FATAL_FAILURE(
+      CreateRegistration(registration_id, requests, options, &error));
   EXPECT_EQ(error, blink::mojom::BackgroundFetchError::NONE);
 }
 
diff --git a/content/browser/background_fetch/background_fetch_job_controller.cc b/content/browser/background_fetch/background_fetch_job_controller.cc
index 1032fa2..c8a75672 100644
--- a/content/browser/background_fetch/background_fetch_job_controller.cc
+++ b/content/browser/background_fetch/background_fetch_job_controller.cc
@@ -260,22 +260,33 @@
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 };
 
-void BackgroundFetchJobController::Start(
-    std::vector<scoped_refptr<BackgroundFetchRequestInfo>> initial_requests) {
+void BackgroundFetchJobController::Start() {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  DCHECK_LE(initial_requests.size(), kMaximumBackgroundFetchParallelRequests);
   DCHECK_EQ(state_, State::INITIALIZED);
 
   state_ = State::FETCHING;
 
-  for (const auto& request : initial_requests)
-    StartRequest(request);
+  // TODO(crbug.com/741609): Enforce kMaximumBackgroundFetchParallelRequests
+  // globally and/or per origin rather than per fetch.
+  for (size_t i = 0; i < kMaximumBackgroundFetchParallelRequests; i++) {
+    data_manager_->PopNextRequest(
+        registration_id_,
+        base::BindOnce(&BackgroundFetchJobController::StartRequest,
+                       weak_ptr_factory_.GetWeakPtr()));
+  }
 }
 
 void BackgroundFetchJobController::StartRequest(
     scoped_refptr<BackgroundFetchRequestInfo> request) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   DCHECK_EQ(state_, State::FETCHING);
+  if (!request) {
+    // This can happen when |Start| tries to start multiple initial requests,
+    // but the fetch does not contain that many pending requests; or when
+    // |DidMarkRequestCompleted| tries to start the next request but there are
+    // none left.
+    return;
+  }
   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
                           base::Bind(&Core::StartRequest, ui_core_ptr_,
                                      std::move(request), traffic_annotation_));
@@ -295,33 +306,29 @@
 
   // The DataManager must acknowledge that it stored the data and that there are
   // no more pending requests to avoid marking this job as completed too early.
-  pending_completed_file_acknowledgements_++;
-
-  data_manager_->MarkRequestAsCompleteAndGetNextRequest(
+  data_manager_->MarkRequestAsComplete(
       registration_id_, request.get(),
-      base::BindOnce(&BackgroundFetchJobController::DidGetNextRequest,
+      base::BindOnce(&BackgroundFetchJobController::DidMarkRequestCompleted,
                      weak_ptr_factory_.GetWeakPtr()));
 }
 
-void BackgroundFetchJobController::DidGetNextRequest(
-    scoped_refptr<BackgroundFetchRequestInfo> request) {
+void BackgroundFetchJobController::DidMarkRequestCompleted(
+    bool has_pending_or_active_requests) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  DCHECK_LE(pending_completed_file_acknowledgements_, 1);
-  pending_completed_file_acknowledgements_--;
+  DCHECK_EQ(state_, State::FETCHING);
 
-  // If a |request| has been given, start downloading the file and bail.
-  if (request) {
-    StartRequest(std::move(request));
+  // If not all requests have completed, start a pending request if there are
+  // any left, and bail.
+  if (has_pending_or_active_requests) {
+    data_manager_->PopNextRequest(
+        registration_id_,
+        base::BindOnce(&BackgroundFetchJobController::StartRequest,
+                       weak_ptr_factory_.GetWeakPtr()));
     return;
   }
 
-  // If there are outstanding completed file acknowlegements, bail as well.
-  if (pending_completed_file_acknowledgements_ > 0)
-    return;
-
-  state_ = State::COMPLETED;
-
   // Otherwise the job this controller is responsible for has completed.
+  state_ = State::COMPLETED;
   std::move(completed_callback_).Run(this);
 }
 
@@ -334,11 +341,19 @@
 void BackgroundFetchJobController::Abort() {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
+  switch (state_) {
+    case State::INITIALIZED:
+    case State::FETCHING:
+      break;
+    case State::ABORTED:
+    case State::COMPLETED:
+      return;  // Ignore attempt to abort after completion/abort.
+  }
+
   // TODO(harkness): Abort all in-progress downloads.
 
   state_ = State::ABORTED;
-
-  // Inform the owner of the controller about the job having completed.
+  // Inform the owner of the controller about the job having aborted.
   std::move(completed_callback_).Run(this);
 }
 
diff --git a/content/browser/background_fetch/background_fetch_job_controller.h b/content/browser/background_fetch/background_fetch_job_controller.h
index 3698664..a944352b0 100644
--- a/content/browser/background_fetch/background_fetch_job_controller.h
+++ b/content/browser/background_fetch/background_fetch_job_controller.h
@@ -48,10 +48,9 @@
       const net::NetworkTrafficAnnotationTag& traffic_annotation);
   ~BackgroundFetchJobController();
 
-  // Starts fetching the |initial_fetches|. The controller will continue to
+  // Starts fetching the first few requests. The controller will continue to
   // fetch new content until all requests have been handled.
-  void Start(
-      std::vector<scoped_refptr<BackgroundFetchRequestInfo>> initial_requests);
+  void Start();
 
   // Updates the representation of this Background Fetch in the user interface
   // to match the given |title|.
@@ -85,9 +84,8 @@
   // Called when the given |request| has been completed.
   void DidCompleteRequest(scoped_refptr<BackgroundFetchRequestInfo> request);
 
-  // Called when a completed download has been marked as such in the DataManager
-  // and the next request, if any, has been read from storage.
-  void DidGetNextRequest(scoped_refptr<BackgroundFetchRequestInfo> request);
+  // Called when a completed download has been marked as such in DataManager.
+  void DidMarkRequestCompleted(bool has_pending_or_active_requests);
 
   // The registration id on behalf of which this controller is fetching data.
   BackgroundFetchRegistrationId registration_id_;
@@ -106,9 +104,6 @@
   // will be kept alive until after the JobController is destroyed.
   BackgroundFetchDataManager* data_manager_;
 
-  // Number of outstanding acknowledgements we still expect to receive.
-  int pending_completed_file_acknowledgements_ = 0;
-
   // Callback for when all fetches have been completed.
   CompletedCallback completed_callback_;
 
diff --git a/content/browser/background_fetch/background_fetch_job_controller_unittest.cc b/content/browser/background_fetch/background_fetch_job_controller_unittest.cc
index 201299e..b2e3e3e 100644
--- a/content/browser/background_fetch/background_fetch_job_controller_unittest.cc
+++ b/content/browser/background_fetch/background_fetch_job_controller_unittest.cc
@@ -41,11 +41,8 @@
   // included |request_data|. Should be wrapped in ASSERT_NO_FATAL_FAILURE().
   void CreateRegistrationForRequests(
       BackgroundFetchRegistrationId* registration_id,
-      std::vector<scoped_refptr<BackgroundFetchRequestInfo>>*
-          out_initial_requests,
       std::map<std::string /* url */, std::string /* method */> request_data) {
     DCHECK(registration_id);
-    DCHECK(out_initial_requests);
 
     ASSERT_TRUE(CreateRegistrationId(kExampleTag, registration_id));
 
@@ -64,14 +61,10 @@
     data_manager_.CreateRegistration(
         *registration_id, requests, BackgroundFetchOptions(),
         base::BindOnce(&BackgroundFetchJobControllerTest::DidCreateRegistration,
-                       base::Unretained(this), &error, out_initial_requests,
-                       run_loop.QuitClosure()));
+                       base::Unretained(this), &error, run_loop.QuitClosure()));
     run_loop.Run();
 
     ASSERT_EQ(error, blink::mojom::BackgroundFetchError::NONE);
-    ASSERT_GE(out_initial_requests->size(), 1u);
-    ASSERT_LE(out_initial_requests->size(),
-              kMaximumBackgroundFetchParallelRequests);
 
     // Provide fake responses for the given |request_data| pairs.
     for (const auto& pair : request_data) {
@@ -107,18 +100,12 @@
   base::OnceClosure job_completed_closure_;
 
  private:
-  void DidCreateRegistration(
-      blink::mojom::BackgroundFetchError* out_error,
-      std::vector<scoped_refptr<BackgroundFetchRequestInfo>>*
-          out_initial_requests,
-      const base::Closure& quit_closure,
-      blink::mojom::BackgroundFetchError error,
-      std::vector<scoped_refptr<BackgroundFetchRequestInfo>> initial_requests) {
+  void DidCreateRegistration(blink::mojom::BackgroundFetchError* out_error,
+                             const base::Closure& quit_closure,
+                             blink::mojom::BackgroundFetchError error) {
     DCHECK(out_error);
-    DCHECK(out_initial_requests);
 
     *out_error = error;
-    *out_initial_requests = std::move(initial_requests);
 
     quit_closure.Run();
   }
@@ -140,11 +127,9 @@
 
 TEST_F(BackgroundFetchJobControllerTest, SingleRequestJob) {
   BackgroundFetchRegistrationId registration_id;
-  std::vector<scoped_refptr<BackgroundFetchRequestInfo>> initial_requests;
 
   ASSERT_NO_FATAL_FAILURE(CreateRegistrationForRequests(
-      &registration_id, &initial_requests,
-      {{"https://example.com/funny_cat.png", "GET"}}));
+      &registration_id, {{"https://example.com/funny_cat.png", "GET"}}));
 
   std::unique_ptr<BackgroundFetchJobController> controller =
       CreateJobController(registration_id);
@@ -152,7 +137,7 @@
   EXPECT_EQ(controller->state(),
             BackgroundFetchJobController::State::INITIALIZED);
 
-  controller->Start(initial_requests /* deliberate copy */);
+  controller->Start();
   EXPECT_EQ(controller->state(), BackgroundFetchJobController::State::FETCHING);
 
   // Mark the single download item as finished, completing the job.
@@ -170,19 +155,17 @@
 
 TEST_F(BackgroundFetchJobControllerTest, MultipleRequestJob) {
   BackgroundFetchRegistrationId registration_id;
-  std::vector<scoped_refptr<BackgroundFetchRequestInfo>> initial_requests;
 
   // This test should always issue more requests than the number of allowed
   // parallel requests. That way we ensure testing the iteration behaviour.
   ASSERT_GT(5u, kMaximumBackgroundFetchParallelRequests);
 
   ASSERT_NO_FATAL_FAILURE(CreateRegistrationForRequests(
-      &registration_id, &initial_requests,
-      {{"https://example.com/funny_cat.png", "GET"},
-       {"https://example.com/scary_cat.png", "GET"},
-       {"https://example.com/crazy_cat.png", "GET"},
-       {"https://example.com/silly_cat.png", "GET"},
-       {"https://example.com/happy_cat.png", "GET"}}));
+      &registration_id, {{"https://example.com/funny_cat.png", "GET"},
+                         {"https://example.com/scary_cat.png", "GET"},
+                         {"https://example.com/crazy_cat.png", "GET"},
+                         {"https://example.com/silly_cat.png", "GET"},
+                         {"https://example.com/happy_cat.png", "GET"}}));
 
   std::unique_ptr<BackgroundFetchJobController> controller =
       CreateJobController(registration_id);
@@ -196,7 +179,7 @@
     base::RunLoop run_loop;
     job_completed_closure_ = run_loop.QuitClosure();
 
-    controller->Start(initial_requests /* deliberate copy */);
+    controller->Start();
     EXPECT_EQ(controller->state(),
               BackgroundFetchJobController::State::FETCHING);
 
@@ -210,11 +193,9 @@
 
 TEST_F(BackgroundFetchJobControllerTest, AbortJob) {
   BackgroundFetchRegistrationId registration_id;
-  std::vector<scoped_refptr<BackgroundFetchRequestInfo>> initial_requests;
 
   ASSERT_NO_FATAL_FAILURE(CreateRegistrationForRequests(
-      &registration_id, &initial_requests,
-      {{"https://example.com/sad_cat.png", "GET"}}));
+      &registration_id, {{"https://example.com/sad_cat.png", "GET"}}));
 
   std::unique_ptr<BackgroundFetchJobController> controller =
       CreateJobController(registration_id);
@@ -222,12 +203,12 @@
   EXPECT_EQ(controller->state(),
             BackgroundFetchJobController::State::INITIALIZED);
 
-  // Start the set of |initial_requests|, and abort them immediately after.
+  // Start the first few requests, and abort them immediately after.
   {
     base::RunLoop run_loop;
     job_completed_closure_ = run_loop.QuitClosure();
 
-    controller->Start(initial_requests /* deliberate copy */);
+    controller->Start();
     EXPECT_EQ(controller->state(),
               BackgroundFetchJobController::State::FETCHING);
 
diff --git a/content/browser/browser_child_process_host_impl.cc b/content/browser/browser_child_process_host_impl.cc
index 47cfea1..15f619a 100644
--- a/content/browser/browser_child_process_host_impl.cc
+++ b/content/browser/browser_child_process_host_impl.cc
@@ -499,9 +499,13 @@
       break;
 
     default:
-      UMA_HISTOGRAM_ENUMERATION(
-          "UMA.SubprocessMetricsProvider.UntrackedProcesses",
-          data_.process_type, PROCESS_TYPE_CONTENT_END);
+      // Report new processes. "Custom" ones are renumbered to 1000+ so that
+      // they won't conflict with any standard ones in the future.
+      int process_type = data_.process_type;
+      if (process_type >= PROCESS_TYPE_CONTENT_END)
+        process_type += 1000 - PROCESS_TYPE_CONTENT_END;
+      UMA_HISTOGRAM_SPARSE_SLOWLY(
+          "UMA.SubprocessMetricsProvider.UntrackedProcesses", process_type);
       return;
   }
 
diff --git a/content/browser/browser_context.cc b/content/browser/browser_context.cc
index 4330c11..0b7c64f03 100644
--- a/content/browser/browser_context.cc
+++ b/content/browser/browser_context.cc
@@ -345,7 +345,7 @@
     const GURL& origin,
     int64_t service_worker_registration_id,
     const PushEventPayload& payload,
-    const base::Callback<void(PushDeliveryStatus)>& callback) {
+    const base::Callback<void(mojom::PushDeliveryStatus)>& callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   PushMessagingRouter::DeliverMessage(browser_context, origin,
                                       service_worker_registration_id, payload,
diff --git a/content/browser/cocoa/system_hotkey_helper_mac.mm b/content/browser/cocoa/system_hotkey_helper_mac.mm
index cbac199..635a29d 100644
--- a/content/browser/cocoa/system_hotkey_helper_mac.mm
+++ b/content/browser/cocoa/system_hotkey_helper_mac.mm
@@ -34,7 +34,9 @@
 
 void SystemHotkeyHelperMac::DeferredLoadSystemHotkeys() {
   base::PostDelayedTaskWithTraits(
-      FROM_HERE, {base::MayBlock()},
+      FROM_HERE,
+      {base::TaskPriority::USER_VISIBLE,
+       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN, base::MayBlock()},
       base::Bind(&SystemHotkeyHelperMac::LoadSystemHotkeys,
                  base::Unretained(this)),
       base::TimeDelta::FromSeconds(kLoadHotkeysDelaySeconds));
diff --git a/content/browser/devtools/protocol/service_worker_handler.cc b/content/browser/devtools/protocol/service_worker_handler.cc
index 1d2f1a6..9ad73e7 100644
--- a/content/browser/devtools/protocol/service_worker_handler.cc
+++ b/content/browser/devtools/protocol/service_worker_handler.cc
@@ -28,7 +28,7 @@
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/push_event_payload.h"
-#include "content/public/common/push_messaging_status.h"
+#include "content/public/common/push_messaging_status.mojom.h"
 #include "url/gurl.h"
 
 namespace content {
@@ -47,8 +47,7 @@
     ServiceWorkerStatusCode status) {
 }
 
-void PushDeliveryNoOp(PushDeliveryStatus status) {
-}
+void PushDeliveryNoOp(mojom::PushDeliveryStatus status) {}
 
 const std::string GetVersionRunningStatusString(
     EmbeddedWorkerStatus running_status) {
diff --git a/content/browser/gpu/gpu_process_host.cc b/content/browser/gpu/gpu_process_host.cc
index 6005e0d..cfe6fab 100644
--- a/content/browser/gpu/gpu_process_host.cc
+++ b/content/browser/gpu/gpu_process_host.cc
@@ -1217,8 +1217,11 @@
   std::string prefix = GetShaderPrefixKey(data);
   bool prefix_ok = !key.compare(0, prefix.length(), prefix);
   UMA_HISTOGRAM_BOOLEAN("GPU.ShaderLoadPrefixOK", prefix_ok);
-  if (prefix_ok)
-    gpu_service_ptr_->LoadedShader(data);
+  if (prefix_ok) {
+    // Remove the prefix from the key before load.
+    std::string key_no_prefix = key.substr(prefix.length() + 1);
+    gpu_service_ptr_->LoadedShader(key_no_prefix, data);
+  }
 }
 
 ui::mojom::GpuService* GpuProcessHost::gpu_service() {
diff --git a/content/browser/push_messaging/push_messaging_manager.cc b/content/browser/push_messaging/push_messaging_manager.cc
index 3b0ee0f..2cf4537 100644
--- a/content/browser/push_messaging/push_messaging_manager.cc
+++ b/content/browser/push_messaging/push_messaging_manager.cc
@@ -19,6 +19,7 @@
 #include "content/browser/service_worker/service_worker_context_core.h"
 #include "content/browser/service_worker/service_worker_context_wrapper.h"
 #include "content/browser/service_worker/service_worker_storage.h"
+#include "content/common/push_messaging.mojom.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/permission_manager.h"
 #include "content/public/browser/permission_type.h"
@@ -28,7 +29,7 @@
 #include "content/public/common/child_process_host.h"
 #include "content/public/common/console_message_level.h"
 #include "content/public/common/content_switches.h"
-#include "content/public/common/push_messaging_status.h"
+#include "content/public/common/push_messaging_status.mojom.h"
 #include "third_party/WebKit/public/platform/modules/push_messaging/WebPushPermissionStatus.h"
 
 namespace content {
@@ -51,26 +52,62 @@
 
 // These UMA methods are called from the IO and/or UI threads. Racey but ok, see
 // https://groups.google.com/a/chromium.org/d/msg/chromium-dev/FNzZRJtN2aw/Aw0CWAXJJ1kJ
-void RecordRegistrationStatus(PushRegistrationStatus status) {
+void RecordRegistrationStatus(mojom::PushRegistrationStatus status) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO) ||
          BrowserThread::CurrentlyOn(BrowserThread::UI));
-  UMA_HISTOGRAM_ENUMERATION("PushMessaging.RegistrationStatus", status,
-                            PUSH_REGISTRATION_STATUS_LAST + 1);
+  UMA_HISTOGRAM_ENUMERATION(
+      "PushMessaging.RegistrationStatus", status,
+      static_cast<int>(mojom::PushRegistrationStatus::LAST) + 1);
 }
-void RecordUnregistrationStatus(PushUnregistrationStatus status) {
+void RecordUnregistrationStatus(mojom::PushUnregistrationStatus status) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  UMA_HISTOGRAM_ENUMERATION("PushMessaging.UnregistrationStatus", status,
-                            PUSH_UNREGISTRATION_STATUS_LAST + 1);
+  UMA_HISTOGRAM_ENUMERATION(
+      "PushMessaging.UnregistrationStatus", status,
+      static_cast<int>(mojom::PushUnregistrationStatus::LAST) + 1);
 }
-void RecordGetRegistrationStatus(PushGetRegistrationStatus status) {
+void RecordGetRegistrationStatus(mojom::PushGetRegistrationStatus status) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO) ||
          BrowserThread::CurrentlyOn(BrowserThread::UI));
-  UMA_HISTOGRAM_ENUMERATION("PushMessaging.GetRegistrationStatus", status,
-                            PUSH_GETREGISTRATION_STATUS_LAST + 1);
+  UMA_HISTOGRAM_ENUMERATION(
+      "PushMessaging.GetRegistrationStatus", status,
+      static_cast<int>(mojom::PushGetRegistrationStatus::LAST) + 1);
+}
+
+const char* PushUnregistrationStatusToString(
+    mojom::PushUnregistrationStatus status) {
+  switch (status) {
+    case mojom::PushUnregistrationStatus::SUCCESS_UNREGISTERED:
+      return "Unregistration successful - from push service";
+
+    case mojom::PushUnregistrationStatus::SUCCESS_WAS_NOT_REGISTERED:
+      return "Unregistration successful - was not registered";
+
+    case mojom::PushUnregistrationStatus::PENDING_NETWORK_ERROR:
+      return "Unregistration pending - a network error occurred, but it will "
+             "be retried until it succeeds";
+
+    case mojom::PushUnregistrationStatus::NO_SERVICE_WORKER:
+      return "Unregistration failed - no Service Worker";
+
+    case mojom::PushUnregistrationStatus::SERVICE_NOT_AVAILABLE:
+      return "Unregistration failed - push service not available";
+
+    case mojom::PushUnregistrationStatus::PENDING_SERVICE_ERROR:
+      return "Unregistration pending - a push service error occurred, but it "
+             "will be retried until it succeeds";
+
+    case mojom::PushUnregistrationStatus::STORAGE_ERROR:
+      return "Unregistration failed - storage error";
+
+    case mojom::PushUnregistrationStatus::NETWORK_ERROR:
+      return "Unregistration failed - could not connect to push server";
+  }
+  NOTREACHED();
+  return "";
 }
 
 void UnregisterCallbackToClosure(const base::Closure& closure,
-                                 PushUnregistrationStatus status) {
+                                 mojom::PushUnregistrationStatus status) {
   DCHECK(!closure.is_null());
   closure.Run();
 }
@@ -158,8 +195,8 @@
   // Callback called on UI thread.
   void GetSubscriptionDidUnsubscribe(
       GetSubscriptionCallback callback,
-      PushGetRegistrationStatus get_status,
-      PushUnregistrationStatus unsubscribe_status);
+      mojom::PushGetRegistrationStatus get_status,
+      mojom::PushUnregistrationStatus unsubscribe_status);
 
   // Public GetPermission methods on UI thread ---------------------------------
 
@@ -203,13 +240,14 @@
                    const std::string& push_registration_id,
                    const std::vector<uint8_t>& p256dh,
                    const std::vector<uint8_t>& auth,
-                   PushRegistrationStatus status);
+                   mojom::PushRegistrationStatus status);
 
   // Private Unregister methods on UI thread -----------------------------------
 
-  void DidUnregisterFromService(UnsubscribeCallback callback,
-                                int64_t service_worker_registration_id,
-                                PushUnregistrationStatus unregistration_status);
+  void DidUnregisterFromService(
+      UnsubscribeCallback callback,
+      int64_t service_worker_registration_id,
+      mojom::PushUnregistrationStatus unregistration_status);
 
   // Outer part of the PushMessagingManager which lives on the IO thread.
   base::WeakPtr<PushMessagingManager> io_parent_;
@@ -304,7 +342,7 @@
   if (!service_worker_registration ||
       !service_worker_registration->active_version()) {
     SendSubscriptionError(std::move(data),
-                          PUSH_REGISTRATION_STATUS_NO_SERVICE_WORKER);
+                          mojom::PushRegistrationStatus::NO_SERVICE_WORKER);
     return;
   }
   data.requesting_origin = service_worker_registration->pattern().GetOrigin();
@@ -332,12 +370,12 @@
         FixSenderInfo(data.options.sender_info, stored_sender_id);
     if (fixed_sender_id.empty()) {
       SendSubscriptionError(std::move(data),
-                            PUSH_REGISTRATION_STATUS_NO_SENDER_ID);
+                            mojom::PushRegistrationStatus::NO_SENDER_ID);
       return;
     }
     if (fixed_sender_id != stored_sender_id) {
       SendSubscriptionError(std::move(data),
-                            PUSH_REGISTRATION_STATUS_SENDER_ID_MISMATCH);
+                            mojom::PushRegistrationStatus::SENDER_ID_MISMATCH);
       return;
     }
 
@@ -387,7 +425,7 @@
         BrowserThread::IO, FROM_HERE,
         base::Bind(&PushMessagingManager::SendSubscriptionSuccess, io_parent_,
                    base::Passed(&data),
-                   PUSH_REGISTRATION_STATUS_SUCCESS_FROM_CACHE,
+                   mojom::PushRegistrationStatus::SUCCESS_FROM_CACHE,
                    push_subscription_id, p256dh, auth));
   } else {
     PushMessagingService* push_service = service();
@@ -399,7 +437,7 @@
           BrowserThread::IO, FROM_HERE,
           base::Bind(&PushMessagingManager::SendSubscriptionError, io_parent_,
                      base::Passed(&data),
-                     PUSH_REGISTRATION_STATUS_RENDERER_SHUTDOWN));
+                     mojom::PushRegistrationStatus::RENDERER_SHUTDOWN));
       return;
     }
 
@@ -410,7 +448,7 @@
 
     // Consider this subscription attempt to have failed. The re-subscribe will
     // be logged to UMA as a separate subscription attempt.
-    RecordRegistrationStatus(PUSH_REGISTRATION_STATUS_STORAGE_CORRUPT);
+    RecordRegistrationStatus(mojom::PushRegistrationStatus::STORAGE_CORRUPT);
 
     int64_t registration_id = data.service_worker_registration_id;
     GURL requesting_origin = data.requesting_origin;
@@ -420,8 +458,8 @@
         std::vector<std::string>() /* push_registration_id_and_sender_id */,
         SERVICE_WORKER_ERROR_NOT_FOUND);
     push_service->Unsubscribe(
-        PUSH_UNREGISTRATION_REASON_SUBSCRIBE_STORAGE_CORRUPT, requesting_origin,
-        registration_id, sender_id,
+        mojom::PushUnregistrationReason::SUBSCRIBE_STORAGE_CORRUPT,
+        requesting_origin, registration_id, sender_id,
         base::Bind(&UnregisterCallbackToClosure,
                    base::Bind(IgnoreResult(&BrowserThread::PostTask),
                               BrowserThread::IO, FROM_HERE, try_again_on_io)));
@@ -435,7 +473,7 @@
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   if (service_worker_status != SERVICE_WORKER_OK) {
     SendSubscriptionError(std::move(data),
-                          PUSH_REGISTRATION_STATUS_NO_SENDER_ID);
+                          mojom::PushRegistrationStatus::NO_SENDER_ID);
     return;
   }
   DCHECK_EQ(1u, stored_sender_id.size());
@@ -445,7 +483,7 @@
       FixSenderInfo(data.options.sender_info, stored_sender_id[0]);
   if (fixed_sender_id.empty()) {
     SendSubscriptionError(std::move(data),
-                          PUSH_REGISTRATION_STATUS_NO_SENDER_ID);
+                          mojom::PushRegistrationStatus::NO_SENDER_ID);
     return;
   }
   data.options.sender_info = fixed_sender_id;
@@ -468,7 +506,7 @@
           BrowserThread::IO, FROM_HERE,
           base::Bind(&PushMessagingManager::SendSubscriptionError, io_parent_,
                      base::Passed(&data),
-                     PUSH_REGISTRATION_STATUS_SERVICE_NOT_AVAILABLE));
+                     mojom::PushRegistrationStatus::SERVICE_NOT_AVAILABLE));
     } else {
       // Prevent websites from detecting incognito mode, by emulating what would
       // have happened if we had a PushMessagingService available.
@@ -476,9 +514,10 @@
         // Throw a permission denied error under the same circumstances.
         BrowserThread::PostTask(
             BrowserThread::IO, FROM_HERE,
-            base::Bind(&PushMessagingManager::SendSubscriptionError, io_parent_,
-                       base::Passed(&data),
-                       PUSH_REGISTRATION_STATUS_INCOGNITO_PERMISSION_DENIED));
+            base::Bind(
+                &PushMessagingManager::SendSubscriptionError, io_parent_,
+                base::Passed(&data),
+                mojom::PushRegistrationStatus::INCOGNITO_PERMISSION_DENIED));
       } else {
         RenderFrameHost* render_frame_host =
             RenderFrameHost::FromID(render_process_id_, data.render_frame_id);
@@ -495,10 +534,10 @@
           if (!browser_context->GetPermissionManager()) {
             BrowserThread::PostTask(
                 BrowserThread::IO, FROM_HERE,
-                base::Bind(
-                    &PushMessagingManager::SendSubscriptionError, io_parent_,
-                    base::Passed(&data),
-                    PUSH_REGISTRATION_STATUS_INCOGNITO_PERMISSION_DENIED));
+                base::Bind(&PushMessagingManager::SendSubscriptionError,
+                           io_parent_, base::Passed(&data),
+                           mojom::PushRegistrationStatus::
+                               INCOGNITO_PERMISSION_DENIED));
 
             return;
           }
@@ -547,7 +586,7 @@
       BrowserThread::IO, FROM_HERE,
       base::Bind(&PushMessagingManager::SendSubscriptionError, io_parent_,
                  base::Passed(&data),
-                 PUSH_REGISTRATION_STATUS_INCOGNITO_PERMISSION_DENIED));
+                 mojom::PushRegistrationStatus::INCOGNITO_PERMISSION_DENIED));
 }
 
 void PushMessagingManager::Core::DidRegister(
@@ -555,9 +594,9 @@
     const std::string& push_registration_id,
     const std::vector<uint8_t>& p256dh,
     const std::vector<uint8_t>& auth,
-    PushRegistrationStatus status) {
+    mojom::PushRegistrationStatus status) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  if (status == PUSH_REGISTRATION_STATUS_SUCCESS_FROM_PUSH_SERVICE) {
+  if (status == mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE) {
     BrowserThread::PostTask(
         BrowserThread::IO, FROM_HERE,
         base::Bind(&PushMessagingManager::PersistRegistrationOnIO, io_parent_,
@@ -596,19 +635,20 @@
     ServiceWorkerStatusCode service_worker_status) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   if (service_worker_status == SERVICE_WORKER_OK) {
-    SendSubscriptionSuccess(std::move(data),
-                            PUSH_REGISTRATION_STATUS_SUCCESS_FROM_PUSH_SERVICE,
-                            push_registration_id, p256dh, auth);
+    SendSubscriptionSuccess(
+        std::move(data),
+        mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE,
+        push_registration_id, p256dh, auth);
   } else {
     // TODO(johnme): Unregister, so PushMessagingServiceImpl can decrease count.
     SendSubscriptionError(std::move(data),
-                          PUSH_REGISTRATION_STATUS_STORAGE_ERROR);
+                          mojom::PushRegistrationStatus::STORAGE_ERROR);
   }
 }
 
 void PushMessagingManager::SendSubscriptionError(
     RegisterData data,
-    PushRegistrationStatus status) {
+    mojom::PushRegistrationStatus status) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   std::move(data.callback)
       .Run(status, base::nullopt /* endpoint */, base::nullopt /* options */,
@@ -618,7 +658,7 @@
 
 void PushMessagingManager::SendSubscriptionSuccess(
     RegisterData data,
-    PushRegistrationStatus status,
+    mojom::PushRegistrationStatus status,
     const std::string& push_subscription_id,
     const std::vector<uint8_t>& p256dh,
     const std::vector<uint8_t>& auth) {
@@ -628,7 +668,7 @@
     // that we have an existing registration. Hence it's ok to throw an error.
     DCHECK(!ui_core_->is_incognito());
     SendSubscriptionError(std::move(data),
-                          PUSH_REGISTRATION_STATUS_SERVICE_NOT_AVAILABLE);
+                          mojom::PushRegistrationStatus::SERVICE_NOT_AVAILABLE);
     return;
   }
 
@@ -652,7 +692,7 @@
           service_worker_registration_id);
   if (!service_worker_registration) {
     DidUnregister(std::move(callback),
-                  PUSH_UNREGISTRATION_STATUS_NO_SERVICE_WORKER);
+                  mojom::PushUnregistrationStatus::NO_SERVICE_WORKER);
     return;
   }
 
@@ -699,12 +739,12 @@
         BrowserThread::IO, FROM_HERE,
         base::Bind(&PushMessagingManager::DidUnregister, io_parent_,
                    base::Passed(&callback),
-                   PUSH_UNREGISTRATION_STATUS_SERVICE_NOT_AVAILABLE));
+                   mojom::PushUnregistrationStatus::SERVICE_NOT_AVAILABLE));
     return;
   }
 
   push_service->Unsubscribe(
-      PUSH_UNREGISTRATION_REASON_JAVASCRIPT_API, requesting_origin,
+      mojom::PushUnregistrationReason::JAVASCRIPT_API, requesting_origin,
       service_worker_registration_id, sender_id,
       base::Bind(&Core::DidUnregisterFromService,
                  weak_factory_ui_to_ui_.GetWeakPtr(), base::Passed(&callback),
@@ -714,7 +754,7 @@
 void PushMessagingManager::Core::DidUnregisterFromService(
     UnsubscribeCallback callback,
     int64_t service_worker_registration_id,
-    PushUnregistrationStatus unregistration_status) {
+    mojom::PushUnregistrationStatus unregistration_status) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   BrowserThread::PostTask(
@@ -725,30 +765,30 @@
 
 void PushMessagingManager::DidUnregister(
     UnsubscribeCallback callback,
-    PushUnregistrationStatus unregistration_status) {
+    mojom::PushUnregistrationStatus unregistration_status) {
   // Only called from IO thread, but would be safe to call from UI thread.
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   switch (unregistration_status) {
-    case PUSH_UNREGISTRATION_STATUS_SUCCESS_UNREGISTERED:
-    case PUSH_UNREGISTRATION_STATUS_PENDING_NETWORK_ERROR:
-    case PUSH_UNREGISTRATION_STATUS_PENDING_SERVICE_ERROR:
+    case mojom::PushUnregistrationStatus::SUCCESS_UNREGISTERED:
+    case mojom::PushUnregistrationStatus::PENDING_NETWORK_ERROR:
+    case mojom::PushUnregistrationStatus::PENDING_SERVICE_ERROR:
       std::move(callback).Run(blink::WebPushError::kErrorTypeNone,
                               true /* did_unsubscribe */,
                               base::nullopt /* error_message */);
       break;
-    case PUSH_UNREGISTRATION_STATUS_SUCCESS_WAS_NOT_REGISTERED:
+    case mojom::PushUnregistrationStatus::SUCCESS_WAS_NOT_REGISTERED:
       std::move(callback).Run(blink::WebPushError::kErrorTypeNone,
                               false /* did_unsubscribe */,
                               base::nullopt /* error_message */);
       break;
-    case PUSH_UNREGISTRATION_STATUS_NO_SERVICE_WORKER:
-    case PUSH_UNREGISTRATION_STATUS_SERVICE_NOT_AVAILABLE:
-    case PUSH_UNREGISTRATION_STATUS_STORAGE_ERROR:
+    case mojom::PushUnregistrationStatus::NO_SERVICE_WORKER:
+    case mojom::PushUnregistrationStatus::SERVICE_NOT_AVAILABLE:
+    case mojom::PushUnregistrationStatus::STORAGE_ERROR:
       std::move(callback).Run(blink::WebPushError::kErrorTypeAbort, false,
                               std::string(PushUnregistrationStatusToString(
                                   unregistration_status)) /* error_message */);
       break;
-    case PUSH_UNREGISTRATION_STATUS_NETWORK_ERROR:
+    case mojom::PushUnregistrationStatus::NETWORK_ERROR:
       NOTREACHED();
       break;
   }
@@ -778,8 +818,8 @@
     const std::vector<std::string>& push_subscription_id_and_sender_info,
     ServiceWorkerStatusCode service_worker_status) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  PushGetRegistrationStatus get_status =
-      PUSH_GETREGISTRATION_STATUS_STORAGE_ERROR;
+  mojom::PushGetRegistrationStatus get_status =
+      mojom::PushGetRegistrationStatus::STORAGE_ERROR;
   switch (service_worker_status) {
     case SERVICE_WORKER_OK: {
       DCHECK_EQ(2u, push_subscription_id_and_sender_info.size());
@@ -791,8 +831,9 @@
         // Return not found in incognito mode, so websites can't detect it.
         get_status =
             ui_core_->is_incognito()
-                ? PUSH_GETREGISTRATION_STATUS_INCOGNITO_REGISTRATION_NOT_FOUND
-                : PUSH_GETREGISTRATION_STATUS_SERVICE_NOT_AVAILABLE;
+                ? mojom::PushGetRegistrationStatus::
+                      INCOGNITO_REGISTRATION_NOT_FOUND
+                : mojom::PushGetRegistrationStatus::SERVICE_NOT_AVAILABLE;
         break;
       }
 
@@ -800,7 +841,7 @@
           service_worker_context_->GetLiveRegistration(
               service_worker_registration_id);
       if (!registration) {
-        get_status = PUSH_GETREGISTRATION_STATUS_NO_LIVE_SERVICE_WORKER;
+        get_status = mojom::PushGetRegistrationStatus::NO_LIVE_SERVICE_WORKER;
         break;
       }
 
@@ -824,11 +865,11 @@
       return;
     }
     case SERVICE_WORKER_ERROR_NOT_FOUND: {
-      get_status = PUSH_GETREGISTRATION_STATUS_REGISTRATION_NOT_FOUND;
+      get_status = mojom::PushGetRegistrationStatus::REGISTRATION_NOT_FOUND;
       break;
     }
     case SERVICE_WORKER_ERROR_FAILED: {
-      get_status = PUSH_GETREGISTRATION_STATUS_STORAGE_ERROR;
+      get_status = mojom::PushGetRegistrationStatus::STORAGE_ERROR;
       break;
     }
     case SERVICE_WORKER_ERROR_ABORT:
@@ -850,7 +891,7 @@
     case SERVICE_WORKER_ERROR_MAX_VALUE: {
       NOTREACHED() << "Got unexpected error code: " << service_worker_status
                    << " " << ServiceWorkerStatusToString(service_worker_status);
-      get_status = PUSH_GETREGISTRATION_STATUS_STORAGE_ERROR;
+      get_status = mojom::PushGetRegistrationStatus::STORAGE_ERROR;
       break;
     }
   }
@@ -879,7 +920,8 @@
     options.user_visible_only = true;
     options.sender_info = sender_info;
 
-    PushGetRegistrationStatus status = PUSH_GETREGISTRATION_STATUS_SUCCESS;
+    mojom::PushGetRegistrationStatus status =
+        mojom::PushGetRegistrationStatus::SUCCESS;
 
     BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
                             base::BindOnce(std::move(callback), status,
@@ -895,7 +937,7 @@
       BrowserThread::PostTask(
           BrowserThread::IO, FROM_HERE,
           base::BindOnce(std::move(callback),
-                         PUSH_GETREGISTRATION_STATUS_RENDERER_SHUTDOWN,
+                         mojom::PushGetRegistrationStatus::RENDERER_SHUTDOWN,
                          base::nullopt /* endpoint */,
                          base::nullopt /* options */,
                          base::nullopt /* p256dh */, base::nullopt /* auth */));
@@ -906,12 +948,12 @@
     // database, it did not have matching counterparts in the
     // PushMessagingAppIdentifier map and/or GCM Store. Unsubscribe to fix this
     // inconsistency.
-    PushGetRegistrationStatus status =
-        PUSH_GETREGISTRATION_STATUS_STORAGE_CORRUPT;
+    mojom::PushGetRegistrationStatus status =
+        mojom::PushGetRegistrationStatus::STORAGE_CORRUPT;
 
     push_service->Unsubscribe(
-        PUSH_UNREGISTRATION_REASON_GET_SUBSCRIPTION_STORAGE_CORRUPT, origin,
-        service_worker_registration_id, sender_info,
+        mojom::PushUnregistrationReason::GET_SUBSCRIPTION_STORAGE_CORRUPT,
+        origin, service_worker_registration_id, sender_info,
         base::Bind(&Core::GetSubscriptionDidUnsubscribe,
                    weak_factory_ui_to_ui_.GetWeakPtr(), base::Passed(&callback),
                    status));
@@ -922,8 +964,8 @@
 
 void PushMessagingManager::Core::GetSubscriptionDidUnsubscribe(
     GetSubscriptionCallback callback,
-    PushGetRegistrationStatus get_status,
-    PushUnregistrationStatus unsubscribe_status) {
+    mojom::PushGetRegistrationStatus get_status,
+    mojom::PushUnregistrationStatus unsubscribe_status) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   BrowserThread::PostTask(
       BrowserThread::IO, FROM_HERE,
diff --git a/content/browser/push_messaging/push_messaging_manager.h b/content/browser/push_messaging/push_messaging_manager.h
index 495f9bce..01d0899 100644
--- a/content/browser/push_messaging/push_messaging_manager.h
+++ b/content/browser/push_messaging/push_messaging_manager.h
@@ -16,7 +16,6 @@
 #include "content/common/push_messaging.mojom.h"
 #include "content/common/service_worker/service_worker_status_code.h"
 #include "content/public/browser/browser_thread.h"
-#include "content/public/common/push_messaging_status.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
 #include "url/gurl.h"
 
@@ -26,6 +25,11 @@
 
 namespace content {
 
+namespace mojom {
+enum class PushRegistrationStatus;
+enum class PushUnregistrationStatus;
+}  // namespace mojom
+
 class PushMessagingService;
 class ServiceWorkerContextWrapper;
 struct PushSubscriptionOptions;
@@ -89,10 +93,11 @@
       ServiceWorkerStatusCode service_worker_status);
 
   // Called both from IO thread, and via PostTask from UI thread.
-  void SendSubscriptionError(RegisterData data, PushRegistrationStatus status);
+  void SendSubscriptionError(RegisterData data,
+                             mojom::PushRegistrationStatus status);
   // Called both from IO thread, and via PostTask from UI thread.
   void SendSubscriptionSuccess(RegisterData data,
-                               PushRegistrationStatus status,
+                               mojom::PushRegistrationStatus status,
                                const std::string& push_subscription_id,
                                const std::vector<uint8_t>& p256dh,
                                const std::vector<uint8_t>& auth);
@@ -106,7 +111,7 @@
 
   // Called both from IO thread, and via PostTask from UI thread.
   void DidUnregister(UnsubscribeCallback callback,
-                     PushUnregistrationStatus unregistration_status);
+                     mojom::PushUnregistrationStatus unregistration_status);
 
   void DidGetSubscription(
       GetSubscriptionCallback callback,
diff --git a/content/browser/push_messaging/push_messaging_router.cc b/content/browser/push_messaging/push_messaging_router.cc
index 995b15d9..7f87b28 100644
--- a/content/browser/push_messaging/push_messaging_router.cc
+++ b/content/browser/push_messaging/push_messaging_router.cc
@@ -17,6 +17,7 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/common/push_event_payload.h"
+#include "content/public/common/push_messaging_status.mojom.h"
 
 namespace content {
 
@@ -28,7 +29,7 @@
 
 void RunDeliverCallback(
     const PushMessagingRouter::DeliverMessageCallback& deliver_message_callback,
-    PushDeliveryStatus delivery_status) {
+    mojom::PushDeliveryStatus delivery_status) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   BrowserThread::PostTask(
       BrowserThread::UI, FROM_HERE,
@@ -85,12 +86,12 @@
                             SERVICE_WORKER_ERROR_MAX_VALUE);
   if (service_worker_status == SERVICE_WORKER_ERROR_NOT_FOUND) {
     RunDeliverCallback(deliver_message_callback,
-                       PUSH_DELIVERY_STATUS_NO_SERVICE_WORKER);
+                       mojom::PushDeliveryStatus::NO_SERVICE_WORKER);
     return;
   }
   if (service_worker_status != SERVICE_WORKER_OK) {
     RunDeliverCallback(deliver_message_callback,
-                       PUSH_DELIVERY_STATUS_SERVICE_WORKER_ERROR);
+                       mojom::PushDeliveryStatus::SERVICE_WORKER_ERROR);
     return;
   }
 
@@ -137,17 +138,17 @@
   UMA_HISTOGRAM_ENUMERATION("PushMessaging.DeliveryStatus.ServiceWorkerEvent",
                             service_worker_status,
                             SERVICE_WORKER_ERROR_MAX_VALUE);
-  PushDeliveryStatus delivery_status =
-      PUSH_DELIVERY_STATUS_SERVICE_WORKER_ERROR;
+  mojom::PushDeliveryStatus delivery_status =
+      mojom::PushDeliveryStatus::SERVICE_WORKER_ERROR;
   switch (service_worker_status) {
     case SERVICE_WORKER_OK:
-      delivery_status = PUSH_DELIVERY_STATUS_SUCCESS;
+      delivery_status = mojom::PushDeliveryStatus::SUCCESS;
       break;
     case SERVICE_WORKER_ERROR_EVENT_WAITUNTIL_REJECTED:
-      delivery_status = PUSH_DELIVERY_STATUS_EVENT_WAITUNTIL_REJECTED;
+      delivery_status = mojom::PushDeliveryStatus::EVENT_WAITUNTIL_REJECTED;
       break;
     case SERVICE_WORKER_ERROR_TIMEOUT:
-      delivery_status = PUSH_DELIVERY_STATUS_TIMEOUT;
+      delivery_status = mojom::PushDeliveryStatus::TIMEOUT;
       break;
     case SERVICE_WORKER_ERROR_FAILED:
     case SERVICE_WORKER_ERROR_ABORT:
@@ -159,7 +160,7 @@
     case SERVICE_WORKER_ERROR_DISK_CACHE:
     case SERVICE_WORKER_ERROR_REDUNDANT:
     case SERVICE_WORKER_ERROR_DISALLOWED:
-      delivery_status = PUSH_DELIVERY_STATUS_SERVICE_WORKER_ERROR;
+      delivery_status = mojom::PushDeliveryStatus::SERVICE_WORKER_ERROR;
       break;
     case SERVICE_WORKER_ERROR_EXISTS:
     case SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED:
@@ -170,7 +171,7 @@
     case SERVICE_WORKER_ERROR_MAX_VALUE:
       NOTREACHED() << "Got unexpected error code: " << service_worker_status
                    << " " << ServiceWorkerStatusToString(service_worker_status);
-      delivery_status = PUSH_DELIVERY_STATUS_SERVICE_WORKER_ERROR;
+      delivery_status = mojom::PushDeliveryStatus::SERVICE_WORKER_ERROR;
       break;
   }
   RunDeliverCallback(deliver_message_callback, delivery_status);
diff --git a/content/browser/push_messaging/push_messaging_router.h b/content/browser/push_messaging/push_messaging_router.h
index d57366f..9fae535 100644
--- a/content/browser/push_messaging/push_messaging_router.h
+++ b/content/browser/push_messaging/push_messaging_router.h
@@ -12,12 +12,15 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "content/common/service_worker/service_worker_status_code.h"
-#include "content/public/common/push_messaging_status.h"
 #include "third_party/WebKit/public/platform/modules/serviceworker/WebServiceWorkerEventResult.h"
 #include "url/gurl.h"
 
 namespace content {
 
+namespace mojom {
+enum class PushDeliveryStatus;
+}
+
 class BrowserContext;
 struct PushEventPayload;
 class ServiceWorkerContextWrapper;
@@ -26,7 +29,8 @@
 
 class PushMessagingRouter {
  public:
-  typedef base::Callback<void(PushDeliveryStatus)> DeliverMessageCallback;
+  using DeliverMessageCallback =
+      base::Callback<void(mojom::PushDeliveryStatus)>;
 
   // Delivers a push message with |data| to the Service Worker identified by
   // |origin| and |service_worker_registration_id|. Must be called on the UI
diff --git a/content/browser/service_manager/service_manager_context.cc b/content/browser/service_manager/service_manager_context.cc
index 91b296e..87b3229 100644
--- a/content/browser/service_manager/service_manager_context.cc
+++ b/content/browser/service_manager/service_manager_context.cc
@@ -77,15 +77,15 @@
 void StartServiceInUtilityProcess(
     const std::string& service_name,
     const base::string16& process_name,
-    bool use_sandbox,
+    SandboxType sandbox_type,
     service_manager::mojom::ServiceRequest request) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   UtilityProcessHost* process_host =
       UtilityProcessHost::Create(nullptr, nullptr);
   process_host->SetName(process_name);
-  if (!use_sandbox)
-    process_host->DisableSandbox();
+  process_host->SetSandboxType(sandbox_type);
   process_host->Start();
+
   service_manager::mojom::ServiceFactoryPtr service_factory;
   BindInterface(process_host, mojo::MakeRequest(&service_factory));
   service_factory->CreateService(std::move(request), service_name);
@@ -337,49 +337,37 @@
   // GetConnectorForIOThread().
   g_io_thread_connector.Get() = browser_connection->GetConnector()->Clone();
 
-  ContentBrowserClient::OutOfProcessServiceMap sandboxed_services;
-  GetContentClient()
-      ->browser()
-      ->RegisterOutOfProcessServices(&sandboxed_services);
-  sandboxed_services.insert(
-      std::make_pair(data_decoder::mojom::kServiceName,
-                     base::ASCIIToUTF16("Data Decoder Service")));
-  for (const auto& service : sandboxed_services) {
-    packaged_services_connection_->AddServiceRequestHandler(
-        service.first, base::Bind(&StartServiceInUtilityProcess, service.first,
-                                  service.second, true /* use_sandbox */));
-  }
+  ContentBrowserClient::OutOfProcessServiceMap out_of_process_services;
+  GetContentClient()->browser()->RegisterOutOfProcessServices(
+      &out_of_process_services);
 
-  ContentBrowserClient::OutOfProcessServiceMap unsandboxed_services;
-  GetContentClient()
-      ->browser()
-      ->RegisterUnsandboxedOutOfProcessServices(&unsandboxed_services);
+  out_of_process_services[data_decoder::mojom::kServiceName] = {
+      base::ASCIIToUTF16("Data Decoder Service"), SANDBOX_TYPE_UTILITY};
 
   bool network_service_enabled =
       base::FeatureList::IsEnabled(features::kNetworkService);
   if (network_service_enabled) {
-    unsandboxed_services.insert(
-        std::make_pair(content::mojom::kNetworkServiceName,
-                       base::ASCIIToUTF16("Network Service")));
+    out_of_process_services[content::mojom::kNetworkServiceName] = {
+        base::ASCIIToUTF16("Network Service"), SANDBOX_TYPE_NETWORK};
   }
+
   if (base::FeatureList::IsEnabled(video_capture::kMojoVideoCapture)) {
-    unsandboxed_services.insert(
-        std::make_pair(video_capture::mojom::kServiceName,
-                       base::ASCIIToUTF16("Video Capture Service")));
+    out_of_process_services[video_capture::mojom::kServiceName] = {
+        base::ASCIIToUTF16("Video Capture Service"), SANDBOX_TYPE_NO_SANDBOX};
   }
 
 #if BUILDFLAG(ENABLE_MOJO_MEDIA_IN_UTILITY_PROCESS)
   // TODO(xhwang): This is only used for test/experiment for now so it's okay
   // to run it in an unsandboxed utility process. Fix CDM loading so that we can
   // run it in the sandboxed utility process. See http://crbug.com/510604
-  unsandboxed_services.insert(std::make_pair(
-      media::mojom::kMediaServiceName, base::ASCIIToUTF16("Media Service")));
+  out_of_process_services[media::mojom::kMediaServiceName] = {
+      base::ASCIIToUTF16("Media Service"), SANDBOX_TYPE_NO_SANDBOX};
 #endif
 
-  for (const auto& service : unsandboxed_services) {
+  for (const auto& service : out_of_process_services) {
     packaged_services_connection_->AddServiceRequestHandler(
         service.first, base::Bind(&StartServiceInUtilityProcess, service.first,
-                                  service.second, false /* use_sandbox */));
+                                  service.second.first, service.second.second));
   }
 
 #if BUILDFLAG(ENABLE_MOJO_MEDIA_IN_GPU_PROCESS)
diff --git a/content/browser/speech/speech_recognition_manager_impl.cc b/content/browser/speech/speech_recognition_manager_impl.cc
index 6fb729c..740885a 100644
--- a/content/browser/speech/speech_recognition_manager_impl.cc
+++ b/content/browser/speech/speech_recognition_manager_impl.cc
@@ -156,9 +156,9 @@
   if (delegate_) {
     delegate_->CheckRecognitionIsAllowed(
         session_id,
-        base::Bind(&SpeechRecognitionManagerImpl::RecognitionAllowedCallback,
-                   weak_factory_.GetWeakPtr(),
-                   session_id));
+        base::BindOnce(
+            &SpeechRecognitionManagerImpl::RecognitionAllowedCallback,
+            weak_factory_.GetWeakPtr(), session_id));
   }
 }
 
@@ -274,7 +274,7 @@
   SessionsTable::iterator iter = sessions_.find(session_id);
   if (iter->second->ui) {
     // Notify the UI that the devices are being used.
-    iter->second->ui->OnStarted(base::Closure(),
+    iter->second->ui->OnStarted(base::OnceClosure(),
                                 MediaStreamUIProxy::WindowIdCallback());
   }
 
diff --git a/content/browser/utility_process_host_impl.cc b/content/browser/utility_process_host_impl.cc
index f6c4e30..7a9069e 100644
--- a/content/browser/utility_process_host_impl.cc
+++ b/content/browser/utility_process_host_impl.cc
@@ -168,8 +168,11 @@
   exposed_dir_ = dir;
 }
 
-void UtilityProcessHostImpl::DisableSandbox() {
-  no_sandbox_ = true;
+void UtilityProcessHostImpl::SetSandboxType(SandboxType sandbox_type) {
+  DCHECK(sandbox_type != SANDBOX_TYPE_INVALID);
+
+  // TODO(tsepez): Store sandbox type itself.
+  no_sandbox_ = IsUnsandboxedSandboxType(sandbox_type);
 }
 
 #if defined(OS_WIN)
diff --git a/content/browser/utility_process_host_impl.h b/content/browser/utility_process_host_impl.h
index 517fb3c..0bf3880 100644
--- a/content/browser/utility_process_host_impl.h
+++ b/content/browser/utility_process_host_impl.h
@@ -44,7 +44,7 @@
   base::WeakPtr<UtilityProcessHost> AsWeakPtr() override;
   bool Send(IPC::Message* message) override;
   void SetExposedDir(const base::FilePath& dir) override;
-  void DisableSandbox() override;
+  void SetSandboxType(SandboxType sandbox_type) override;
 #if defined(OS_WIN)
   void ElevatePrivileges() override;
 #endif
diff --git a/content/child/push_messaging/push_provider.cc b/content/child/push_messaging/push_provider.cc
index 54761ccb..f571543c 100644
--- a/content/child/push_messaging/push_provider.cc
+++ b/content/child/push_messaging/push_provider.cc
@@ -14,6 +14,7 @@
 #include "content/child/child_thread_impl.h"
 #include "content/child/service_worker/web_service_worker_registration_impl.h"
 #include "content/public/common/child_process_host.h"
+#include "content/public/common/push_messaging_status.mojom.h"
 #include "content/public/common/push_subscription_options.h"
 #include "content/public/common/service_names.mojom.h"
 #include "services/service_manager/public/cpp/connector.h"
@@ -24,8 +25,65 @@
 namespace content {
 namespace {
 
-int CurrentWorkerId() {
-  return WorkerThread::GetCurrentId();
+const char* PushRegistrationStatusToString(
+    mojom::PushRegistrationStatus status) {
+  switch (status) {
+    case mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE:
+      return "Registration successful - from push service";
+
+    case mojom::PushRegistrationStatus::NO_SERVICE_WORKER:
+      return "Registration failed - no Service Worker";
+
+    case mojom::PushRegistrationStatus::SERVICE_NOT_AVAILABLE:
+      return "Registration failed - push service not available";
+
+    case mojom::PushRegistrationStatus::LIMIT_REACHED:
+      return "Registration failed - registration limit has been reached";
+
+    case mojom::PushRegistrationStatus::PERMISSION_DENIED:
+      return "Registration failed - permission denied";
+
+    case mojom::PushRegistrationStatus::SERVICE_ERROR:
+      return "Registration failed - push service error";
+
+    case mojom::PushRegistrationStatus::NO_SENDER_ID:
+      return "Registration failed - missing applicationServerKey, and "
+             "gcm_sender_id not found in manifest";
+
+    case mojom::PushRegistrationStatus::STORAGE_ERROR:
+      return "Registration failed - storage error";
+
+    case mojom::PushRegistrationStatus::SUCCESS_FROM_CACHE:
+      return "Registration successful - from cache";
+
+    case mojom::PushRegistrationStatus::NETWORK_ERROR:
+      return "Registration failed - could not connect to push server";
+
+    case mojom::PushRegistrationStatus::INCOGNITO_PERMISSION_DENIED:
+      // We split this out for UMA, but it must be indistinguishable to JS.
+      return PushRegistrationStatusToString(
+          mojom::PushRegistrationStatus::PERMISSION_DENIED);
+
+    case mojom::PushRegistrationStatus::PUBLIC_KEY_UNAVAILABLE:
+      return "Registration failed - could not retrieve the public key";
+
+    case mojom::PushRegistrationStatus::MANIFEST_EMPTY_OR_MISSING:
+      return "Registration failed - missing applicationServerKey, and manifest "
+             "empty or missing";
+
+    case mojom::PushRegistrationStatus::SENDER_ID_MISMATCH:
+      return "Registration failed - A subscription with a different "
+             "applicationServerKey (or gcm_sender_id) already exists; to "
+             "change the applicationServerKey, unsubscribe then resubscribe.";
+
+    case mojom::PushRegistrationStatus::STORAGE_CORRUPT:
+      return "Registration failed - storage corrupt";
+
+    case mojom::PushRegistrationStatus::RENDERER_SHUTDOWN:
+      return "Registration failed - renderer shutdown";
+  }
+  NOTREACHED();
+  return "";
 }
 
 // Returns the id of the given |service_worker_registration|, which
@@ -40,30 +98,30 @@
 }  // namespace
 
 blink::WebPushError PushRegistrationStatusToWebPushError(
-    PushRegistrationStatus status) {
+    mojom::PushRegistrationStatus status) {
   blink::WebPushError::ErrorType error_type =
       blink::WebPushError::kErrorTypeAbort;
   switch (status) {
-    case PUSH_REGISTRATION_STATUS_PERMISSION_DENIED:
+    case mojom::PushRegistrationStatus::PERMISSION_DENIED:
       error_type = blink::WebPushError::kErrorTypeNotAllowed;
       break;
-    case PUSH_REGISTRATION_STATUS_SENDER_ID_MISMATCH:
+    case mojom::PushRegistrationStatus::SENDER_ID_MISMATCH:
       error_type = blink::WebPushError::kErrorTypeInvalidState;
       break;
-    case PUSH_REGISTRATION_STATUS_SUCCESS_FROM_PUSH_SERVICE:
-    case PUSH_REGISTRATION_STATUS_NO_SERVICE_WORKER:
-    case PUSH_REGISTRATION_STATUS_SERVICE_NOT_AVAILABLE:
-    case PUSH_REGISTRATION_STATUS_LIMIT_REACHED:
-    case PUSH_REGISTRATION_STATUS_SERVICE_ERROR:
-    case PUSH_REGISTRATION_STATUS_NO_SENDER_ID:
-    case PUSH_REGISTRATION_STATUS_STORAGE_ERROR:
-    case PUSH_REGISTRATION_STATUS_SUCCESS_FROM_CACHE:
-    case PUSH_REGISTRATION_STATUS_NETWORK_ERROR:
-    case PUSH_REGISTRATION_STATUS_INCOGNITO_PERMISSION_DENIED:
-    case PUSH_REGISTRATION_STATUS_PUBLIC_KEY_UNAVAILABLE:
-    case PUSH_REGISTRATION_STATUS_MANIFEST_EMPTY_OR_MISSING:
-    case PUSH_REGISTRATION_STATUS_STORAGE_CORRUPT:
-    case PUSH_REGISTRATION_STATUS_RENDERER_SHUTDOWN:
+    case mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE:
+    case mojom::PushRegistrationStatus::NO_SERVICE_WORKER:
+    case mojom::PushRegistrationStatus::SERVICE_NOT_AVAILABLE:
+    case mojom::PushRegistrationStatus::LIMIT_REACHED:
+    case mojom::PushRegistrationStatus::SERVICE_ERROR:
+    case mojom::PushRegistrationStatus::NO_SENDER_ID:
+    case mojom::PushRegistrationStatus::STORAGE_ERROR:
+    case mojom::PushRegistrationStatus::SUCCESS_FROM_CACHE:
+    case mojom::PushRegistrationStatus::NETWORK_ERROR:
+    case mojom::PushRegistrationStatus::INCOGNITO_PERMISSION_DENIED:
+    case mojom::PushRegistrationStatus::PUBLIC_KEY_UNAVAILABLE:
+    case mojom::PushRegistrationStatus::MANIFEST_EMPTY_OR_MISSING:
+    case mojom::PushRegistrationStatus::STORAGE_CORRUPT:
+    case mojom::PushRegistrationStatus::RENDERER_SHUTDOWN:
       error_type = blink::WebPushError::kErrorTypeAbort;
       break;
   }
@@ -101,7 +159,7 @@
     return g_push_provider_tls.Pointer()->Get();
 
   PushProvider* provider = new PushProvider(main_thread_task_runner);
-  if (CurrentWorkerId())
+  if (WorkerThread::GetCurrentId())
     WorkerThread::AddObserver(provider);
   return provider;
 }
@@ -146,15 +204,15 @@
 
 void PushProvider::DidSubscribe(
     std::unique_ptr<blink::WebPushSubscriptionCallbacks> callbacks,
-    content::PushRegistrationStatus status,
+    mojom::PushRegistrationStatus status,
     const base::Optional<GURL>& endpoint,
-    const base::Optional<content::PushSubscriptionOptions>& options,
+    const base::Optional<PushSubscriptionOptions>& options,
     const base::Optional<std::vector<uint8_t>>& p256dh,
     const base::Optional<std::vector<uint8_t>>& auth) {
   DCHECK(callbacks);
 
-  if (status == PUSH_REGISTRATION_STATUS_SUCCESS_FROM_PUSH_SERVICE ||
-      status == PUSH_REGISTRATION_STATUS_SUCCESS_FROM_CACHE) {
+  if (status == mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE ||
+      status == mojom::PushRegistrationStatus::SUCCESS_FROM_CACHE) {
     DCHECK(endpoint);
     DCHECK(options);
     DCHECK(p256dh);
@@ -221,14 +279,14 @@
 
 void PushProvider::DidGetSubscription(
     std::unique_ptr<blink::WebPushSubscriptionCallbacks> callbacks,
-    content::PushGetRegistrationStatus status,
+    mojom::PushGetRegistrationStatus status,
     const base::Optional<GURL>& endpoint,
-    const base::Optional<content::PushSubscriptionOptions>& options,
+    const base::Optional<PushSubscriptionOptions>& options,
     const base::Optional<std::vector<uint8_t>>& p256dh,
     const base::Optional<std::vector<uint8_t>>& auth) {
   DCHECK(callbacks);
 
-  if (status == PUSH_GETREGISTRATION_STATUS_SUCCESS) {
+  if (status == mojom::PushGetRegistrationStatus::SUCCESS) {
     DCHECK(endpoint);
     DCHECK(options);
     DCHECK(p256dh);
diff --git a/content/child/push_messaging/push_provider.h b/content/child/push_messaging/push_provider.h
index 6264fc18..07d3bda 100644
--- a/content/child/push_messaging/push_provider.h
+++ b/content/child/push_messaging/push_provider.h
@@ -15,7 +15,6 @@
 #include "base/memory/ref_counted.h"
 #include "content/common/push_messaging.mojom.h"
 #include "content/public/child/worker_thread.h"
-#include "content/public/common/push_messaging_status.h"
 #include "third_party/WebKit/public/platform/modules/push_messaging/WebPushError.h"
 #include "third_party/WebKit/public/platform/modules/push_messaging/WebPushProvider.h"
 
@@ -27,10 +26,15 @@
 
 namespace content {
 
+namespace mojom {
+enum class PushGetRegistrationStatus;
+enum class PushRegistrationStatus;
+}  // namespace mojom
+
 struct PushSubscriptionOptions;
 
 blink::WebPushError PushRegistrationStatusToWebPushError(
-    PushRegistrationStatus status);
+    mojom::PushRegistrationStatus status);
 
 class PushProvider : public blink::WebPushProvider,
                      public WorkerThread::Observer {
@@ -70,9 +74,9 @@
 
   void DidSubscribe(
       std::unique_ptr<blink::WebPushSubscriptionCallbacks> callbacks,
-      content::PushRegistrationStatus status,
+      mojom::PushRegistrationStatus status,
       const base::Optional<GURL>& endpoint,
-      const base::Optional<content::PushSubscriptionOptions>& options,
+      const base::Optional<PushSubscriptionOptions>& options,
       const base::Optional<std::vector<uint8_t>>& p256dh,
       const base::Optional<std::vector<uint8_t>>& auth);
 
@@ -84,9 +88,9 @@
 
   void DidGetSubscription(
       std::unique_ptr<blink::WebPushSubscriptionCallbacks> callbacks,
-      content::PushGetRegistrationStatus status,
+      mojom::PushGetRegistrationStatus status,
       const base::Optional<GURL>& endpoint,
-      const base::Optional<content::PushSubscriptionOptions>& options,
+      const base::Optional<PushSubscriptionOptions>& options,
       const base::Optional<std::vector<uint8_t>>& p256dh,
       const base::Optional<std::vector<uint8_t>>& auth);
 
diff --git a/content/common/push_messaging.mojom b/content/common/push_messaging.mojom
index 94b0ea1..7b0b64c 100644
--- a/content/common/push_messaging.mojom
+++ b/content/common/push_messaging.mojom
@@ -4,6 +4,7 @@
 
 module content.mojom;
 
+import "content/public/common/push_messaging_status.mojom";
 import "url/mojo/url.mojom";
 
 // TODO(heke): The type-mapping struct and enums are duplicately defined. Need
@@ -14,75 +15,6 @@
   string sender_info;
 };
 
-// Push registration success/error codes for internal use & reporting in UMA.
-// Enum values can be added, but must never be renumbered or deleted and reused.
-enum PushRegistrationStatus {
-  // New successful registration (there was not yet a registration cached in
-  // Service Worker storage, so the browser successfully registered with the
-  // push service. This is likely to be a new push registration, though it's
-  // possible that the push service had its own cache (for example if Chrome's
-  // app data was cleared, we might have forgotten about a registration that the
-  // push service still stores).
-  SUCCESS_FROM_PUSH_SERVICE = 0,
-
-  // Registration failed because there is no Service Worker.
-  NO_SERVICE_WORKER = 1,
-
-  // Registration failed because the push service is not available.
-  SERVICE_NOT_AVAILABLE = 2,
-
-  // Registration failed because the maximum number of registratons has been
-  // reached.
-  LIMIT_REACHED = 3,
-
-  // Registration failed because permission was denied.
-  PERMISSION_DENIED = 4,
-
-  // Registration failed in the push service implemented by the embedder.
-  SERVICE_ERROR = 5,
-
-  // Registration failed because no sender id was provided by the page.
-  NO_SENDER_ID = 6,
-
-  // Registration succeeded, but we failed to persist it.
-  STORAGE_ERROR = 7,
-
-  // A successful registration was already cached in Service Worker storage.
-  SUCCESS_FROM_CACHE = 8,
-
-  // Registration failed due to a network error.
-  NETWORK_ERROR = 9,
-
-  // Registration failed because the push service is not available in incognito,
-  // but we tell JS that permission was denied to not reveal incognito.
-  INCOGNITO_PERMISSION_DENIED = 10,
-
-  // Registration failed because the public key could not be retrieved.
-  PUBLIC_KEY_UNAVAILABLE = 11,
-
-  // Registration failed because the manifest could not be retrieved or was
-  // empty.
-  MANIFEST_EMPTY_OR_MISSING = 12,
-
-  // Registration failed because a subscription with a different sender id
-  // already exists.
-  SENDER_ID_MISMATCH = 13,
-
-  // Registration failed because storage was corrupt. It will be retried
-  // automatically after unsubscribing to fix the corruption.
-  STORAGE_CORRUPT = 14,
-
-  // Registration failed because the renderer was shut down.
-  RENDERER_SHUTDOWN = 15,
-
-  // NOTE: Do not renumber these as that would confuse interpretation of
-  // previously logged data. When making changes, also update the enum list
-  // in tools/metrics/histograms/histograms.xml to keep it in sync, and
-  // update LAST below.
-
-  LAST = RENDERER_SHUTDOWN
-};
-
 enum PushErrorType {
   ABORT = 0,
   NETWORK = 1,
@@ -94,45 +26,6 @@
   LAST = INVALID_STATE
 };
 
-// Push getregistration success/error codes for internal use & reporting in UMA.
-// Enum values can be added, but must never be renumbered or deleted and reused.
-enum PushGetRegistrationStatus {
-  // Getting the registration was successful.
-  SUCCESS = 0,
-
-  // Getting the registration failed because the push service is not available.
-  SERVICE_NOT_AVAILABLE = 1,
-
-  // Getting the registration failed because we failed to read from storage.
-  STORAGE_ERROR = 2,
-
-  // Getting the registration failed because there is no push registration.
-  REGISTRATION_NOT_FOUND = 3,
-
-  // Getting the registration failed because the push service isn't available in
-  // incognito, but we tell JS registration not found to not reveal incognito.
-  INCOGNITO_REGISTRATION_NOT_FOUND = 4,
-
-  // Getting the registration failed because public key could not be retrieved.
-  // PUBLIC_KEY_UNAVAILABLE = 5,
-
-  // Getting the registration failed because storage was corrupt.
-  STORAGE_CORRUPT = 6,
-
-  // Getting the registration failed because the renderer was shut down.
-  RENDERER_SHUTDOWN = 7,
-
-  // Getting the registration failed because there was no live service worker.
-  NO_LIVE_SERVICE_WORKER = 8,
-
-  // NOTE: Do not renumber these as that would confuse interpretation of
-  // previously logged data. When making changes, also update the enum list
-  // in tools/metrics/histograms/histograms.xml to keep it in sync, and
-  // update LAST below.
-
-  LAST = NO_LIVE_SERVICE_WORKER
-};
-
 enum PushPermissionStatus {
   GRANTED = 0,
   DENIED = 1,
diff --git a/content/common/push_messaging.typemap b/content/common/push_messaging.typemap
index c2afcc8..c3b94c6 100644
--- a/content/common/push_messaging.typemap
+++ b/content/common/push_messaging.typemap
@@ -4,7 +4,6 @@
 
 mojom = "//content/common/push_messaging.mojom"
 public_headers = [
-  "//content/public/common/push_messaging_status.h",
   "//content/public/common/push_subscription_options.h",
   "//third_party/WebKit/public/platform/modules/push_messaging/WebPushError.h",
   "//third_party/WebKit/public/platform/modules/push_messaging/WebPushPermissionStatus.h",
@@ -21,8 +20,6 @@
 ]
 type_mappings = [
   "content.mojom.PushErrorType=blink::WebPushError::ErrorType",
-  "content.mojom.PushGetRegistrationStatus=content::PushGetRegistrationStatus",
   "content.mojom.PushPermissionStatus=blink::WebPushPermissionStatus",
-  "content.mojom.PushRegistrationStatus=content::PushRegistrationStatus",
   "content.mojom.PushSubscriptionOptions=content::PushSubscriptionOptions",
 ]
diff --git a/content/common/push_messaging_param_traits.cc b/content/common/push_messaging_param_traits.cc
index 882ab47..a0b6240 100644
--- a/content/common/push_messaging_param_traits.cc
+++ b/content/common/push_messaging_param_traits.cc
@@ -4,121 +4,10 @@
 
 #include "content/common/push_messaging_param_traits.h"
 
+#include "content/public/common/push_messaging_status.mojom.h"
+
 namespace mojo {
 
-// PushRegistrationStatus
-static_assert(
-    content::PushRegistrationStatus::
-            PUSH_REGISTRATION_STATUS_SUCCESS_FROM_PUSH_SERVICE ==
-        static_cast<content::PushRegistrationStatus>(
-            content::mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE),
-    "PushRegistrationStatus enums must match, SUCCESS_FROM_PUSH_SERVICE");
-
-static_assert(
-    content::PushRegistrationStatus::
-            PUSH_REGISTRATION_STATUS_NO_SERVICE_WORKER ==
-        static_cast<content::PushRegistrationStatus>(
-            content::mojom::PushRegistrationStatus::NO_SERVICE_WORKER),
-    "PushRegistrationStatus enums must match, NO_SERVICE_WORKER");
-
-static_assert(
-    content::PushRegistrationStatus::
-            PUSH_REGISTRATION_STATUS_SERVICE_NOT_AVAILABLE ==
-        static_cast<content::PushRegistrationStatus>(
-            content::mojom::PushRegistrationStatus::SERVICE_NOT_AVAILABLE),
-    "PushRegistrationStatus enums must match, SERVICE_NOT_AVAILABLE");
-
-static_assert(
-    content::PushRegistrationStatus::PUSH_REGISTRATION_STATUS_LIMIT_REACHED ==
-        static_cast<content::PushRegistrationStatus>(
-            content::mojom::PushRegistrationStatus::LIMIT_REACHED),
-    "PushRegistrationStatus enums must match, LIMIT_REACHED");
-
-static_assert(
-    content::PushRegistrationStatus::
-            PUSH_REGISTRATION_STATUS_PERMISSION_DENIED ==
-        static_cast<content::PushRegistrationStatus>(
-            content::mojom::PushRegistrationStatus::PERMISSION_DENIED),
-    "PushRegistrationStatus enums must match, PERMISSION_DENIED");
-
-static_assert(
-    content::PushRegistrationStatus::PUSH_REGISTRATION_STATUS_SERVICE_ERROR ==
-        static_cast<content::PushRegistrationStatus>(
-            content::mojom::PushRegistrationStatus::SERVICE_ERROR),
-    "PushRegistrationStatus enums must match, SERVICE_ERROR");
-
-static_assert(
-    content::PushRegistrationStatus::PUSH_REGISTRATION_STATUS_NO_SENDER_ID ==
-        static_cast<content::PushRegistrationStatus>(
-            content::mojom::PushRegistrationStatus::NO_SENDER_ID),
-    "PushRegistrationStatus enums must match, NO_SENDER_ID");
-
-static_assert(
-    content::PushRegistrationStatus::PUSH_REGISTRATION_STATUS_STORAGE_ERROR ==
-        static_cast<content::PushRegistrationStatus>(
-            content::mojom::PushRegistrationStatus::STORAGE_ERROR),
-    "PushRegistrationStatus enums must match, STORAGE_ERROR");
-
-static_assert(
-    content::PushRegistrationStatus::
-            PUSH_REGISTRATION_STATUS_SUCCESS_FROM_CACHE ==
-        static_cast<content::PushRegistrationStatus>(
-            content::mojom::PushRegistrationStatus::SUCCESS_FROM_CACHE),
-    "PushRegistrationStatus enums must match, SUCCESS_FROM_CACHE");
-
-static_assert(
-    content::PushRegistrationStatus::PUSH_REGISTRATION_STATUS_NETWORK_ERROR ==
-        static_cast<content::PushRegistrationStatus>(
-            content::mojom::PushRegistrationStatus::NETWORK_ERROR),
-    "PushRegistrationStatus enums must match, NETWORK_ERROR");
-
-static_assert(
-    content::PushRegistrationStatus::
-            PUSH_REGISTRATION_STATUS_INCOGNITO_PERMISSION_DENIED ==
-        static_cast<content::PushRegistrationStatus>(
-            content::mojom::PushRegistrationStatus::
-                INCOGNITO_PERMISSION_DENIED),
-    "PushRegistrationStatus enums must match, INCOGNITO_PERMISSION_DENIED");
-
-static_assert(
-    content::PushRegistrationStatus::
-            PUSH_REGISTRATION_STATUS_PUBLIC_KEY_UNAVAILABLE ==
-        static_cast<content::PushRegistrationStatus>(
-            content::mojom::PushRegistrationStatus::PUBLIC_KEY_UNAVAILABLE),
-    "PushRegistrationStatus enums must match, PUBLIC_KEY_UNAVAILABLE");
-
-static_assert(
-    content::PushRegistrationStatus::
-            PUSH_REGISTRATION_STATUS_MANIFEST_EMPTY_OR_MISSING ==
-        static_cast<content::PushRegistrationStatus>(
-            content::mojom::PushRegistrationStatus::MANIFEST_EMPTY_OR_MISSING),
-    "PushRegistrationStatus enums must match, MANIFEST_EMPTY_OR_MISSING");
-
-static_assert(
-    content::PushRegistrationStatus::
-            PUSH_REGISTRATION_STATUS_SENDER_ID_MISMATCH ==
-        static_cast<content::PushRegistrationStatus>(
-            content::mojom::PushRegistrationStatus::SENDER_ID_MISMATCH),
-    "PushRegistrationStatus enums must match, SENDER_ID_MISMATCH");
-
-static_assert(
-    content::PushRegistrationStatus::PUSH_REGISTRATION_STATUS_STORAGE_CORRUPT ==
-        static_cast<content::PushRegistrationStatus>(
-            content::mojom::PushRegistrationStatus::STORAGE_CORRUPT),
-    "PushRegistrationStatus enums must match, STORAGE_CORRUPT");
-
-static_assert(
-    content::PushRegistrationStatus::
-            PUSH_REGISTRATION_STATUS_RENDERER_SHUTDOWN ==
-        static_cast<content::PushRegistrationStatus>(
-            content::mojom::PushRegistrationStatus::RENDERER_SHUTDOWN),
-    "PushRegistrationStatus enums must match, RENDERER_SHUTDOWN");
-
-static_assert(content::PushRegistrationStatus::PUSH_REGISTRATION_STATUS_LAST ==
-                  static_cast<content::PushRegistrationStatus>(
-                      content::mojom::PushRegistrationStatus::LAST),
-              "PushRegistrationStatus enums must match, LAST");
-
 // PushErrorType
 static_assert(blink::WebPushError::ErrorType::kErrorTypeAbort ==
                   static_cast<blink::WebPushError::ErrorType>(
@@ -160,69 +49,6 @@
                       content::mojom::PushErrorType::LAST),
               "PushErrorType enums must match, LAST");
 
-// PushGetRegistrationStatus
-static_assert(
-    content::PushGetRegistrationStatus::PUSH_GETREGISTRATION_STATUS_SUCCESS ==
-        static_cast<content::PushGetRegistrationStatus>(
-            content::mojom::PushGetRegistrationStatus::SUCCESS),
-    "PushGetRegistrationStatus enums must match, SUCCESS");
-
-static_assert(
-    content::PushGetRegistrationStatus::
-            PUSH_GETREGISTRATION_STATUS_SERVICE_NOT_AVAILABLE ==
-        static_cast<content::PushGetRegistrationStatus>(
-            content::mojom::PushGetRegistrationStatus::SERVICE_NOT_AVAILABLE),
-    "PushGetRegistrationStatus enums must match, SERVICE_NOT_AVAILABLE");
-
-static_assert(content::PushGetRegistrationStatus::
-                      PUSH_GETREGISTRATION_STATUS_STORAGE_ERROR ==
-                  static_cast<content::PushGetRegistrationStatus>(
-                      content::mojom::PushGetRegistrationStatus::STORAGE_ERROR),
-              "PushGetRegistrationStatus enums must match, STORAGE_ERROR");
-
-static_assert(
-    content::PushGetRegistrationStatus::
-            PUSH_GETREGISTRATION_STATUS_REGISTRATION_NOT_FOUND ==
-        static_cast<content::PushGetRegistrationStatus>(
-            content::mojom::PushGetRegistrationStatus::REGISTRATION_NOT_FOUND),
-    "PushGetRegistrationStatus enums must match, REGISTRATION_NOT_FOUND");
-
-static_assert(
-    content::PushGetRegistrationStatus::
-            PUSH_GETREGISTRATION_STATUS_INCOGNITO_REGISTRATION_NOT_FOUND ==
-        static_cast<content::PushGetRegistrationStatus>(
-            content::mojom::PushGetRegistrationStatus::
-                INCOGNITO_REGISTRATION_NOT_FOUND),
-    "PushGetRegistrationStatus enums must match, "
-    "INCOGNITO_REGISTRATION_NOT_FOUND");
-
-static_assert(
-    content::PushGetRegistrationStatus::
-            PUSH_GETREGISTRATION_STATUS_STORAGE_CORRUPT ==
-        static_cast<content::PushGetRegistrationStatus>(
-            content::mojom::PushGetRegistrationStatus::STORAGE_CORRUPT),
-    "PushGetRegistrationStatus enums must match, STORAGE_CORRUPT");
-
-static_assert(
-    content::PushGetRegistrationStatus::
-            PUSH_GETREGISTRATION_STATUS_NO_LIVE_SERVICE_WORKER ==
-        static_cast<content::PushGetRegistrationStatus>(
-            content::mojom::PushGetRegistrationStatus::NO_LIVE_SERVICE_WORKER),
-    "PushGetRegistrationStatus enums must match, NO_LIVE_SERVICE_WORKER");
-
-static_assert(
-    content::PushGetRegistrationStatus::
-            PUSH_GETREGISTRATION_STATUS_RENDERER_SHUTDOWN ==
-        static_cast<content::PushGetRegistrationStatus>(
-            content::mojom::PushGetRegistrationStatus::RENDERER_SHUTDOWN),
-    "PushGetRegistrationStatus enums must match, RENDERER_SHUTDOWN");
-
-static_assert(
-    content::PushGetRegistrationStatus::PUSH_GETREGISTRATION_STATUS_LAST ==
-        static_cast<content::PushGetRegistrationStatus>(
-            content::mojom::PushGetRegistrationStatus::LAST),
-    "PushGetRegistrationStatus enums must match, LAST");
-
 // PushPermissionStatus
 static_assert(blink::WebPushPermissionStatus::kWebPushPermissionStatusGranted ==
                   static_cast<blink::WebPushPermissionStatus>(
@@ -257,37 +83,6 @@
 }
 
 // static
-content::mojom::PushRegistrationStatus EnumTraits<
-    content::mojom::PushRegistrationStatus,
-    content::PushRegistrationStatus>::ToMojom(content::PushRegistrationStatus
-                                                  input) {
-  if (input >= content::PushRegistrationStatus::
-                   PUSH_REGISTRATION_STATUS_SUCCESS_FROM_PUSH_SERVICE &&
-      input <= content::PushRegistrationStatus::PUSH_REGISTRATION_STATUS_LAST) {
-    return static_cast<content::mojom::PushRegistrationStatus>(input);
-  }
-
-  NOTREACHED();
-  return content::mojom::PushRegistrationStatus::SERVICE_ERROR;
-}
-
-// static
-bool EnumTraits<content::mojom::PushRegistrationStatus,
-                content::PushRegistrationStatus>::
-    FromMojom(content::mojom::PushRegistrationStatus input,
-              content::PushRegistrationStatus* output) {
-  if (input >=
-          content::mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE &&
-      input <= content::mojom::PushRegistrationStatus::LAST) {
-    *output = static_cast<content::PushRegistrationStatus>(input);
-    return true;
-  }
-
-  NOTREACHED();
-  return false;
-}
-
-// static
 content::mojom::PushErrorType
 EnumTraits<content::mojom::PushErrorType, blink::WebPushError::ErrorType>::
     ToMojom(blink::WebPushError::ErrorType input) {
@@ -315,37 +110,6 @@
 }
 
 // static
-content::mojom::PushGetRegistrationStatus
-EnumTraits<content::mojom::PushGetRegistrationStatus,
-           content::PushGetRegistrationStatus>::
-    ToMojom(content::PushGetRegistrationStatus input) {
-  if (input >= content::PushGetRegistrationStatus::
-                   PUSH_GETREGISTRATION_STATUS_SUCCESS &&
-      input <= content::PushGetRegistrationStatus::
-                   PUSH_GETREGISTRATION_STATUS_LAST) {
-    return static_cast<content::mojom::PushGetRegistrationStatus>(input);
-  }
-
-  NOTREACHED();
-  return content::mojom::PushGetRegistrationStatus::SERVICE_NOT_AVAILABLE;
-}
-
-// static
-bool EnumTraits<content::mojom::PushGetRegistrationStatus,
-                content::PushGetRegistrationStatus>::
-    FromMojom(content::mojom::PushGetRegistrationStatus input,
-              content::PushGetRegistrationStatus* output) {
-  if (input >= content::mojom::PushGetRegistrationStatus::SUCCESS &&
-      input <= content::mojom::PushGetRegistrationStatus::LAST) {
-    *output = static_cast<content::PushGetRegistrationStatus>(input);
-    return true;
-  }
-
-  NOTREACHED();
-  return false;
-}
-
-// static
 content::mojom::PushPermissionStatus EnumTraits<
     content::mojom::PushPermissionStatus,
     blink::WebPushPermissionStatus>::ToMojom(blink::WebPushPermissionStatus
diff --git a/content/common/push_messaging_param_traits.h b/content/common/push_messaging_param_traits.h
index 1fd16a64..4845efd2 100644
--- a/content/common/push_messaging_param_traits.h
+++ b/content/common/push_messaging_param_traits.h
@@ -8,6 +8,7 @@
 #include <stddef.h>
 
 #include "content/common/push_messaging.mojom.h"
+#include "content/public/common/push_messaging_status.mojom.h"
 #include "mojo/public/cpp/bindings/struct_traits.h"
 
 namespace mojo {
@@ -27,15 +28,6 @@
 };
 
 template <>
-struct EnumTraits<content::mojom::PushRegistrationStatus,
-                  content::PushRegistrationStatus> {
-  static content::mojom::PushRegistrationStatus ToMojom(
-      content::PushRegistrationStatus input);
-  static bool FromMojom(content::mojom::PushRegistrationStatus input,
-                        content::PushRegistrationStatus* output);
-};
-
-template <>
 struct EnumTraits<content::mojom::PushErrorType,
                   blink::WebPushError::ErrorType> {
   static content::mojom::PushErrorType ToMojom(
@@ -45,15 +37,6 @@
 };
 
 template <>
-struct EnumTraits<content::mojom::PushGetRegistrationStatus,
-                  content::PushGetRegistrationStatus> {
-  static content::mojom::PushGetRegistrationStatus ToMojom(
-      content::PushGetRegistrationStatus input);
-  static bool FromMojom(content::mojom::PushGetRegistrationStatus input,
-                        content::PushGetRegistrationStatus* output);
-};
-
-template <>
 struct EnumTraits<content::mojom::PushPermissionStatus,
                   blink::WebPushPermissionStatus> {
   static content::mojom::PushPermissionStatus ToMojom(
diff --git a/content/common/sandbox_mac.mm b/content/common/sandbox_mac.mm
index 0eb7e2f..9156287 100644
--- a/content/common/sandbox_mac.mm
+++ b/content/common/sandbox_mac.mm
@@ -54,11 +54,14 @@
 
 // Mapping from sandbox process types to resource IDs containing the sandbox
 // profile for all process types known to content.
+// TODO(tsepez): Implement profile for SANDBOX_TYPE_NETWORK.
 SandboxTypeToResourceIDMapping kDefaultSandboxTypeToResourceIDMapping[] = {
-  { SANDBOX_TYPE_RENDERER, IDR_RENDERER_SANDBOX_PROFILE },
-  { SANDBOX_TYPE_UTILITY,  IDR_UTILITY_SANDBOX_PROFILE },
-  { SANDBOX_TYPE_GPU,      IDR_GPU_SANDBOX_PROFILE },
-  { SANDBOX_TYPE_PPAPI,    IDR_PPAPI_SANDBOX_PROFILE },
+    {SANDBOX_TYPE_NO_SANDBOX, -1},
+    {SANDBOX_TYPE_RENDERER, IDR_RENDERER_SANDBOX_PROFILE},
+    {SANDBOX_TYPE_UTILITY, IDR_UTILITY_SANDBOX_PROFILE},
+    {SANDBOX_TYPE_GPU, IDR_GPU_SANDBOX_PROFILE},
+    {SANDBOX_TYPE_PPAPI, IDR_PPAPI_SANDBOX_PROFILE},
+    {SANDBOX_TYPE_NETWORK, -1},
 };
 
 static_assert(arraysize(kDefaultSandboxTypeToResourceIDMapping) == \
diff --git a/content/common/sandbox_mac_unittest_helper.mm b/content/common/sandbox_mac_unittest_helper.mm
index 9d8ddd45..a888465 100644
--- a/content/common/sandbox_mac_unittest_helper.mm
+++ b/content/common/sandbox_mac_unittest_helper.mm
@@ -15,6 +15,7 @@
 #include "base/logging.h"
 #include "base/process/kill.h"
 #include "content/common/sandbox_mac.h"
+#include "content/public/common/sandbox_type.h"
 #include "content/test/test_content_client.h"
 #include "testing/multiprocess_func_list.h"
 
@@ -56,15 +57,17 @@
   for(int i = static_cast<int>(SANDBOX_TYPE_FIRST_TYPE);
       i < SANDBOX_TYPE_AFTER_LAST_TYPE;
       ++i) {
-    if (!RunTestInSandbox(static_cast<SandboxType>(i),
-            test_name, test_data)) {
-      LOG(ERROR) << "Sandboxed test (" << test_name << ")" <<
-          "Failed in sandbox type " << i <<
-          "user data: (" << test_data << ")";
+    if (IsUnsandboxedSandboxType(static_cast<SandboxType>(i)))
+      continue;
+
+    if (!RunTestInSandbox(static_cast<SandboxType>(i), test_name, test_data)) {
+      LOG(ERROR) << "Sandboxed test (" << test_name << ")"
+                 << "Failed in sandbox type " << i << "user data: ("
+                 << test_data << ")";
       return false;
     }
   }
- return true;
+  return true;
 }
 
 bool MacSandboxTest::RunTestInSandbox(SandboxType sandbox_type,
diff --git a/content/common/sandbox_win.cc b/content/common/sandbox_win.cc
index 3963c6e..323e57e 100644
--- a/content/common/sandbox_win.cc
+++ b/content/common/sandbox_win.cc
@@ -12,6 +12,7 @@
 #include "base/command_line.h"
 #include "base/debug/activity_tracker.h"
 #include "base/debug/profiler.h"
+#include "base/feature_list.h"
 #include "base/files/file_util.h"
 #include "base/hash.h"
 #include "base/logging.h"
@@ -627,9 +628,24 @@
   return SetJobMemoryLimit(cmd_line, policy);
 }
 
+// This is for finch. See also crbug.com/464430 for details.
+const base::Feature kEnableCsrssLockdownFeature{
+    "EnableCsrssLockdown", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // TODO(jschuh): Need get these restrictions applied to NaCl and Pepper.
 // Just have to figure out what needs to be warmed up first.
 sandbox::ResultCode AddBaseHandleClosePolicy(sandbox::TargetPolicy* policy) {
+  if (base::win::GetVersion() >= base::win::VERSION_WIN10) {
+    if (base::FeatureList::IsEnabled(kEnableCsrssLockdownFeature)) {
+      // Close all ALPC ports.
+      sandbox::ResultCode ret =
+          policy->AddKernelObjectToClose(L"ALPC Port", NULL);
+      if (ret != sandbox::SBOX_ALL_OK) {
+        return ret;
+      }
+    }
+  }
+
   // TODO(cpu): Add back the BaseNamedObjects policy.
   base::string16 object_path = PrependWindowsSessionPath(
       L"\\BaseNamedObjects\\windows_shell_global_counters");
diff --git a/content/public/browser/browser_context.h b/content/public/browser/browser_context.h
index bbcfad41..8503ee85 100644
--- a/content/public/browser/browser_context.h
+++ b/content/public/browser/browser_context.h
@@ -18,8 +18,6 @@
 #include "base/memory/linked_ptr.h"
 #include "base/supports_user_data.h"
 #include "content/common/content_export.h"
-#include "content/public/common/push_event_payload.h"
-#include "content/public/common/push_messaging_status.h"
 #include "net/url_request/url_request_interceptor.h"
 #include "net/url_request/url_request_job_factory.h"
 #include "services/service_manager/embedder/embedded_service_info.h"
@@ -53,6 +51,10 @@
 
 namespace content {
 
+namespace mojom {
+enum class PushDeliveryStatus;
+}
+
 class BackgroundSyncController;
 class BlobHandle;
 class BrowserPluginGuestManager;
@@ -61,6 +63,7 @@
 class DownloadManager;
 class DownloadManagerDelegate;
 class PermissionManager;
+struct PushEventPayload;
 class PushMessagingService;
 class ResourceContext;
 class ServiceManagerConnection;
@@ -140,7 +143,7 @@
       const GURL& origin,
       int64_t service_worker_registration_id,
       const PushEventPayload& payload,
-      const base::Callback<void(PushDeliveryStatus)>& callback);
+      const base::Callback<void(mojom::PushDeliveryStatus)>& callback);
 
   static void NotifyWillBeDestroyed(BrowserContext* browser_context);
 
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 122447d..689af0c 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -23,6 +23,7 @@
 #include "content/public/common/content_client.h"
 #include "content/public/common/media_stream_request.h"
 #include "content/public/common/resource_type.h"
+#include "content/public/common/sandbox_type.h"
 #include "content/public/common/socket_permission_request.h"
 #include "content/public/common/window_container_type.mojom.h"
 #include "media/media_features.h"
@@ -684,22 +685,18 @@
   // Manager.
   virtual void RegisterInProcessServices(StaticServiceMap* services) {}
 
-  using OutOfProcessServiceMap = std::map<std::string, base::string16>;
+  using OutOfProcessServiceMap =
+      std::map<std::string, std::pair<base::string16, SandboxType>>;
 
-  // Registers services to be loaded out of the browser process, in a sandboxed
-  // utility process. The value of each map entry should be the process name to
-  // use for the service's host process when launched.
-  virtual void RegisterOutOfProcessServices(OutOfProcessServiceMap* services) {}
-
-  // Registers services to be loaded out of the browser process (in a utility
-  // process) without the sandbox.
+  // Registers services to be loaded out of the browser process, in an
+  // utility process. The value of each map entry should be a { process name,
+  // sandbox type } pair to use for the service's host process when launched.
   //
-  // WARNING: This path is NOT recommended! If a service needs another service
-  // that is only available out of the sandbox, it could ask the browser
-  // process to provide it. Only use this method when that approach does not
-  // work.
-  virtual void RegisterUnsandboxedOutOfProcessServices(
-      OutOfProcessServiceMap* services) {}
+  // WARNING: SANDBOX_TYPE_NO_SANDBOX is NOT recommended as it creates an
+  // unsandboxed process! If a service needs another service that is only
+  // available out of the sandbox, it could ask the browser process to provide
+  // it. Only use this method when that approach does not work.
+  virtual void RegisterOutOfProcessServices(OutOfProcessServiceMap* services) {}
 
   // Allow the embedder to provide a dictionary loaded from a JSON file
   // resembling a service manifest whose capabilities section will be merged
diff --git a/content/public/browser/push_messaging_service.h b/content/public/browser/push_messaging_service.h
index 3ca8ec5e..70ea159 100644
--- a/content/public/browser/push_messaging_service.h
+++ b/content/public/browser/push_messaging_service.h
@@ -11,12 +11,17 @@
 
 #include "base/callback_forward.h"
 #include "content/common/content_export.h"
-#include "content/public/common/push_messaging_status.h"
 #include "third_party/WebKit/public/platform/modules/push_messaging/WebPushPermissionStatus.h"
 #include "url/gurl.h"
 
 namespace content {
 
+namespace mojom {
+enum class PushRegistrationStatus;
+enum class PushUnregistrationReason;
+enum class PushUnregistrationStatus;
+}  // namespace mojom
+
 class BrowserContext;
 struct PushSubscriptionOptions;
 
@@ -28,8 +33,9 @@
       base::Callback<void(const std::string& registration_id,
                           const std::vector<uint8_t>& p256dh,
                           const std::vector<uint8_t>& auth,
-                          PushRegistrationStatus status)>;
-  using UnregisterCallback = base::Callback<void(PushUnregistrationStatus)>;
+                          mojom::PushRegistrationStatus status)>;
+  using UnregisterCallback =
+      base::Callback<void(mojom::PushUnregistrationStatus)>;
   using SubscriptionInfoCallback =
       base::Callback<void(bool is_valid,
                           const std::vector<uint8_t>& p256dh,
@@ -80,7 +86,7 @@
   // Unsubscribe the given |sender_id| from the push messaging service. Locally
   // deactivates the subscription, then runs |callback|, then asynchronously
   // attempts to unsubscribe with the push service.
-  virtual void Unsubscribe(PushUnregistrationReason reason,
+  virtual void Unsubscribe(mojom::PushUnregistrationReason reason,
                            const GURL& requesting_origin,
                            int64_t service_worker_registration_id,
                            const std::string& sender_id,
diff --git a/content/public/browser/speech_recognition_manager_delegate.h b/content/public/browser/speech_recognition_manager_delegate.h
index f2ea12d4..c8223d1 100644
--- a/content/public/browser/speech_recognition_manager_delegate.h
+++ b/content/public/browser/speech_recognition_manager_delegate.h
@@ -24,7 +24,7 @@
   // This is called on the IO thread.
   virtual void CheckRecognitionIsAllowed(
       int session_id,
-      base::Callback<void(bool ask_user, bool is_allowed)> callback) = 0;
+      base::OnceCallback<void(bool ask_user, bool is_allowed)> callback) = 0;
 
   // Checks whether the delegate is interested (returning a non nullptr ptr) or
   // not (returning nullptr) in receiving a copy of all sessions events.
diff --git a/content/public/browser/utility_process_host.h b/content/public/browser/utility_process_host.h
index fc1b97d..29559cc 100644
--- a/content/public/browser/utility_process_host.h
+++ b/content/public/browser/utility_process_host.h
@@ -11,6 +11,7 @@
 #include "build/build_config.h"
 #include "content/common/content_export.h"
 #include "content/public/common/bind_interface_helpers.h"
+#include "content/public/common/sandbox_type.h"
 #include "ipc/ipc_sender.h"
 #include "mojo/public/cpp/bindings/interface_ptr.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
@@ -56,8 +57,9 @@
   // the operation.
   virtual void SetExposedDir(const base::FilePath& dir) = 0;
 
-  // Make the process run without a sandbox.
-  virtual void DisableSandbox() = 0;
+  // Make the process run with a specific sandbox type, or unsandboxed if
+  // SANDBOX_TYPE_NO_SANDBOX is specified.
+  virtual void SetSandboxType(SandboxType sandbox_type) = 0;
 
 #if defined(OS_WIN)
   // Make the process run elevated.
diff --git a/content/public/browser/utility_process_mojo_client.h b/content/public/browser/utility_process_mojo_client.h
index 164c1dd3..f13eb762 100644
--- a/content/public/browser/utility_process_mojo_client.h
+++ b/content/public/browser/utility_process_mojo_client.h
@@ -136,7 +136,7 @@
         utility_host_->SetExposedDir(exposed_directory_);
 
       if (disable_sandbox_)
-        utility_host_->DisableSandbox();
+        utility_host_->SetSandboxType(SANDBOX_TYPE_NO_SANDBOX);
 #if defined(OS_WIN)
       if (run_elevated_) {
         DCHECK(disable_sandbox_);
diff --git a/content/public/common/BUILD.gn b/content/public/common/BUILD.gn
index 2ce68b81d..60fc4bd 100644
--- a/content/public/common/BUILD.gn
+++ b/content/public/common/BUILD.gn
@@ -193,8 +193,6 @@
     "previews_state.h",
     "process_type.h",
     "push_event_payload.h",
-    "push_messaging_status.cc",
-    "push_messaging_status.h",
     "push_subscription_options.h",
     "quarantine.h",
     "referrer.cc",
@@ -355,6 +353,7 @@
     "mutable_network_traffic_annotation_tag.mojom",
     "network_service.mojom",
     "network_service_test.mojom",
+    "push_messaging_status.mojom",
     "url_loader.mojom",
     "url_loader_factory.mojom",
     "window_container_type.mojom",
diff --git a/content/public/common/push_messaging_status.cc b/content/public/common/push_messaging_status.cc
deleted file mode 100644
index 5f75d8b..0000000
--- a/content/public/common/push_messaging_status.cc
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright 2014 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 "content/public/common/push_messaging_status.h"
-
-#include "base/logging.h"
-
-namespace content {
-
-const char* PushRegistrationStatusToString(PushRegistrationStatus status) {
-  switch (status) {
-    case PUSH_REGISTRATION_STATUS_SUCCESS_FROM_PUSH_SERVICE:
-      return "Registration successful - from push service";
-
-    case PUSH_REGISTRATION_STATUS_NO_SERVICE_WORKER:
-      return "Registration failed - no Service Worker";
-
-    case PUSH_REGISTRATION_STATUS_SERVICE_NOT_AVAILABLE:
-      return "Registration failed - push service not available";
-
-    case PUSH_REGISTRATION_STATUS_LIMIT_REACHED:
-      return "Registration failed - registration limit has been reached";
-
-    case PUSH_REGISTRATION_STATUS_PERMISSION_DENIED:
-      return "Registration failed - permission denied";
-
-    case PUSH_REGISTRATION_STATUS_SERVICE_ERROR:
-      return "Registration failed - push service error";
-
-    case PUSH_REGISTRATION_STATUS_NO_SENDER_ID:
-      return "Registration failed - missing applicationServerKey, and "
-             "gcm_sender_id not found in manifest";
-
-    case PUSH_REGISTRATION_STATUS_STORAGE_ERROR:
-      return "Registration failed - storage error";
-
-    case PUSH_REGISTRATION_STATUS_SUCCESS_FROM_CACHE:
-      return "Registration successful - from cache";
-
-    case PUSH_REGISTRATION_STATUS_NETWORK_ERROR:
-      return "Registration failed - could not connect to push server";
-
-    case PUSH_REGISTRATION_STATUS_INCOGNITO_PERMISSION_DENIED:
-      // We split this out for UMA, but it must be indistinguishable to JS.
-      return PushRegistrationStatusToString(
-          PUSH_REGISTRATION_STATUS_PERMISSION_DENIED);
-
-    case PUSH_REGISTRATION_STATUS_PUBLIC_KEY_UNAVAILABLE:
-      return "Registration failed - could not retrieve the public key";
-
-    case PUSH_REGISTRATION_STATUS_MANIFEST_EMPTY_OR_MISSING:
-      return "Registration failed - missing applicationServerKey, and manifest "
-             "empty or missing";
-
-    case PUSH_REGISTRATION_STATUS_SENDER_ID_MISMATCH:
-      return "Registration failed - A subscription with a different "
-             "applicationServerKey (or gcm_sender_id) already exists; to "
-             "change the applicationServerKey, unsubscribe then resubscribe.";
-
-    case PUSH_REGISTRATION_STATUS_STORAGE_CORRUPT:
-      return "Registration failed - storage corrupt";
-
-    case PUSH_REGISTRATION_STATUS_RENDERER_SHUTDOWN:
-      return "Registration failed - renderer shutdown";
-  }
-  NOTREACHED();
-  return "";
-}
-
-const char* PushUnregistrationStatusToString(PushUnregistrationStatus status) {
-  switch (status) {
-    case PUSH_UNREGISTRATION_STATUS_SUCCESS_UNREGISTERED:
-      return "Unregistration successful - from push service";
-
-    case PUSH_UNREGISTRATION_STATUS_SUCCESS_WAS_NOT_REGISTERED:
-      return "Unregistration successful - was not registered";
-
-    case PUSH_UNREGISTRATION_STATUS_PENDING_NETWORK_ERROR:
-      return "Unregistration pending - a network error occurred, but it will "
-             "be retried until it succeeds";
-
-    case PUSH_UNREGISTRATION_STATUS_NO_SERVICE_WORKER:
-      return "Unregistration failed - no Service Worker";
-
-    case PUSH_UNREGISTRATION_STATUS_SERVICE_NOT_AVAILABLE:
-      return "Unregistration failed - push service not available";
-
-    case PUSH_UNREGISTRATION_STATUS_PENDING_SERVICE_ERROR:
-      return "Unregistration pending - a push service error occurred, but it "
-             "will be retried until it succeeds";
-
-    case PUSH_UNREGISTRATION_STATUS_STORAGE_ERROR:
-      return "Unregistration failed - storage error";
-
-    case PUSH_UNREGISTRATION_STATUS_NETWORK_ERROR:
-      return "Unregistration failed - could not connect to push server";
-  }
-  NOTREACHED();
-  return "";
-}
-
-}  // namespace content
diff --git a/content/public/common/push_messaging_status.h b/content/public/common/push_messaging_status.mojom
similarity index 63%
rename from content/public/common/push_messaging_status.h
rename to content/public/common/push_messaging_status.mojom
index 64d0418..ed5ef56 100644
--- a/content/public/common/push_messaging_status.h
+++ b/content/public/common/push_messaging_status.mojom
@@ -1,11 +1,80 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright 2017 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CONTENT_PUBLIC_COMMON_PUSH_MESSAGING_STATUS_H_
-#define CONTENT_PUBLIC_COMMON_PUSH_MESSAGING_STATUS_H_
+module content.mojom;
 
-namespace content {
+// Push message event success/error codes for internal use & reporting in UMA.
+// Enum values can be added, but must never be renumbered or deleted and reused.
+enum PushDeliveryStatus {
+  // The message was successfully delivered.
+  SUCCESS = 0,
+
+  // The message could not be delivered because the app id was unknown.
+  UNKNOWN_APP_ID = 2,
+
+  // The message could not be delivered because origin no longer has permission.
+  PERMISSION_DENIED = 3,
+
+  // The message could not be delivered because no service worker was found.
+  NO_SERVICE_WORKER = 4,
+
+  // The message could not be delivered because of a service worker error.
+  SERVICE_WORKER_ERROR = 5,
+
+  // The message was delivered, but the Service Worker passed a Promise to
+  // event.waitUntil that got rejected.
+  EVENT_WAITUNTIL_REJECTED = 6,
+
+  // The message was delivered, but the Service Worker timed out processing it.
+  TIMEOUT = 7,
+
+  // NOTE: Do not renumber these as that would confuse interpretation of
+  // previously logged data. When making changes, also update the enum list
+  // in tools/metrics/histograms/histograms.xml to keep it in sync, and
+  // update LAST below.
+
+  LAST = TIMEOUT
+};
+
+// Push getregistration success/error codes for internal use & reporting in UMA.
+// Enum values can be added, but must never be renumbered or deleted and reused.
+enum PushGetRegistrationStatus {
+  // Getting the registration was successful.
+  SUCCESS = 0,
+
+  // Getting the registration failed because the push service is not available.
+  SERVICE_NOT_AVAILABLE = 1,
+
+  // Getting the registration failed because we failed to read from storage.
+  STORAGE_ERROR = 2,
+
+  // Getting the registration failed because there is no push registration.
+  REGISTRATION_NOT_FOUND = 3,
+
+  // Getting the registration failed because the push service isn't available in
+  // incognito, but we tell JS registration not found to not reveal incognito.
+  INCOGNITO_REGISTRATION_NOT_FOUND = 4,
+
+  // Getting the registration failed because public key could not be retrieved.
+  // PUBLIC_KEY_UNAVAILABLE = 5,
+
+  // Getting the registration failed because storage was corrupt.
+  STORAGE_CORRUPT = 6,
+
+  // Getting the registration failed because the renderer was shut down.
+  RENDERER_SHUTDOWN = 7,
+
+  // Getting the registration failed because there was no live service worker.
+  NO_LIVE_SERVICE_WORKER = 8,
+
+  // NOTE: Do not renumber these as that would confuse interpretation of
+  // previously logged data. When making changes, also update the enum list
+  // in tools/metrics/histograms/histograms.xml to keep it in sync, and
+  // update LAST below.
+
+  LAST = NO_LIVE_SERVICE_WORKER
+};
 
 // Push registration success/error codes for internal use & reporting in UMA.
 // Enum values can be added, but must never be renumbered or deleted and reused.
@@ -16,255 +85,173 @@
   // possible that the push service had its own cache (for example if Chrome's
   // app data was cleared, we might have forgotten about a registration that the
   // push service still stores).
-  PUSH_REGISTRATION_STATUS_SUCCESS_FROM_PUSH_SERVICE = 0,
+  SUCCESS_FROM_PUSH_SERVICE = 0,
 
   // Registration failed because there is no Service Worker.
-  PUSH_REGISTRATION_STATUS_NO_SERVICE_WORKER = 1,
+  NO_SERVICE_WORKER = 1,
 
   // Registration failed because the push service is not available.
-  PUSH_REGISTRATION_STATUS_SERVICE_NOT_AVAILABLE = 2,
+  SERVICE_NOT_AVAILABLE = 2,
 
   // Registration failed because the maximum number of registratons has been
   // reached.
-  PUSH_REGISTRATION_STATUS_LIMIT_REACHED = 3,
+  LIMIT_REACHED = 3,
 
   // Registration failed because permission was denied.
-  PUSH_REGISTRATION_STATUS_PERMISSION_DENIED = 4,
+  PERMISSION_DENIED = 4,
 
   // Registration failed in the push service implemented by the embedder.
-  PUSH_REGISTRATION_STATUS_SERVICE_ERROR = 5,
+  SERVICE_ERROR = 5,
 
   // Registration failed because no sender id was provided by the page.
-  PUSH_REGISTRATION_STATUS_NO_SENDER_ID = 6,
+  NO_SENDER_ID = 6,
 
   // Registration succeeded, but we failed to persist it.
-  PUSH_REGISTRATION_STATUS_STORAGE_ERROR = 7,
+  STORAGE_ERROR = 7,
 
   // A successful registration was already cached in Service Worker storage.
-  PUSH_REGISTRATION_STATUS_SUCCESS_FROM_CACHE = 8,
+  SUCCESS_FROM_CACHE = 8,
 
   // Registration failed due to a network error.
-  PUSH_REGISTRATION_STATUS_NETWORK_ERROR = 9,
+  NETWORK_ERROR = 9,
 
   // Registration failed because the push service is not available in incognito,
   // but we tell JS that permission was denied to not reveal incognito.
-  PUSH_REGISTRATION_STATUS_INCOGNITO_PERMISSION_DENIED = 10,
+  INCOGNITO_PERMISSION_DENIED = 10,
 
   // Registration failed because the public key could not be retrieved.
-  PUSH_REGISTRATION_STATUS_PUBLIC_KEY_UNAVAILABLE = 11,
+  PUBLIC_KEY_UNAVAILABLE = 11,
 
   // Registration failed because the manifest could not be retrieved or was
   // empty.
-  PUSH_REGISTRATION_STATUS_MANIFEST_EMPTY_OR_MISSING = 12,
+  MANIFEST_EMPTY_OR_MISSING = 12,
 
   // Registration failed because a subscription with a different sender id
   // already exists.
-  PUSH_REGISTRATION_STATUS_SENDER_ID_MISMATCH = 13,
+  SENDER_ID_MISMATCH = 13,
 
   // Registration failed because storage was corrupt. It will be retried
   // automatically after unsubscribing to fix the corruption.
-  PUSH_REGISTRATION_STATUS_STORAGE_CORRUPT = 14,
+  STORAGE_CORRUPT = 14,
 
   // Registration failed because the renderer was shut down.
-  PUSH_REGISTRATION_STATUS_RENDERER_SHUTDOWN = 15,
+  RENDERER_SHUTDOWN = 15,
 
   // NOTE: Do not renumber these as that would confuse interpretation of
   // previously logged data. When making changes, also update the enum list
   // in tools/metrics/histograms/histograms.xml to keep it in sync, and
-  // update PUSH_REGISTRATION_STATUS_LAST below.
+  // update LAST below.
 
-  PUSH_REGISTRATION_STATUS_LAST = PUSH_REGISTRATION_STATUS_RENDERER_SHUTDOWN
+  LAST = RENDERER_SHUTDOWN
 };
 
 // Push unregistration reason for reporting in UMA. Enum values can be added,
 // but must never be renumbered or deleted and reused.
 enum PushUnregistrationReason {
   // Should never happen.
-  PUSH_UNREGISTRATION_REASON_UNKNOWN = 0,
+  UNKNOWN = 0,
 
   // Unregistering because the website called the unsubscribe API.
-  PUSH_UNREGISTRATION_REASON_JAVASCRIPT_API = 1,
+  JAVASCRIPT_API = 1,
 
   // Unregistering because the user manually revoked permission.
-  PUSH_UNREGISTRATION_REASON_PERMISSION_REVOKED = 2,
+  PERMISSION_REVOKED = 2,
 
   // Automatic - incoming message's app id was unknown.
-  PUSH_UNREGISTRATION_REASON_DELIVERY_UNKNOWN_APP_ID = 3,
+  DELIVERY_UNKNOWN_APP_ID = 3,
 
   // Automatic - incoming message's origin no longer has permission.
-  PUSH_UNREGISTRATION_REASON_DELIVERY_PERMISSION_DENIED = 4,
+  DELIVERY_PERMISSION_DENIED = 4,
 
   // Automatic - incoming message's service worker was not found.
-  PUSH_UNREGISTRATION_REASON_DELIVERY_NO_SERVICE_WORKER = 5,
+  DELIVERY_NO_SERVICE_WORKER = 5,
 
   // Automatic - GCM Store reset due to corruption.
-  PUSH_UNREGISTRATION_REASON_GCM_STORE_RESET = 6,
+  GCM_STORE_RESET = 6,
 
   // Unregistering because the service worker was unregistered.
-  PUSH_UNREGISTRATION_REASON_SERVICE_WORKER_UNREGISTERED = 7,
+  SERVICE_WORKER_UNREGISTERED = 7,
 
   // Website called subscribe API and the stored subscription was corrupt, so
   // it is being unsubscribed in order to attempt a clean subscription.
-  PUSH_UNREGISTRATION_REASON_SUBSCRIBE_STORAGE_CORRUPT = 8,
+  SUBSCRIBE_STORAGE_CORRUPT = 8,
 
   // Website called getSubscription API and the stored subscription was corrupt.
-  PUSH_UNREGISTRATION_REASON_GET_SUBSCRIPTION_STORAGE_CORRUPT = 9,
+  GET_SUBSCRIPTION_STORAGE_CORRUPT = 9,
 
   // The Service Worker database got wiped, most likely due to corruption.
-  PUSH_UNREGISTRATION_REASON_SERVICE_WORKER_DATABASE_WIPED = 10,
+  SERVICE_WORKER_DATABASE_WIPED = 10,
 
   // NOTE: Do not renumber these as that would confuse interpretation of
   // previously logged data. When making changes, also update the enum list
   // in tools/metrics/histograms/histograms.xml to keep it in sync, and
-  // update PUSH_UNREGISTRATION_REASON_LAST below.
+  // update LAST below.
 
-  PUSH_UNREGISTRATION_REASON_LAST =
-      PUSH_UNREGISTRATION_REASON_SERVICE_WORKER_DATABASE_WIPED
+  LAST = SERVICE_WORKER_DATABASE_WIPED
 };
 
+
 // Push unregistration success/error codes for internal use & reporting in UMA.
 // Enum values can be added, but must never be renumbered or deleted and reused.
 enum PushUnregistrationStatus {
   // The unregistration was successful.
-  PUSH_UNREGISTRATION_STATUS_SUCCESS_UNREGISTERED = 0,
+  SUCCESS_UNREGISTERED = 0,
 
   // Unregistration was unnecessary, as the registration was not found.
-  PUSH_UNREGISTRATION_STATUS_SUCCESS_WAS_NOT_REGISTERED = 1,
+  SUCCESS_WAS_NOT_REGISTERED = 1,
 
   // The unregistration did not happen because of a network error, but will be
   // retried until it succeeds.
-  PUSH_UNREGISTRATION_STATUS_PENDING_NETWORK_ERROR = 2,
+  PENDING_NETWORK_ERROR = 2,
 
   // Unregistration failed because there is no Service Worker.
-  PUSH_UNREGISTRATION_STATUS_NO_SERVICE_WORKER = 3,
+  NO_SERVICE_WORKER = 3,
 
   // Unregistration failed because the push service is not available.
-  PUSH_UNREGISTRATION_STATUS_SERVICE_NOT_AVAILABLE = 4,
+  SERVICE_NOT_AVAILABLE = 4,
 
   // Unregistration failed in the push service implemented by the embedder, but
   // will be retried until it succeeds.
-  PUSH_UNREGISTRATION_STATUS_PENDING_SERVICE_ERROR = 5,
+  PENDING_SERVICE_ERROR = 5,
 
   // Unregistration succeeded, but we failed to clear Service Worker storage.
-  PUSH_UNREGISTRATION_STATUS_STORAGE_ERROR = 6,
+  STORAGE_ERROR = 6,
 
   // Unregistration failed due to a network error.
-  PUSH_UNREGISTRATION_STATUS_NETWORK_ERROR = 7,
+  NETWORK_ERROR = 7,
 
   // NOTE: Do not renumber these as that would confuse interpretation of
   // previously logged data. When making changes, also update the enum list
   // in tools/metrics/histograms/histograms.xml to keep it in sync, and
-  // update PUSH_UNREGISTRATION_STATUS_LAST below.
+  // update LAST below.
 
-  PUSH_UNREGISTRATION_STATUS_LAST = PUSH_UNREGISTRATION_STATUS_NETWORK_ERROR
-};
-
-// Push getregistration success/error codes for internal use & reporting in UMA.
-// Enum values can be added, but must never be renumbered or deleted and reused.
-enum PushGetRegistrationStatus {
-  // Getting the registration was successful.
-  PUSH_GETREGISTRATION_STATUS_SUCCESS = 0,
-
-  // Getting the registration failed because the push service is not available.
-  PUSH_GETREGISTRATION_STATUS_SERVICE_NOT_AVAILABLE = 1,
-
-  // Getting the registration failed because we failed to read from storage.
-  PUSH_GETREGISTRATION_STATUS_STORAGE_ERROR = 2,
-
-  // Getting the registration failed because there is no push registration.
-  PUSH_GETREGISTRATION_STATUS_REGISTRATION_NOT_FOUND = 3,
-
-  // Getting the registration failed because the push service isn't available in
-  // incognito, but we tell JS registration not found to not reveal incognito.
-  PUSH_GETREGISTRATION_STATUS_INCOGNITO_REGISTRATION_NOT_FOUND = 4,
-
-  // Getting the registration failed because public key could not be retrieved.
-  // PUSH_GETREGISTRATION_STATUS_PUBLIC_KEY_UNAVAILABLE = 5,
-
-  // Getting the registration failed because storage was corrupt.
-  PUSH_GETREGISTRATION_STATUS_STORAGE_CORRUPT = 6,
-
-  // Getting the registration failed because the renderer was shut down.
-  PUSH_GETREGISTRATION_STATUS_RENDERER_SHUTDOWN = 7,
-
-  // Getting the registration failed because there was no live service worker.
-  PUSH_GETREGISTRATION_STATUS_NO_LIVE_SERVICE_WORKER = 8,
-
-  // NOTE: Do not renumber these as that would confuse interpretation of
-  // previously logged data. When making changes, also update the enum list
-  // in tools/metrics/histograms/histograms.xml to keep it in sync, and
-  // update PUSH_GETREGISTRATION_STATUS_LAST below.
-
-  PUSH_GETREGISTRATION_STATUS_LAST =
-      PUSH_GETREGISTRATION_STATUS_NO_LIVE_SERVICE_WORKER
-};
-
-// Push message event success/error codes for internal use & reporting in UMA.
-// Enum values can be added, but must never be renumbered or deleted and reused.
-enum PushDeliveryStatus {
-  // The message was successfully delivered.
-  PUSH_DELIVERY_STATUS_SUCCESS = 0,
-
-  // The message could not be delivered because the app id was unknown.
-  PUSH_DELIVERY_STATUS_UNKNOWN_APP_ID = 2,
-
-  // The message could not be delivered because origin no longer has permission.
-  PUSH_DELIVERY_STATUS_PERMISSION_DENIED = 3,
-
-  // The message could not be delivered because no service worker was found.
-  PUSH_DELIVERY_STATUS_NO_SERVICE_WORKER = 4,
-
-  // The message could not be delivered because of a service worker error.
-  PUSH_DELIVERY_STATUS_SERVICE_WORKER_ERROR = 5,
-
-  // The message was delivered, but the Service Worker passed a Promise to
-  // event.waitUntil that got rejected.
-  PUSH_DELIVERY_STATUS_EVENT_WAITUNTIL_REJECTED = 6,
-
-  // The message was delivered, but the Service Worker timed out processing it.
-  PUSH_DELIVERY_STATUS_TIMEOUT = 7,
-
-  // NOTE: Do not renumber these as that would confuse interpretation of
-  // previously logged data. When making changes, also update the enum list
-  // in tools/metrics/histograms/histograms.xml to keep it in sync, and
-  // update PUSH_DELIVERY_STATUS_LAST below.
-
-  PUSH_DELIVERY_STATUS_LAST = PUSH_DELIVERY_STATUS_TIMEOUT
+  LAST = NETWORK_ERROR
 };
 
 // Push message user visible tracking for reporting in UMA. Enum values can be
 // added, but must never be renumbered or deleted and reused.
 enum PushUserVisibleStatus {
   // A notification was required and one (or more) were shown.
-  PUSH_USER_VISIBLE_STATUS_REQUIRED_AND_SHOWN = 0,
+  REQUIRED_AND_SHOWN = 0,
 
   // A notification was not required, but one (or more) were shown anyway.
-  PUSH_USER_VISIBLE_STATUS_NOT_REQUIRED_BUT_SHOWN = 1,
+  NOT_REQUIRED_BUT_SHOWN = 1,
 
   // A notification was not required and none were shown.
-  PUSH_USER_VISIBLE_STATUS_NOT_REQUIRED_AND_NOT_SHOWN = 2,
+  NOT_REQUIRED_AND_NOT_SHOWN = 2,
 
   // A notification was required, but none were shown. Fortunately, the site has
   // been well behaved recently so it was glossed over.
-  PUSH_USER_VISIBLE_STATUS_REQUIRED_BUT_NOT_SHOWN_USED_GRACE = 3,
+  REQUIRED_BUT_NOT_SHOWN_USED_GRACE = 3,
 
   // A notification was required, but none were shown. Unfortunately, the site
   // has run out of grace, so we had to show the user a generic notification.
-  PUSH_USER_VISIBLE_STATUS_REQUIRED_BUT_NOT_SHOWN_GRACE_EXCEEDED = 4,
+  REQUIRED_BUT_NOT_SHOWN_GRACE_EXCEEDED = 4,
 
   // NOTE: Do not renumber these as that would confuse interpretation of
   // previously logged data. When making changes, also update the enum list
   // in tools/metrics/histograms/histograms.xml to keep it in sync, and
-  // update PUSH_USER_VISIBLE_STATUS_LAST below.
+  // update LAST below.
 
-  PUSH_USER_VISIBLE_STATUS_LAST =
-      PUSH_USER_VISIBLE_STATUS_REQUIRED_BUT_NOT_SHOWN_GRACE_EXCEEDED
+  LAST = REQUIRED_BUT_NOT_SHOWN_GRACE_EXCEEDED
 };
-
-const char* PushRegistrationStatusToString(PushRegistrationStatus status);
-
-const char* PushUnregistrationStatusToString(PushUnregistrationStatus status);
-
-}  // namespace content
-
-#endif  // CONTENT_PUBLIC_COMMON_PUSH_MESSAGING_STATUS_H_
diff --git a/content/public/common/sandbox_type.h b/content/public/common/sandbox_type.h
index ef610fbe..f4cf91df 100644
--- a/content/public/common/sandbox_type.h
+++ b/content/public/common/sandbox_type.h
@@ -16,7 +16,11 @@
 
   SANDBOX_TYPE_FIRST_TYPE = 0,  // Placeholder to ease iteration.
 
-  SANDBOX_TYPE_RENDERER = SANDBOX_TYPE_FIRST_TYPE,
+  // Do not apply any sandboxing to the process.
+  SANDBOX_TYPE_NO_SANDBOX = SANDBOX_TYPE_FIRST_TYPE,
+
+  // Renderer or worker process. Most common case.
+  SANDBOX_TYPE_RENDERER,
 
   // Utility process is as restrictive as the worker process except full
   // access is allowed to one configurable directory.
@@ -28,9 +32,18 @@
   // The PPAPI plugin process.
   SANDBOX_TYPE_PPAPI,
 
+  // The network process.
+  SANDBOX_TYPE_NETWORK,
+
   SANDBOX_TYPE_AFTER_LAST_TYPE,  // Placeholder to ease iteration.
 };
 
+inline bool IsUnsandboxedSandboxType(SandboxType sandbox_type) {
+  // TODO(tsepez): Sandbox network process.
+  return sandbox_type == SANDBOX_TYPE_NO_SANDBOX ||
+         sandbox_type == SANDBOX_TYPE_NETWORK;
+}
+
 }  // namespace content
 
 #endif  // CONTENT_PUBLIC_COMMON_SANDBOX_TYPE_H_
diff --git a/content/public/common/web_preferences.cc b/content/public/common/web_preferences.cc
index 68d5e488..4311aff33 100644
--- a/content/public/common/web_preferences.cc
+++ b/content/public/common/web_preferences.cc
@@ -179,6 +179,7 @@
       animation_policy(IMAGE_ANIMATION_POLICY_ALLOWED),
       user_gesture_required_for_presentation(true),
       text_track_margin_percentage(0.0f),
+      page_popups_suppressed(false),
 #if defined(OS_ANDROID)
       text_autosizing_enabled(true),
       font_scale_factor(1.0f),
@@ -205,7 +206,6 @@
       video_rotate_to_fullscreen_enabled(false),
       video_fullscreen_detection_enabled(false),
       embedded_media_experience_enabled(false),
-      page_popups_suppressed(false),
 #endif  // defined(OS_ANDROID)
 #if defined(OS_ANDROID)
       default_minimum_page_scale_factor(0.25f),
diff --git a/content/public/common/web_preferences.h b/content/public/common/web_preferences.h
index 0a275a92..643c288 100644
--- a/content/public/common/web_preferences.h
+++ b/content/public/common/web_preferences.h
@@ -215,6 +215,8 @@
   // Cues will not be placed in this margin area.
   float text_track_margin_percentage;
 
+  bool page_popups_suppressed;
+
 #if defined(OS_ANDROID)
   bool text_autosizing_enabled;
   float font_scale_factor;
@@ -251,7 +253,6 @@
   // If enabled, video fullscreen detection will be enabled.
   bool video_fullscreen_detection_enabled;
   bool embedded_media_experience_enabled;
-  bool page_popups_suppressed;
 #else  // defined(OS_ANDROID)
 #endif  // defined(OS_ANDROID)
 
diff --git a/content/public/test/repeated_notification_observer.cc b/content/public/test/repeated_notification_observer.cc
deleted file mode 100644
index 097ec901..0000000
--- a/content/public/test/repeated_notification_observer.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/public/test/repeated_notification_observer.h"
-
-#include "base/auto_reset.h"
-#include "base/macros.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/browser/notification_service.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace content {
-
-RepeatedNotificationObserver::RepeatedNotificationObserver(int type, int count)
-    : num_outstanding_(count), running_(false) {
-  registrar_.Add(this, type, NotificationService::AllSources());
-}
-
-void RepeatedNotificationObserver::Observe(int type,
-                                           const NotificationSource& source,
-                                           const NotificationDetails& details) {
-  ASSERT_GT(num_outstanding_, 0);
-  if (!--num_outstanding_ && running_)
-    run_loop_.QuitWhenIdle();
-}
-
-void RepeatedNotificationObserver::Wait() {
-  if (num_outstanding_ <= 0)
-    return;
-
-  {
-    DCHECK(!running_);
-    base::AutoReset<bool> auto_reset(&running_, true);
-    run_loop_.Run();
-  }
-}
-
-}  // namespace content
diff --git a/content/public/test/repeated_notification_observer.h b/content/public/test/repeated_notification_observer.h
deleted file mode 100644
index 5e96dfaf8..0000000
--- a/content/public/test/repeated_notification_observer.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_PUBLIC_TEST_REPEATED_NOTIFICATION_OBSERVER_H_
-#define CONTENT_PUBLIC_TEST_REPEATED_NOTIFICATION_OBSERVER_H_
-
-#include "base/run_loop.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
-
-namespace content {
-
-// RepeatedNotificationObserver allows code to wait until specified number
-// of notifications of particular type are posted.
-class RepeatedNotificationObserver : public NotificationObserver {
- public:
-  RepeatedNotificationObserver(int type, int count);
-
-  // NotificationObserver:
-  void Observe(int type,
-               const NotificationSource& source,
-               const NotificationDetails& details) override;
-
-  // Wait until |count| events of |type| (both specified in constructor) happen.
-  void Wait();
-
- private:
-  int num_outstanding_;
-  NotificationRegistrar registrar_;
-  bool running_;
-  base::RunLoop run_loop_;
-
-  DISALLOW_COPY_AND_ASSIGN(RepeatedNotificationObserver);
-};
-
-}  // namespace content
-
-#endif  // CONTENT_PUBLIC_TEST_REPEATED_NOTIFICATION_OBSERVER_H_
diff --git a/content/renderer/media/mojo_audio_output_ipc.cc b/content/renderer/media/mojo_audio_output_ipc.cc
index 7c5a7da5..203bd7c 100644
--- a/content/renderer/media/mojo_audio_output_ipc.cc
+++ b/content/renderer/media/mojo_audio_output_ipc.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "media/audio/audio_device_description.h"
+#include "media/base/scoped_callback_runner.h"
 #include "mojo/public/cpp/system/platform_handle.h"
 
 namespace content {
@@ -44,22 +45,17 @@
   DCHECK(!StreamCreationRequested());
   delegate_ = delegate;
 
-  // We pass in a ScopedClosureRunner to detect the case when the mojo
-  // connection is terminated prior to receiving the response. In this case,
-  // the closure runner will be destructed and call ReceivedDeviceAuthorization
-  // with an error.
+  // We wrap the callback in a ScopedCallbackRunner to detect the case when the
+  // mojo connection is terminated prior to receiving the response. In this
+  // case, the callback runner will be destructed and call
+  // ReceivedDeviceAuthorization with an error.
   DoRequestDeviceAuthorization(
       session_id, device_id,
-      base::BindOnce(
-          &MojoAudioOutputIPC::ReceivedDeviceAuthorization,
-          weak_factory_.GetWeakPtr(),
-          base::ScopedClosureRunner(base::Bind(
-              &MojoAudioOutputIPC::ReceivedDeviceAuthorization,
-              weak_factory_.GetWeakPtr(),
-              base::Passed(base::ScopedClosureRunner()),
-              media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL,
-              media::AudioParameters::UnavailableDeviceParams(),
-              std::string()))));
+      media::ScopedCallbackRunner(
+          base::BindOnce(&MojoAudioOutputIPC::ReceivedDeviceAuthorization,
+                         weak_factory_.GetWeakPtr()),
+          media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL,
+          media::AudioParameters::UnavailableDeviceParams(), std::string()));
 }
 
 void MojoAudioOutputIPC::CreateStream(media::AudioOutputIPCDelegate* delegate,
@@ -167,13 +163,11 @@
 }
 
 void MojoAudioOutputIPC::ReceivedDeviceAuthorization(
-    base::ScopedClosureRunner fallback_closure,
     media::OutputDeviceStatus status,
     const media::AudioParameters& params,
     const std::string& device_id) const {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(delegate_);
-  ignore_result(fallback_closure.Release());
   delegate_->OnDeviceAuthorized(status, params, device_id);
 }
 
diff --git a/content/renderer/media/mojo_audio_output_ipc.h b/content/renderer/media/mojo_audio_output_ipc.h
index b014e50..a8182651 100644
--- a/content/renderer/media/mojo_audio_output_ipc.h
+++ b/content/renderer/media/mojo_audio_output_ipc.h
@@ -58,8 +58,7 @@
                                     const std::string& device_id,
                                     AuthorizationCB callback);
 
-  void ReceivedDeviceAuthorization(base::ScopedClosureRunner fallback_closure,
-                                   media::OutputDeviceStatus status,
+  void ReceivedDeviceAuthorization(media::OutputDeviceStatus status,
                                    const media::AudioParameters& params,
                                    const std::string& device_id) const;
 
diff --git a/content/renderer/push_messaging/push_messaging_client.cc b/content/renderer/push_messaging/push_messaging_client.cc
index 51785ca..0b51e3f 100644
--- a/content/renderer/push_messaging/push_messaging_client.cc
+++ b/content/renderer/push_messaging/push_messaging_client.cc
@@ -13,6 +13,8 @@
 #include "content/child/child_thread_impl.h"
 #include "content/child/push_messaging/push_provider.h"
 #include "content/child/service_worker/web_service_worker_registration_impl.h"
+#include "content/common/push_messaging.mojom.h"
+#include "content/public/common/push_messaging_status.mojom.h"
 #include "content/public/common/service_names.mojom.h"
 #include "content/renderer/manifest/manifest_manager.h"
 #include "content/renderer/render_frame_impl.h"
@@ -83,7 +85,7 @@
   // the caller.
   if (manifest.IsEmpty()) {
     DidSubscribe(std::move(callbacks),
-                 PUSH_REGISTRATION_STATUS_MANIFEST_EMPTY_OR_MISSING,
+                 mojom::PushRegistrationStatus::MANIFEST_EMPTY_OR_MISSING,
                  base::nullopt, base::nullopt, base::nullopt, base::nullopt);
     return;
   }
@@ -110,8 +112,9 @@
           ->RegistrationId();
 
   if (options.sender_info.empty()) {
-    DidSubscribe(std::move(callbacks), PUSH_REGISTRATION_STATUS_NO_SENDER_ID,
-                 base::nullopt, base::nullopt, base::nullopt, base::nullopt);
+    DidSubscribe(std::move(callbacks),
+                 mojom::PushRegistrationStatus::NO_SENDER_ID, base::nullopt,
+                 base::nullopt, base::nullopt, base::nullopt);
     return;
   }
 
@@ -126,15 +129,15 @@
 
 void PushMessagingClient::DidSubscribe(
     std::unique_ptr<blink::WebPushSubscriptionCallbacks> callbacks,
-    content::PushRegistrationStatus status,
+    mojom::PushRegistrationStatus status,
     const base::Optional<GURL>& endpoint,
-    const base::Optional<content::PushSubscriptionOptions>& options,
+    const base::Optional<PushSubscriptionOptions>& options,
     const base::Optional<std::vector<uint8_t>>& p256dh,
     const base::Optional<std::vector<uint8_t>>& auth) {
   DCHECK(callbacks);
 
-  if (status == PUSH_REGISTRATION_STATUS_SUCCESS_FROM_PUSH_SERVICE ||
-      status == PUSH_REGISTRATION_STATUS_SUCCESS_FROM_CACHE) {
+  if (status == mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE ||
+      status == mojom::PushRegistrationStatus::SUCCESS_FROM_CACHE) {
     DCHECK(endpoint);
     DCHECK(options);
     DCHECK(p256dh);
diff --git a/content/renderer/push_messaging/push_messaging_client.h b/content/renderer/push_messaging/push_messaging_client.h
index 695479e0..d243ec2 100644
--- a/content/renderer/push_messaging/push_messaging_client.h
+++ b/content/renderer/push_messaging/push_messaging_client.h
@@ -13,7 +13,6 @@
 
 #include "base/macros.h"
 #include "content/common/push_messaging.mojom.h"
-#include "content/public/common/push_messaging_status.h"
 #include "content/public/renderer/render_frame_observer.h"
 #include "third_party/WebKit/public/platform/modules/push_messaging/WebPushClient.h"
 #include "third_party/WebKit/public/platform/modules/push_messaging/WebPushPermissionStatus.h"
@@ -26,6 +25,10 @@
 
 namespace content {
 
+namespace mojom {
+enum class PushRegistrationStatus;
+}
+
 struct Manifest;
 struct ManifestDebugInfo;
 struct PushSubscriptionOptions;
@@ -64,9 +67,9 @@
 
   void DidSubscribe(
       std::unique_ptr<blink::WebPushSubscriptionCallbacks> callbacks,
-      content::PushRegistrationStatus status,
+      mojom::PushRegistrationStatus status,
       const base::Optional<GURL>& endpoint,
-      const base::Optional<content::PushSubscriptionOptions>& options,
+      const base::Optional<PushSubscriptionOptions>& options,
       const base::Optional<std::vector<uint8_t>>& p256dh,
       const base::Optional<std::vector<uint8_t>>& auth);
 
diff --git a/content/shell/browser/layout_test/layout_test_push_messaging_service.cc b/content/shell/browser/layout_test/layout_test_push_messaging_service.cc
index ec9e296..bea0626 100644
--- a/content/shell/browser/layout_test/layout_test_push_messaging_service.cc
+++ b/content/shell/browser/layout_test/layout_test_push_messaging_service.cc
@@ -8,6 +8,7 @@
 #include "base/logging.h"
 #include "base/macros.h"
 #include "content/public/browser/permission_type.h"
+#include "content/public/common/push_messaging_status.mojom.h"
 #include "content/public/common/push_subscription_options.h"
 #include "content/shell/browser/layout_test/layout_test_browser_context.h"
 #include "content/shell/browser/layout_test/layout_test_content_browser_client.h"
@@ -95,11 +96,11 @@
 
     subscribed_service_worker_registration_ = service_worker_registration_id;
     callback.Run("layoutTestRegistrationId", p256dh, auth,
-                 PUSH_REGISTRATION_STATUS_SUCCESS_FROM_PUSH_SERVICE);
+                 mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE);
   } else {
     callback.Run("registration_id", std::vector<uint8_t>() /* p256dh */,
                  std::vector<uint8_t>() /* auth */,
-                 PUSH_REGISTRATION_STATUS_PERMISSION_DENIED);
+                 mojom::PushRegistrationStatus::PERMISSION_DENIED);
   }
 }
 
@@ -131,7 +132,7 @@
 }
 
 void LayoutTestPushMessagingService::Unsubscribe(
-    PushUnregistrationReason reason,
+    mojom::PushUnregistrationReason reason,
     const GURL& requesting_origin,
     int64_t service_worker_registration_id,
     const std::string& sender_id,
@@ -139,11 +140,12 @@
   ClearPushSubscriptionId(
       LayoutTestContentBrowserClient::Get()->browser_context(),
       requesting_origin, service_worker_registration_id,
-      base::Bind(callback,
-                 service_worker_registration_id ==
-                         subscribed_service_worker_registration_
-                     ? PUSH_UNREGISTRATION_STATUS_SUCCESS_UNREGISTERED
-                     : PUSH_UNREGISTRATION_STATUS_SUCCESS_WAS_NOT_REGISTERED));
+      base::Bind(
+          callback,
+          service_worker_registration_id ==
+                  subscribed_service_worker_registration_
+              ? mojom::PushUnregistrationStatus::SUCCESS_UNREGISTERED
+              : mojom::PushUnregistrationStatus::SUCCESS_WAS_NOT_REGISTERED));
   if (service_worker_registration_id ==
       subscribed_service_worker_registration_) {
     subscribed_service_worker_registration_ =
diff --git a/content/shell/browser/layout_test/layout_test_push_messaging_service.h b/content/shell/browser/layout_test/layout_test_push_messaging_service.h
index 547cb418..5b61b17d 100644
--- a/content/shell/browser/layout_test/layout_test_push_messaging_service.h
+++ b/content/shell/browser/layout_test/layout_test_push_messaging_service.h
@@ -12,7 +12,6 @@
 
 #include "base/macros.h"
 #include "content/public/browser/push_messaging_service.h"
-#include "content/public/common/push_messaging_status.h"
 #include "third_party/WebKit/public/platform/modules/push_messaging/WebPushPermissionStatus.h"
 
 namespace content {
@@ -46,7 +45,7 @@
                                                      bool user_visible)
       override;
   bool SupportNonVisibleMessages() override;
-  void Unsubscribe(PushUnregistrationReason reason,
+  void Unsubscribe(mojom::PushUnregistrationReason reason,
                    const GURL& requesting_origin,
                    int64_t service_worker_registration_id,
                    const std::string& sender_id,
diff --git a/content/shell/browser/shell_content_browser_client.cc b/content/shell/browser/shell_content_browser_client.cc
index f03cde3a..ed2e264 100644
--- a/content/shell/browser/shell_content_browser_client.cc
+++ b/content/shell/browser/shell_content_browser_client.cc
@@ -246,8 +246,8 @@
 
 void ShellContentBrowserClient::RegisterOutOfProcessServices(
       OutOfProcessServiceMap* services) {
-  services->insert(std::make_pair(kTestServiceUrl,
-                                  base::UTF8ToUTF16("Test Service")));
+  (*services)[kTestServiceUrl] = {base::UTF8ToUTF16("Test Service"),
+                                  SANDBOX_TYPE_UTILITY};
 }
 
 std::unique_ptr<base::Value>
diff --git a/content/shell/browser/shell_speech_recognition_manager_delegate.cc b/content/shell/browser/shell_speech_recognition_manager_delegate.cc
index 0b0778d0..2d51634 100644
--- a/content/shell/browser/shell_speech_recognition_manager_delegate.cc
+++ b/content/shell/browser/shell_speech_recognition_manager_delegate.cc
@@ -6,18 +6,19 @@
 
 #include "content/public/browser/browser_thread.h"
 
-using base::Callback;
+using base::OnceCallback;
 
 namespace content {
 
 void ShellSpeechRecognitionManagerDelegate::CheckRecognitionIsAllowed(
-    int session_id, Callback<void(bool ask_user, bool is_allowed)> callback) {
+    int session_id,
+    OnceCallback<void(bool ask_user, bool is_allowed)> callback) {
   // In content_shell, we expect speech recognition to happen when requested.
   // Therefore we simply authorize it by calling back with is_allowed=true. The
   // first parameter, ask_user, is set to false because we don't want to prompt
   // the user for permission with an infobar.
-  BrowserThread::PostTask(
-      BrowserThread::IO, FROM_HERE, base::Bind(callback, false, true));
+  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+                          base::BindOnce(std::move(callback), false, true));
 }
 
 SpeechRecognitionEventListener*
diff --git a/content/shell/browser/shell_speech_recognition_manager_delegate.h b/content/shell/browser/shell_speech_recognition_manager_delegate.h
index c1f6b9d9f..808a109 100644
--- a/content/shell/browser/shell_speech_recognition_manager_delegate.h
+++ b/content/shell/browser/shell_speech_recognition_manager_delegate.h
@@ -25,7 +25,8 @@
   // SpeechRecognitionManagerDelegate methods.
   void CheckRecognitionIsAllowed(
       int session_id,
-      base::Callback<void(bool ask_user, bool is_allowed)> callback) override;
+      base::OnceCallback<void(bool ask_user, bool is_allowed)> callback)
+      override;
   SpeechRecognitionEventListener* GetEventListener() override;
   bool FilterProfanities(int render_process_id) override;
 
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index f55394f..0e70354 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -98,8 +98,6 @@
     "../public/test/ppapi_test_utils.h",
     "../public/test/render_view_test.cc",
     "../public/test/render_view_test.h",
-    "../public/test/repeated_notification_observer.cc",
-    "../public/test/repeated_notification_observer.h",
     "../public/test/service_worker_test_helpers.cc",
     "../public/test/service_worker_test_helpers.h",
     "../public/test/test_browser_context.cc",
diff --git a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
index a6a0947..b450504 100644
--- a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
@@ -201,8 +201,6 @@
     self.Skip('conformance2/textures/misc/' +
         'copy-texture-image-webgl-specific.html',
         ['win', 'passthrough', 'd3d11'], bug=602688)
-    self.Skip('conformance2/reading/read-pixels-pack-parameters.html',
-        ['win', 'passthrough', 'd3d11'], bug=602688)
     self.Skip('conformance2/reading/read-pixels-into-pixel-pack-buffer.html',
         ['win', 'passthrough', 'd3d11'], bug=602688)
 
diff --git a/docs/ios/build_instructions.md b/docs/ios/build_instructions.md
index 3a70ff0..f24adbf 100644
--- a/docs/ios/build_instructions.md
+++ b/docs/ios/build_instructions.md
@@ -234,6 +234,18 @@
 $ out/Debug-iphonesimulator/iossim out/Debug-iphonesimulator/Chromium.app
 ```
 
+### Running EarlGrey tests
+
+EarlGrey tests are run differently than other test targets, as there is an
+XCTest bundle that is injected into the target application. Therefore you must
+also pass in the test bundle:
+
+```shell
+$ out/Debug-iphonesimulator/iossim \
+    out/Debug-iphonesimulator/ios_chrome_ui_egtests.app \
+    out/Debug-iphonesimulator/ios_chrome_ui_egtests.app/PlugIns/ios_chrome_ui_egtests_module.xctest
+```
+
 ## Update your checkout
 
 To update an existing checkout, you can run
diff --git a/extensions/common/api/virtual_keyboard_private.json b/extensions/common/api/virtual_keyboard_private.json
index ec7fdeb..90de6d4e 100644
--- a/extensions/common/api/virtual_keyboard_private.json
+++ b/extensions/common/api/virtual_keyboard_private.json
@@ -41,12 +41,6 @@
         "description": "The value of the virtual keyboard state to change to."
       },
       {
-        "id": "OnTextInputBoxFocusedType",
-        "type": "string",
-        "description": "The value of type attribute of the focused text input box.",
-        "enum": ["text", "number", "password", "date", "url", "tel", "email"]
-      },
-      {
         "id": "Bounds",
         "type": "object",
         "properties": {
@@ -214,24 +208,6 @@
     ],
     "events": [
       {
-        "name": "onTextInputBoxFocused",
-        "type": "function",
-        "description": "This event is sent when focus enters a text input box.",
-        "parameters": [
-          {
-            "type": "object",
-            "name": "context",
-            "description": "Describes the text input box that has acquired focus. Note only the type of text input box is passed. This API is intended to be used by non-ime virtual keyboard only. Normal ime virtual keyboard should use chrome.input.ime.onFocus to get the more detailed InputContext.",
-            "properties": {
-              "type": {
-                "$ref": "OnTextInputBoxFocusedType",
-                "description": "The value of type attribute of the focused text input box."
-              }
-            }
-          }
-        ]
-      },
-      {
         "name": "onBoundsChanged",
         "type": "function",
         "description": "This event is sent when virtual keyboard bounds changed and overscroll/resize is enabled.",
diff --git a/extensions/renderer/BUILD.gn b/extensions/renderer/BUILD.gn
index f3ea9b38..31f727b 100644
--- a/extensions/renderer/BUILD.gn
+++ b/extensions/renderer/BUILD.gn
@@ -309,6 +309,7 @@
   sources = [
     "activity_log_converter_strategy_unittest.cc",
     "api/mojo_private/mojo_private_unittest.cc",
+    "api_activity_logger_unittest.cc",
     "api_test_base.cc",
     "api_test_base.h",
     "api_test_base_unittest.cc",
@@ -362,6 +363,7 @@
     "//base/test:test_support",
     "//components/crx_file:crx_file",
     "//content/public/child",
+    "//content/test:test_support",
     "//extensions:extensions_renderer_resources",
     "//extensions:test_support",
     "//gin:gin_test",
@@ -370,6 +372,7 @@
     "//extensions/browser",
     "//extensions/common",
     "//gin",
+    "//ipc:test_support",
     "//mojo/edk/js",
     "//testing/gmock",
     "//testing/gtest",
diff --git a/extensions/renderer/api_activity_logger.cc b/extensions/renderer/api_activity_logger.cc
index 71cd1e4a..64f35d96 100644
--- a/extensions/renderer/api_activity_logger.cc
+++ b/extensions/renderer/api_activity_logger.cc
@@ -19,6 +19,10 @@
 
 namespace extensions {
 
+namespace {
+bool g_log_for_testing = false;
+}
+
 APIActivityLogger::APIActivityLogger(ScriptContext* context,
                                      Dispatcher* dispatcher)
     : ObjectBackedNativeHandler(context), dispatcher_(dispatcher) {
@@ -37,8 +41,9 @@
     const std::vector<v8::Local<v8::Value>>& arguments) {
   const Dispatcher* dispatcher =
       ExtensionsRendererClient::Get()->GetDispatcher();
-  if (!dispatcher ||  // dispatcher can be null in unittests.
-      !dispatcher->activity_logging_enabled()) {
+  if ((!dispatcher ||  // dispatcher can be null in unittests.
+       !dispatcher->activity_logging_enabled()) &&
+      !g_log_for_testing) {
     return;
   }
 
@@ -53,13 +58,21 @@
   value_args->Reserve(arguments.size());
   // TODO(devlin): This doesn't protect against custom properties, so it might
   // not perfectly reflect the passed arguments.
-  for (const auto& arg : arguments)
-    value_args->Append(converter->FromV8Value(arg, context));
+  for (const auto& arg : arguments) {
+    std::unique_ptr<base::Value> converted_arg =
+        converter->FromV8Value(arg, context);
+    value_args->Append(converted_arg ? std::move(converted_arg)
+                                     : base::MakeUnique<base::Value>());
+  }
 
   LogInternal(APICALL, script_context->GetExtensionID(), call_name,
               std::move(value_args), std::string());
 }
 
+void APIActivityLogger::set_log_for_testing(bool log) {
+  g_log_for_testing = log;
+}
+
 void APIActivityLogger::LogForJS(
     const CallType call_type,
     const v8::FunctionCallbackInfo<v8::Value>& args) {
@@ -93,8 +106,12 @@
     ActivityLogConverterStrategy strategy;
     converter->SetFunctionAllowed(true);
     converter->SetStrategy(&strategy);
-    for (size_t i = 0; i < arg_array->Length(); ++i)
-      arguments->Append(converter->FromV8Value(arg_array->Get(i), context));
+    for (size_t i = 0; i < arg_array->Length(); ++i) {
+      std::unique_ptr<base::Value> converted_arg =
+          converter->FromV8Value(arg_array->Get(i), context);
+      arguments->Append(converted_arg ? std::move(converted_arg)
+                                      : base::MakeUnique<base::Value>());
+    }
   }
 
   LogInternal(call_type, extension_id, call_name, std::move(arguments), extra);
diff --git a/extensions/renderer/api_activity_logger.h b/extensions/renderer/api_activity_logger.h
index 2f586b53..e185b6d 100644
--- a/extensions/renderer/api_activity_logger.h
+++ b/extensions/renderer/api_activity_logger.h
@@ -34,6 +34,8 @@
                          const std::string& call_name,
                          const std::vector<v8::Local<v8::Value>>& arguments);
 
+  static void set_log_for_testing(bool log);
+
  private:
   // Used to distinguish API calls & events from each other in LogInternal.
   enum CallType { APICALL, EVENT };
diff --git a/extensions/renderer/api_activity_logger_unittest.cc b/extensions/renderer/api_activity_logger_unittest.cc
new file mode 100644
index 0000000..be64bb4
--- /dev/null
+++ b/extensions/renderer/api_activity_logger_unittest.cc
@@ -0,0 +1,83 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/api_activity_logger.h"
+
+#include "base/memory/ptr_util.h"
+#include "content/public/test/mock_render_thread.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/common/test_util.h"
+#include "extensions/renderer/bindings/api_binding_test.h"
+#include "extensions/renderer/bindings/api_binding_test_util.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_context_set.h"
+#include "extensions/renderer/test_extensions_renderer_client.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_test_sink.h"
+
+namespace extensions {
+
+namespace {
+
+class ScopedAllowActivityLogging {
+ public:
+  ScopedAllowActivityLogging() { APIActivityLogger::set_log_for_testing(true); }
+
+  ~ScopedAllowActivityLogging() {
+    APIActivityLogger::set_log_for_testing(false);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ScopedAllowActivityLogging);
+};
+
+}  // namespace
+
+using ActivityLoggerTest = APIBindingTest;
+
+// Regression test for crbug.com/740866.
+TEST_F(ActivityLoggerTest, DontCrashOnUnconvertedValues) {
+  content::MockRenderThread mock_render_thread;
+  TestExtensionsRendererClient client;
+  std::set<ExtensionId> extension_ids;
+  ScriptContextSet script_context_set(&extension_ids);
+
+  ScopedAllowActivityLogging scoped_allow_activity_logging;
+
+  v8::HandleScope handle_scope(isolate());
+  v8::Local<v8::Context> context = MainContext();
+
+  scoped_refptr<const Extension> extension = test_util::CreateEmptyExtension();
+  extension_ids.insert(extension->id());
+  const Feature::Context kContextType = Feature::BLESSED_EXTENSION_CONTEXT;
+  script_context_set.AddForTesting(base::MakeUnique<ScriptContext>(
+      context, nullptr, extension.get(), kContextType, extension.get(),
+      kContextType));
+
+  std::vector<v8::Local<v8::Value>> args = {v8::Undefined(isolate())};
+
+  IPC::TestSink& sink = mock_render_thread.sink();
+  sink.ClearMessages();
+
+  APIActivityLogger::LogAPICall(context, "someApiMethod", args);
+
+  ASSERT_EQ(1u, sink.message_count());
+  const IPC::Message* message = sink.GetMessageAt(0u);
+  ASSERT_EQ(ExtensionHostMsg_AddAPIActionToActivityLog::ID, message->type());
+  ExtensionHostMsg_AddAPIActionToActivityLog::Param full_params;
+  ASSERT_TRUE(
+      ExtensionHostMsg_AddAPIActionToActivityLog::Read(message, &full_params));
+  std::string extension_id = std::get<0>(full_params);
+  ExtensionHostMsg_APIActionOrEvent_Params params = std::get<1>(full_params);
+  EXPECT_EQ(extension->id(), extension_id);
+  ASSERT_EQ(1u, params.arguments.GetList().size());
+  EXPECT_EQ(base::Value::Type::NONE, params.arguments.GetList()[0].type());
+
+  ScriptContext* script_context = script_context_set.GetByV8Context(context);
+  script_context_set.Remove(script_context);
+  base::RunLoop().RunUntilIdle();  // Let script context destruction complete.
+}
+
+}  // namespace extensions
diff --git a/extensions/shell/browser/shell_speech_recognition_manager_delegate.cc b/extensions/shell/browser/shell_speech_recognition_manager_delegate.cc
index 25d1b87..759d67af 100644
--- a/extensions/shell/browser/shell_speech_recognition_manager_delegate.cc
+++ b/extensions/shell/browser/shell_speech_recognition_manager_delegate.cc
@@ -66,7 +66,7 @@
 
 void ShellSpeechRecognitionManagerDelegate::CheckRecognitionIsAllowed(
     int session_id,
-    base::Callback<void(bool ask_user, bool is_allowed)> callback) {
+    base::OnceCallback<void(bool ask_user, bool is_allowed)> callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
   const content::SpeechRecognitionSessionContext& context =
@@ -76,11 +76,10 @@
   // |render_process_id| field, which is needed later to retrieve the profile.
   DCHECK_NE(context.render_process_id, 0);
 
-  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
-                          base::Bind(&CheckRenderViewType,
-                                     callback,
-                                     context.render_process_id,
-                                     context.render_view_id));
+  BrowserThread::PostTask(
+      BrowserThread::UI, FROM_HERE,
+      base::BindOnce(&CheckRenderViewType, std::move(callback),
+                     context.render_process_id, context.render_view_id));
 }
 
 content::SpeechRecognitionEventListener*
@@ -96,7 +95,7 @@
 
 // static
 void ShellSpeechRecognitionManagerDelegate::CheckRenderViewType(
-    base::Callback<void(bool ask_user, bool is_allowed)> callback,
+    base::OnceCallback<void(bool ask_user, bool is_allowed)> callback,
     int render_process_id,
     int render_view_id) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
@@ -119,8 +118,9 @@
     }
   }
 
-  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
-                          base::Bind(callback, check_permission, allowed));
+  BrowserThread::PostTask(
+      BrowserThread::IO, FROM_HERE,
+      base::BindOnce(std::move(callback), check_permission, allowed));
 }
 
 }  // namespace speech
diff --git a/extensions/shell/browser/shell_speech_recognition_manager_delegate.h b/extensions/shell/browser/shell_speech_recognition_manager_delegate.h
index 7c05813a..0a0577f2 100644
--- a/extensions/shell/browser/shell_speech_recognition_manager_delegate.h
+++ b/extensions/shell/browser/shell_speech_recognition_manager_delegate.h
@@ -41,12 +41,13 @@
   // SpeechRecognitionManagerDelegate methods.
   void CheckRecognitionIsAllowed(
       int session_id,
-      base::Callback<void(bool ask_user, bool is_allowed)> callback) override;
+      base::OnceCallback<void(bool ask_user, bool is_allowed)> callback)
+      override;
   content::SpeechRecognitionEventListener* GetEventListener() override;
   bool FilterProfanities(int render_process_id) override;
 
   static void CheckRenderViewType(
-      base::Callback<void(bool ask_user, bool is_allowed)> callback,
+      base::OnceCallback<void(bool ask_user, bool is_allowed)> callback,
       int render_process_id,
       int render_view_id);
 
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
index 005a523..6a1cbc9 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
@@ -250,6 +250,27 @@
   GLint image_height_ = 0;
 };
 
+class ScopedPackStateRowLengthReset {
+ public:
+  ScopedPackStateRowLengthReset(bool enable) {
+    if (!enable) {
+      return;
+    }
+
+    glGetIntegerv(GL_PACK_ROW_LENGTH, &row_length_);
+    glPixelStorei(GL_PACK_ROW_LENGTH, 0);
+  }
+
+  ~ScopedPackStateRowLengthReset() {
+    if (row_length_ != 0) {
+      glPixelStorei(GL_PACK_ROW_LENGTH, row_length_);
+    }
+  }
+
+ private:
+  GLint row_length_ = 0;
+};
+
 }  // anonymous namespace
 
 // Implementations of commands
@@ -1757,6 +1778,8 @@
                                                        void* pixels,
                                                        int32_t* success) {
   FlushErrors();
+  ScopedPackStateRowLengthReset reset_row_length(
+      bufsize != 0 && feature_info_->gl_version_info().is_es3);
   glReadPixelsRobustANGLE(x, y, width, height, format, type, bufsize, length,
                           columns, rows, pixels);
   *success = FlushErrors() ? 0 : 1;
diff --git a/gpu/command_buffer/service/memory_program_cache.cc b/gpu/command_buffer/service/memory_program_cache.cc
index 49a68842..2601dea 100644
--- a/gpu/command_buffer/service/memory_program_cache.cc
+++ b/gpu/command_buffer/service/memory_program_cache.cc
@@ -395,7 +395,8 @@
                        curr_size_bytes_ / 1024);
 }
 
-void MemoryProgramCache::LoadProgram(const std::string& program) {
+void MemoryProgramCache::LoadProgram(const std::string& key,
+                                     const std::string& program) {
   std::unique_ptr<GpuProgramProto> proto(
       GpuProgramProto::default_instance().New());
   if (proto->ParseFromString(program)) {
diff --git a/gpu/command_buffer/service/memory_program_cache.h b/gpu/command_buffer/service/memory_program_cache.h
index e4fce94..03881f2 100644
--- a/gpu/command_buffer/service/memory_program_cache.h
+++ b/gpu/command_buffer/service/memory_program_cache.h
@@ -50,7 +50,7 @@
       GLenum transform_feedback_buffer_mode,
       GLES2DecoderClient* client) override;
 
-  void LoadProgram(const std::string& program) override;
+  void LoadProgram(const std::string& key, const std::string& program) override;
 
   size_t Trim(size_t limit) override;
 
diff --git a/gpu/command_buffer/service/memory_program_cache_unittest.cc b/gpu/command_buffer/service/memory_program_cache_unittest.cc
index 62442c9..b2c2f001 100644
--- a/gpu/command_buffer/service/memory_program_cache_unittest.cc
+++ b/gpu/command_buffer/service/memory_program_cache_unittest.cc
@@ -248,7 +248,8 @@
 
   cache_->Clear();
 
-  cache_->LoadProgram(shader_cache_shader());
+  std::string blank;
+  cache_->LoadProgram(blank, shader_cache_shader());
   EXPECT_EQ(ProgramCache::LINK_SUCCEEDED, cache_->GetLinkedProgramStatus(
       vertex_shader_->last_compiled_signature(),
       fragment_shader_->last_compiled_signature(),
@@ -349,8 +350,9 @@
 
   SetExpectationsForLoadLinkedProgram(kProgramId, &emulator);
 
+  std::string blank;
   cache_->Clear();
-  cache_->LoadProgram(shader_cache_shader());
+  cache_->LoadProgram(blank, shader_cache_shader());
 
   EXPECT_EQ(
       ProgramCache::PROGRAM_LOAD_SUCCESS,
diff --git a/gpu/command_buffer/service/mocks.h b/gpu/command_buffer/service/mocks.h
index 0f55139..25c23f0 100644
--- a/gpu/command_buffer/service/mocks.h
+++ b/gpu/command_buffer/service/mocks.h
@@ -138,7 +138,7 @@
                     const std::vector<std::string>& transform_feedback_varyings,
                     GLenum transform_feedback_buffer_mode,
                     GLES2DecoderClient* client));
-  MOCK_METHOD1(LoadProgram, void(const std::string&));
+  MOCK_METHOD2(LoadProgram, void(const std::string&, const std::string&));
   MOCK_METHOD1(Trim, size_t(size_t));
 
  private:
diff --git a/gpu/command_buffer/service/program_cache.h b/gpu/command_buffer/service/program_cache.h
index 5ac9d87..bbed054 100644
--- a/gpu/command_buffer/service/program_cache.h
+++ b/gpu/command_buffer/service/program_cache.h
@@ -71,7 +71,8 @@
       GLenum transform_feedback_buffer_mode,
       GLES2DecoderClient* client) = 0;
 
-  virtual void LoadProgram(const std::string& program) = 0;
+  virtual void LoadProgram(const std::string& key,
+                           const std::string& program) = 0;
 
   // clears the cache
   void Clear();
diff --git a/gpu/command_buffer/service/program_cache_unittest.cc b/gpu/command_buffer/service/program_cache_unittest.cc
index ee80e3a..b4d99ed7 100644
--- a/gpu/command_buffer/service/program_cache_unittest.cc
+++ b/gpu/command_buffer/service/program_cache_unittest.cc
@@ -35,7 +35,8 @@
       GLenum /* transform_feedback_buffer_mode */,
       GLES2DecoderClient* /* client */) override {}
 
-  void LoadProgram(const std::string& /* program */) override {}
+  void LoadProgram(const std::string& /*key*/,
+                   const std::string& /* program */) override {}
 
   void ClearBackend() override {}
 
diff --git a/gpu/ipc/service/gpu_channel_manager.cc b/gpu/ipc/service/gpu_channel_manager.cc
index 51b0c853..6da1483 100644
--- a/gpu/ipc/service/gpu_channel_manager.cc
+++ b/gpu/ipc/service/gpu_channel_manager.cc
@@ -167,9 +167,10 @@
   }
 }
 
-void GpuChannelManager::PopulateShaderCache(const std::string& program_proto) {
+void GpuChannelManager::PopulateShaderCache(const std::string& key,
+                                            const std::string& program) {
   if (program_cache())
-    program_cache()->LoadProgram(program_proto);
+    program_cache()->LoadProgram(key, program);
 }
 
 void GpuChannelManager::LoseAllContexts() {
diff --git a/gpu/ipc/service/gpu_channel_manager.h b/gpu/ipc/service/gpu_channel_manager.h
index 54a1d21..6ec72c1 100644
--- a/gpu/ipc/service/gpu_channel_manager.h
+++ b/gpu/ipc/service/gpu_channel_manager.h
@@ -85,7 +85,7 @@
                                uint64_t client_tracing_id,
                                bool is_gpu_host);
 
-  void PopulateShaderCache(const std::string& shader);
+  void PopulateShaderCache(const std::string& key, const std::string& program);
   void DestroyGpuMemoryBuffer(gfx::GpuMemoryBufferId id,
                               int client_id,
                               const SyncToken& sync_token);
diff --git a/ios/chrome/app/DEPS b/ios/chrome/app/DEPS
index cb09f05..e9d61b1a 100644
--- a/ios/chrome/app/DEPS
+++ b/ios/chrome/app/DEPS
@@ -17,6 +17,7 @@
   "+components/reading_list/core",
   "+components/signin/core/browser",
   "+components/suggestions",
+  "+components/task_scheduler_util",
   "+components/url_formatter",
   "+components/web_resource",
   "+ios/chrome/browser",
diff --git a/ios/chrome/app/startup/BUILD.gn b/ios/chrome/app/startup/BUILD.gn
index bbd7bea..8e236b6 100644
--- a/ios/chrome/app/startup/BUILD.gn
+++ b/ios/chrome/app/startup/BUILD.gn
@@ -24,6 +24,7 @@
   deps = [
     "//base",
     "//components/crash/core/common",
+    "//components/task_scheduler_util/browser",
     "//ios/chrome/browser:chrome_paths",
     "//ios/web/public/app",
     "//skia",
diff --git a/ios/chrome/app/startup/ios_chrome_main.mm b/ios/chrome/app/startup/ios_chrome_main.mm
index d3ff28a..df0f6785 100644
--- a/ios/chrome/app/startup/ios_chrome_main.mm
+++ b/ios/chrome/app/startup/ios_chrome_main.mm
@@ -8,9 +8,11 @@
 
 #include <vector>
 
+#include "base/bind.h"
 #include "base/logging.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/time/time.h"
+#include "components/task_scheduler_util/browser/initialization.h"
 #include "ios/web/public/app/web_main_runner.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -24,7 +26,7 @@
 IOSChromeMain::IOSChromeMain() {
   web_main_runner_.reset(web::WebMainRunner::Create());
 
-  web::WebMainParams main_params = web::WebMainParams(&main_delegate_);
+  web::WebMainParams main_params(&main_delegate_);
 // Copy NSProcessInfo arguments into WebMainParams in debug only, since
 // command line should be meaningless outside of developer builds.
 #if !defined(NDEBUG)
@@ -47,10 +49,12 @@
   main_params.argv = argv;
 #endif
 
+  main_params.get_task_scheduler_init_params_callback = base::Bind(
+      &task_scheduler_util::GetBrowserTaskSchedulerInitParamsFromVariations);
   // Chrome registers an AtExitManager in main in order to initialize breakpad
   // early, so prevent a second registration by WebMainRunner.
   main_params.register_exit_manager = false;
-  web_main_runner_->Initialize(main_params);
+  web_main_runner_->Initialize(std::move(main_params));
 }
 
 IOSChromeMain::~IOSChromeMain() {
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index 55bab02e..d8437f1 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -1245,12 +1245,6 @@
       <message name="IDS_IOS_CONFIRM_PASSWORD_DELETION" desc="Label of a confirmation dialogue button which allows the user to confirm deletion of a stored password. [Length: one line] [iOS only]">
         Delete Saved Password
       </message>
-      <message name="IDS_IOS_SETTINGS_PASSWORDS_SAVED_HEADING" desc="The title for a list of username/site/password items. These items are already saved by the browser and can be deleted/edited. [Length: one line] [iOS only]">
-      Saved Passwords
-      </message>
-      <message name="IDS_IOS_SETTINGS_PASSWORDS_EXCEPTIONS_HEADING" desc="The title for a list of sites where passwords will not be saved. These items are already saved by the browser and can only be deleted. [Length: one line] [iOS only]">
-      Never Saved
-      </message>
       <message name="IDS_IOS_SIGNED_IN_ACCOUNTS_VIEW_OK_BUTTON" desc="The title of the OK button of the Signed In Accounts view [iOS only] [20em]">
         OK, Got It
       </message>
diff --git a/ios/chrome/browser/ntp_tiles/ntp_tiles_egtest.mm b/ios/chrome/browser/ntp_tiles/ntp_tiles_egtest.mm
index 6296993..27f20db 100644
--- a/ios/chrome/browser/ntp_tiles/ntp_tiles_egtest.mm
+++ b/ios/chrome/browser/ntp_tiles/ntp_tiles_egtest.mm
@@ -11,6 +11,8 @@
 #import "ios/chrome/test/earl_grey/chrome_matchers.h"
 #import "ios/chrome/test/earl_grey/chrome_test_case.h"
 #import "ios/testing/wait_util.h"
+#import "ios/web/public/test/http_server/html_response_provider.h"
+#import "ios/web/public/test/http_server/html_response_provider_impl.h"
 #import "ios/web/public/test/http_server/http_server.h"
 #include "ios/web/public/test/http_server/http_server_util.h"
 
@@ -18,12 +20,19 @@
 #error "This file requires ARC support."
 #endif
 
+using web::test::HttpServer;
+
 // Test case for NTP tiles.
 @interface NTPTilesTest : ChromeTestCase
 @end
 
 @implementation NTPTilesTest
 
+- (void)tearDown {
+  chrome_test_util::ClearBrowsingHistory();
+  [[GREYUIThreadExecutor sharedInstance] drainUntilIdle];
+}
+
 // Tests that loading a URL ends up creating an NTP tile.
 - (void)testTopSitesTileAfterLoadURL {
   std::map<GURL, std::string> responses;
@@ -54,4 +63,55 @@
       assertWithMatcher:grey_notNil()];
 }
 
+// Tests that only one NTP tile is displayed for a TopSite that involves a
+// redirect.
+- (void)testTopSitesTileAfterRedirect {
+  std::map<GURL, HtmlResponseProviderImpl::Response> responses;
+  const GURL firstRedirectURL = HttpServer::MakeUrl("http://firstRedirect/");
+  const GURL destinationURL = HttpServer::MakeUrl("http://destination/");
+  responses[firstRedirectURL] = HtmlResponseProviderImpl::GetRedirectResponse(
+      destinationURL, net::HTTP_MOVED_PERMANENTLY);
+
+  // Add titles to both responses, which is what will show up on the NTP.
+  responses[firstRedirectURL].body =
+      "<head><title>title1</title></head>"
+      "<body>Should redirect away.</body>";
+
+  const char kFinalPageContent[] =
+      "<head><title>title2</title></head>"
+      "<body>redirect complete</body>";
+  responses[destinationURL] =
+      HtmlResponseProviderImpl::GetSimpleResponse(kFinalPageContent);
+  std::unique_ptr<web::DataResponseProvider> provider(
+      new HtmlResponseProvider(responses));
+  web::test::SetUpHttpServer(std::move(provider));
+
+  // Clear history and verify that the tile does not exist.
+  chrome_test_util::ClearBrowsingHistory();
+  [[GREYUIThreadExecutor sharedInstance] drainUntilIdle];
+  chrome_test_util::OpenNewTab();
+  [[EarlGrey selectElementWithMatcher:
+                 chrome_test_util::StaticTextWithAccessibilityLabel(@"title2")]
+      assertWithMatcher:grey_nil()];
+
+  // Load first URL and expect redirect to destination URL.
+  [ChromeEarlGrey loadURL:firstRedirectURL];
+  [ChromeEarlGrey waitForWebViewContainingText:"redirect complete"];
+
+  // After loading URL, need to do another action before opening a new tab
+  // with the icon present.
+  [ChromeEarlGrey goBack];
+  chrome_test_util::OpenNewTab();
+
+  // Which of the two tiles that is displayed is an implementation detail, and
+  // this test helps document it. The purpose of the test is to verify that only
+  // one tile is displayed.
+  [[EarlGrey selectElementWithMatcher:
+                 chrome_test_util::StaticTextWithAccessibilityLabel(@"title2")]
+      assertWithMatcher:grey_notNil()];
+  [[EarlGrey selectElementWithMatcher:
+                 chrome_test_util::StaticTextWithAccessibilityLabel(@"title1")]
+      assertWithMatcher:grey_nil()];
+}
+
 @end
diff --git a/ios/chrome/browser/passwords/ios_chrome_password_store_factory.cc b/ios/chrome/browser/passwords/ios_chrome_password_store_factory.cc
index d6ac1a2..b39ec12 100644
--- a/ios/chrome/browser/passwords/ios_chrome_password_store_factory.cc
+++ b/ios/chrome/browser/passwords/ios_chrome_password_store_factory.cc
@@ -10,6 +10,7 @@
 #include "base/command_line.h"
 #include "base/memory/singleton.h"
 #include "base/sequenced_task_runner.h"
+#include "base/task_scheduler/post_task.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "components/browser_sync/profile_sync_service.h"
 #include "components/keyed_service/core/service_access_type.h"
@@ -26,7 +27,6 @@
 #include "ios/chrome/browser/sync/glue/sync_start_util.h"
 #include "ios/chrome/browser/sync/ios_chrome_profile_sync_service_factory.h"
 #include "ios/chrome/browser/web_data_service_factory.h"
-#include "ios/web/public/web_thread.h"
 
 // static
 scoped_refptr<password_manager::PasswordStore>
@@ -80,8 +80,14 @@
 
   scoped_refptr<base::SequencedTaskRunner> main_thread_runner(
       base::SequencedTaskRunnerHandle::Get());
+  // USER_VISIBLE priority is chosen for the background task runner, because
+  // the passwords obtained through tasks on the background runner influence
+  // what the user sees.
+  // TODO(crbug.com/741660): Create the task runner inside password_manager
+  // component instead.
   scoped_refptr<base::SequencedTaskRunner> db_thread_runner(
-      web::WebThread::GetTaskRunnerForThread(web::WebThread::DB));
+      base::CreateSequencedTaskRunnerWithTraits(
+          {base::MayBlock(), base::TaskPriority::USER_VISIBLE}));
 
   scoped_refptr<password_manager::PasswordStore> store =
       new password_manager::PasswordStoreDefault(
diff --git a/ios/chrome/browser/payments/payment_request.h b/ios/chrome/browser/payments/payment_request.h
index 4f54940c..a851d44 100644
--- a/ios/chrome/browser/payments/payment_request.h
+++ b/ios/chrome/browser/payments/payment_request.h
@@ -220,6 +220,10 @@
 
   virtual PaymentsProfileComparator* profile_comparator();
 
+  // Returns a const version of what the non-const |profile_comparator()|
+  // method above returns.
+  const PaymentsProfileComparator* profile_comparator() const;
+
   // Returns whether the current PaymentRequest can be used to make a payment.
   bool CanMakePayment() const;
 
diff --git a/ios/chrome/browser/payments/payment_request.mm b/ios/chrome/browser/payments/payment_request.mm
index 2d1661af..785c7f7 100644
--- a/ios/chrome/browser/payments/payment_request.mm
+++ b/ios/chrome/browser/payments/payment_request.mm
@@ -105,9 +105,7 @@
     }
   }
 
-  // TODO(crbug.com/702063): Change this code to prioritize payment methods by
-  // use count and other means.
-  auto first_complete_payment_method =
+  const auto first_complete_payment_method =
       std::find_if(payment_methods_.begin(), payment_methods_.end(),
                    [this](PaymentInstrument* payment_method) {
                      return payment_method->IsCompleteForPayment();
@@ -229,7 +227,8 @@
   profile_cache_.push_back(
       base::MakeUnique<autofill::AutofillProfile>(profile));
 
-  PopulateAvailableProfiles();
+  contact_profiles_.push_back(profile_cache_.back().get());
+  shipping_profiles_.push_back(profile_cache_.back().get());
 
   return profile_cache_.back().get();
 }
@@ -241,6 +240,7 @@
   if (profiles_to_suggest.empty())
     return;
 
+  profile_cache_.clear();
   profile_cache_.reserve(profiles_to_suggest.size());
 
   for (const auto* profile : profiles_to_suggest) {
@@ -305,7 +305,7 @@
       method_name, credit_card, matches_merchant_card_type_exactly,
       billing_profiles(), GetApplicationLocale(), this));
 
-  PopulateAvailablePaymentMethods();
+  payment_methods_.push_back(payment_method_cache_.back().get());
 
   return static_cast<AutofillPaymentInstrument*>(
       payment_method_cache_.back().get());
@@ -315,6 +315,12 @@
   return &profile_comparator_;
 }
 
+const PaymentsProfileComparator* PaymentRequest::profile_comparator() const {
+  // Return a const version of what the non-const |profile_comparator| method
+  // returns.
+  return const_cast<PaymentRequest*>(this)->profile_comparator();
+}
+
 bool PaymentRequest::CanMakePayment() const {
   for (PaymentInstrument* payment_method : payment_methods_) {
     if (payment_method->IsValidForCanMakePayment()) {
@@ -365,9 +371,10 @@
   if (credit_cards_to_suggest.empty())
     return;
 
-  // TODO(crbug.com/602666): Determine number of possible payments so
-  // that we can appropriate reserve space in the following vector.
+  // TODO(crbug.com/602666): Determine the number of possible payment methods so
+  // that we can reserve enough space in the following vector.
 
+  payment_method_cache_.clear();
   payment_method_cache_.reserve(credit_cards_to_suggest.size());
 
   for (const auto* credit_card : credit_cards_to_suggest)
@@ -381,7 +388,6 @@
   payment_methods_.clear();
   payment_methods_.reserve(payment_method_cache_.size());
 
-  // TODO(crbug.com/602666): Implement prioritization rules for payment methods.
   for (auto const& payment_method : payment_method_cache_)
     payment_methods_.push_back(payment_method.get());
 }
diff --git a/ios/chrome/browser/payments/payment_request_util.h b/ios/chrome/browser/payments/payment_request_util.h
index 5a054a1..ddb51672 100644
--- a/ios/chrome/browser/payments/payment_request_util.h
+++ b/ios/chrome/browser/payments/payment_request_util.h
@@ -51,13 +51,13 @@
 // Helper function to create a notification label for an address cell from an
 // autofill profile. Returns nil if the resulting label is empty.
 NSString* GetAddressNotificationLabelFromAutofillProfile(
-    payments::PaymentRequest& payment_request,
+    const payments::PaymentRequest& payment_request,
     const autofill::AutofillProfile& profile);
 
 // Helper function to create a notification label for what's missing from a
 // payment method. Returns nil if the resulting label is empty.
 NSString* GetPaymentMethodNotificationLabelFromPaymentMethod(
-    payments::PaymentInstrument& payment_method,
+    const payments::PaymentInstrument& payment_method,
     const std::vector<autofill::AutofillProfile*>& billing_profiles);
 
 // Returns the title for the shipping section of the payment summary view given
@@ -74,6 +74,12 @@
 NSString* GetShippingOptionSelectorErrorMessage(
     const payments::PaymentRequest& payment_request);
 
+// Helper function to create a notification label for a contact info entry from
+// an autofill profile. Returns nil if the resulting label is empty.
+NSString* GetContactNotificationLabelFromAutofillProfile(
+    const payments::PaymentRequest& payment_request,
+    const autofill::AutofillProfile& profile);
+
 }  // namespace payment_request_util
 
 #endif  // IOS_CHROME_BROWSER_PAYMENTS_PAYMENT_REQUEST_UTIL_H_
diff --git a/ios/chrome/browser/payments/payment_request_util.mm b/ios/chrome/browser/payments/payment_request_util.mm
index b272ae4..83b1eb0 100644
--- a/ios/chrome/browser/payments/payment_request_util.mm
+++ b/ios/chrome/browser/payments/payment_request_util.mm
@@ -66,7 +66,7 @@
 }
 
 NSString* GetAddressNotificationLabelFromAutofillProfile(
-    payments::PaymentRequest& payment_request,
+    const payments::PaymentRequest& payment_request,
     const autofill::AutofillProfile& profile) {
   base::string16 label =
       payment_request.profile_comparator()->GetStringForMissingShippingFields(
@@ -75,7 +75,7 @@
 }
 
 NSString* GetPaymentMethodNotificationLabelFromPaymentMethod(
-    payments::PaymentInstrument& payment_method,
+    const payments::PaymentInstrument& payment_method,
     const std::vector<autofill::AutofillProfile*>& billing_profiles) {
   base::string16 label = payment_method.GetMissingInfoLabel();
   return !label.empty() ? base::SysUTF16ToNSString(label) : nil;
@@ -131,4 +131,13 @@
   }
 }
 
+NSString* GetContactNotificationLabelFromAutofillProfile(
+    const payments::PaymentRequest& payment_request,
+    const autofill::AutofillProfile& profile) {
+  const base::string16 notification =
+      payment_request.profile_comparator()->GetStringForMissingContactFields(
+          profile);
+  return !notification.empty() ? base::SysUTF16ToNSString(notification) : nil;
+}
+
 }  // namespace payment_request_util
diff --git a/ios/chrome/browser/reading_list/url_downloader.cc b/ios/chrome/browser/reading_list/url_downloader.cc
index ddf99e64..135c014d 100644
--- a/ios/chrome/browser/reading_list/url_downloader.cc
+++ b/ios/chrome/browser/reading_list/url_downloader.cc
@@ -12,12 +12,13 @@
 #include "base/memory/ptr_util.h"
 #include "base/path_service.h"
 #include "base/stl_util.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/threading/thread_restrictions.h"
 #include "components/reading_list/core/offline_url_utils.h"
 #include "ios/chrome/browser/chrome_paths.h"
 #include "ios/chrome/browser/dom_distiller/distiller_viewer.h"
 #include "ios/chrome/browser/reading_list/reading_list_distiller_page.h"
 #include "ios/chrome/browser/reading_list/reading_list_distiller_page_factory.h"
-#include "ios/web/public/web_thread.h"
 #include "net/base/escape.h"
 #include "net/base/load_flags.h"
 #include "net/http/http_response_headers.h"
@@ -60,6 +61,9 @@
       base_directory_(chrome_profile_path),
       mime_type_(),
       url_request_context_getter_(url_request_context_getter),
+      task_runner_(base::CreateSequencedTaskRunnerWithTraits(
+          {base::MayBlock(), base::TaskPriority::BACKGROUND,
+           base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})),
       task_tracker_() {}
 
 URLDownloader::~URLDownloader() {
@@ -68,9 +72,9 @@
 
 void URLDownloader::OfflinePathExists(const base::FilePath& path,
                                       base::Callback<void(bool)> callback) {
-  task_tracker_.PostTaskAndReplyWithResult(
-      web::WebThread::GetTaskRunnerForThread(web::WebThread::FILE).get(),
-      FROM_HERE, base::Bind(&base::PathExists, path), callback);
+  task_tracker_.PostTaskAndReplyWithResult(task_runner_.get(), FROM_HERE,
+                                           base::Bind(&base::PathExists, path),
+                                           callback);
 }
 
 void URLDownloader::RemoveOfflineURL(const GURL& url) {
@@ -117,12 +121,12 @@
     base::FilePath directory_path =
         reading_list::OfflineURLDirectoryAbsolutePath(base_directory_, url);
     task_tracker_.PostTaskAndReply(
-        web::WebThread::GetTaskRunnerForThread(web::WebThread::FILE).get(),
-        FROM_HERE, base::Bind(
-                       [](const base::FilePath& offline_directory_path) {
-                         base::DeleteFile(offline_directory_path, true);
-                       },
-                       directory_path),
+        task_runner_.get(), FROM_HERE,
+        base::Bind(
+            [](const base::FilePath& offline_directory_path) {
+              base::DeleteFile(offline_directory_path, true);
+            },
+            directory_path),
         post_delete);
   } else {
     post_delete.Run();
@@ -150,8 +154,8 @@
 
   if (task.first == DELETE) {
     task_tracker_.PostTaskAndReplyWithResult(
-        web::WebThread::GetTaskRunnerForThread(web::WebThread::FILE).get(),
-        FROM_HERE, base::Bind(&base::DeleteFile, directory_path, true),
+        task_runner_.get(), FROM_HERE,
+        base::Bind(&base::DeleteFile, directory_path, true),
         base::Bind(&URLDownloader::DeleteCompletionHandler,
                    base::Unretained(this), url));
   } else if (task.first == DOWNLOAD) {
@@ -212,9 +216,9 @@
   fetcher_->GetResponseAsFilePath(false, &temporary_path);
 
   task_tracker_.PostTaskAndReplyWithResult(
-      web::WebThread::GetTaskRunnerForThread(web::WebThread::FILE).get(),
-      FROM_HERE, base::Bind(&URLDownloader::SavePDFFile, base::Unretained(this),
-                            temporary_path),
+      task_runner_.get(), FROM_HERE,
+      base::Bind(&URLDownloader::SavePDFFile, base::Unretained(this),
+                 temporary_path),
       base::Bind(&URLDownloader::DownloadCompletionHandler,
                  base::Unretained(this), source->GetOriginalURL(), "", path));
 }
@@ -230,13 +234,13 @@
   fetcher_ = net::URLFetcher::Create(0, pdf_url, net::URLFetcher::GET, this);
   fetcher_->SetRequestContext(url_request_context_getter_.get());
   fetcher_->SetLoadFlags(net::LOAD_SKIP_CACHE_VALIDATION);
-  fetcher_->SaveResponseToTemporaryFile(
-      web::WebThread::GetTaskRunnerForThread(web::WebThread::FILE));
+  fetcher_->SaveResponseToTemporaryFile(task_runner_.get());
   fetcher_->Start();
 }
 
 URLDownloader::SuccessState URLDownloader::SavePDFFile(
     const base::FilePath& temporary_path) {
+  base::ThreadRestrictions::AssertIOAllowed();
   if (CreateOfflineURLDirectory(original_url_)) {
     base::FilePath path = reading_list::OfflinePagePath(
         original_url_, reading_list::OFFLINE_TYPE_PDF);
@@ -279,8 +283,7 @@
   std::vector<dom_distiller::DistillerViewer::ImageInfo> images_block = images;
   std::string block_html = html;
   task_tracker_.PostTaskAndReplyWithResult(
-      web::WebThread::GetTaskRunnerForThread(web::WebThread::FILE).get(),
-      FROM_HERE,
+      task_runner_.get(), FROM_HERE,
       base::Bind(&URLDownloader::SaveDistilledHTML, base::Unretained(this),
                  page_url, images_block, block_html),
       base::Bind(&URLDownloader::DownloadCompletionHandler,
@@ -294,6 +297,7 @@
     const std::vector<dom_distiller::DistillerViewerInterface::ImageInfo>&
         images,
     const std::string& html) {
+  base::ThreadRestrictions::AssertIOAllowed();
   if (CreateOfflineURLDirectory(url)) {
     return SaveHTMLForURL(SaveAndReplaceImagesInHTML(url, html, images), url)
                ? DOWNLOAD_SUCCESS
diff --git a/ios/chrome/browser/reading_list/url_downloader.h b/ios/chrome/browser/reading_list/url_downloader.h
index 24beb41..8bf83c43 100644
--- a/ios/chrome/browser/reading_list/url_downloader.h
+++ b/ios/chrome/browser/reading_list/url_downloader.h
@@ -16,6 +16,7 @@
 class GURL;
 namespace base {
 class FilePath;
+class SequencedTaskRunner;
 }
 
 namespace net {
@@ -192,6 +193,7 @@
   // URLRequestContextGetter needed for the URLFetcher.
   scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
   std::unique_ptr<dom_distiller::DistillerViewerInterface> distiller_;
+  scoped_refptr<base::SequencedTaskRunner> task_runner_;
   base::CancelableTaskTracker task_tracker_;
 
   DISALLOW_COPY_AND_ASSIGN(URLDownloader);
diff --git a/ios/chrome/browser/reading_list/url_downloader_unittest.mm b/ios/chrome/browser/reading_list/url_downloader_unittest.mm
index 6905af9..4e0c5c46 100644
--- a/ios/chrome/browser/reading_list/url_downloader_unittest.mm
+++ b/ios/chrome/browser/reading_list/url_downloader_unittest.mm
@@ -9,15 +9,14 @@
 #include "base/files/file_util.h"
 #import "base/mac/bind_objc_block.h"
 #include "base/path_service.h"
-#include "base/run_loop.h"
 #include "base/stl_util.h"
 #import "base/test/ios/wait_util.h"
+#include "base/test/scoped_task_environment.h"
 #include "components/reading_list/core/offline_url_utils.h"
 #include "ios/chrome/browser/chrome_paths.h"
 #include "ios/chrome/browser/dom_distiller/distiller_viewer.h"
 #include "ios/chrome/browser/reading_list/offline_url_utils.h"
 #include "ios/chrome/browser/reading_list/reading_list_distiller_page.h"
-#include "ios/web/public/test/test_web_thread_bundle.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace {
@@ -136,15 +135,13 @@
 namespace {
 class URLDownloaderTest : public testing::Test {
  public:
-  std::unique_ptr<MockURLDownloader> downloader_;
-  web::TestWebThreadBundle bundle_;
-
   URLDownloaderTest() {
     base::FilePath data_dir;
     base::PathService::Get(ios::DIR_USER_DATA, &data_dir);
     RemoveOfflineFilesDirectory(data_dir);
     downloader_.reset(new MockURLDownloader(data_dir));
   }
+
   ~URLDownloaderTest() override {}
 
   void TearDown() override {
@@ -154,10 +151,12 @@
     downloader_->ClearCompletionTrackers();
   }
 
-  void WaitUntilCondition(ConditionBlock condition) {
-    base::test::ios::WaitUntilCondition(condition, true,
-                                        base::TimeDelta::FromSeconds(1));
-  }
+ protected:
+  base::test::ScopedTaskEnvironment task_environment_;
+  std::unique_ptr<MockURLDownloader> downloader_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(URLDownloaderTest);
 };
 
 TEST_F(URLDownloaderTest, SingleDownload) {
@@ -168,9 +167,8 @@
 
   downloader_->DownloadOfflineURL(url);
 
-  WaitUntilCondition(^bool {
-    return base::ContainsValue(downloader_->downloaded_files_, url);
-  });
+  // Wait for all asynchronous tasks to complete.
+  task_environment_.RunUntilIdle();
 
   ASSERT_TRUE(downloader_->CheckExistenceOfOfflineURLPagePath(url));
 }
@@ -187,9 +185,8 @@
 
   downloader_->DownloadOfflineURL(url);
 
-  WaitUntilCondition(^bool {
-    return base::ContainsValue(downloader_->downloaded_files_, url);
-  });
+  // Wait for all asynchronous tasks to complete.
+  task_environment_.RunUntilIdle();
 
   EXPECT_TRUE(downloader_->CheckExistenceOfOfflineURLPagePath(url));
 }
@@ -205,9 +202,8 @@
 
   downloader_->DownloadOfflineURL(url);
 
-  WaitUntilCondition(^bool {
-    return downloader_->fetching_pdf_;
-  });
+  // Wait for all asynchronous tasks to complete.
+  task_environment_.RunUntilIdle();
 
   EXPECT_FALSE(downloader_->CheckExistenceOfOfflineURLPagePath(url));
 }
@@ -225,9 +221,8 @@
   downloader_->RemoveOfflineURL(url);
   downloader_->FakeEndWorking();
 
-  WaitUntilCondition(^bool {
-    return base::ContainsValue(downloader_->removed_files_, url);
-  });
+  // Wait for all asynchronous tasks to complete.
+  task_environment_.RunUntilIdle();
 
   ASSERT_TRUE(!base::ContainsValue(downloader_->downloaded_files_, url));
   ASSERT_EQ(1ul, downloader_->downloaded_files_.size());
@@ -245,9 +240,8 @@
   downloader_->DownloadOfflineURL(url);
   downloader_->FakeEndWorking();
 
-  WaitUntilCondition(^bool {
-    return base::ContainsValue(downloader_->removed_files_, url);
-  });
+  // Wait for all asynchronous tasks to complete.
+  task_environment_.RunUntilIdle();
 
   ASSERT_TRUE(base::ContainsValue(downloader_->downloaded_files_, url));
   ASSERT_TRUE(base::ContainsValue(downloader_->removed_files_, url));
diff --git a/ios/chrome/browser/snapshots/BUILD.gn b/ios/chrome/browser/snapshots/BUILD.gn
index 20d924e..8b4aecc6 100644
--- a/ios/chrome/browser/snapshots/BUILD.gn
+++ b/ios/chrome/browser/snapshots/BUILD.gn
@@ -13,6 +13,8 @@
     "snapshot_cache_internal.h",
     "snapshot_cache_web_state_list_observer.h",
     "snapshot_cache_web_state_list_observer.mm",
+    "snapshot_constants.h",
+    "snapshot_constants.mm",
     "snapshot_manager.h",
     "snapshot_manager.mm",
     "snapshot_overlay.h",
diff --git a/ios/chrome/browser/snapshots/snapshot_constants.h b/ios/chrome/browser/snapshots/snapshot_constants.h
new file mode 100644
index 0000000..15e65e79
--- /dev/null
+++ b/ios/chrome/browser/snapshots/snapshot_constants.h
@@ -0,0 +1,13 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_SNAPSHOTS_SNAPSHOT_CONSTANTS_H_
+#define IOS_CHROME_BROWSER_SNAPSHOTS_SNAPSHOT_CONSTANTS_H_
+
+#import <CoreGraphics/CoreGraphics.h>
+
+// Size for thumbnail snapshots.
+extern const CGSize kSnapshotThumbnailSize;
+
+#endif  // IOS_CHROME_BROWSER_SNAPSHOTS_SNAPSHOT_CONSTANTS_H_
diff --git a/ios/chrome/browser/snapshots/snapshot_constants.mm b/ios/chrome/browser/snapshots/snapshot_constants.mm
new file mode 100644
index 0000000..34e2925
--- /dev/null
+++ b/ios/chrome/browser/snapshots/snapshot_constants.mm
@@ -0,0 +1,11 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/snapshots/snapshot_constants.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+const CGSize kSnapshotThumbnailSize = {250.0f, 250.0f};
diff --git a/ios/chrome/browser/ui/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view_controller.mm
index 0f6e5177..ee0cd0a8 100644
--- a/ios/chrome/browser/ui/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view_controller.mm
@@ -33,7 +33,9 @@
 #include "base/metrics/user_metrics_action.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/task_scheduler/post_task.h"
 #include "base/threading/sequenced_worker_pool.h"
+#include "base/threading/thread_restrictions.h"
 #include "components/bookmarks/browser/base_bookmark_model_observer.h"
 #include "components/bookmarks/browser/bookmark_model.h"
 #include "components/image_fetcher/ios/ios_image_data_fetcher_wrapper.h"
@@ -739,9 +741,12 @@
 - (void)managePermissionAndSaveImage:(NSData*)data
                    withFileExtension:(NSString*)fileExtension;
 // Saves the image. In order to keep the metadata of the image, the image is
-// saved as a temporary file on disk then saved in photos.
-// This should be called on FILE thread.
-- (void)saveImage:(NSData*)data withFileExtension:(NSString*)fileExtension;
+// saved as a temporary file on disk then saved in photos. Saving will happen
+// on a background sequence and the completion block will be invoked on that
+// sequence.
+- (void)saveImage:(NSData*)data
+    withFileExtension:(NSString*)fileExtension
+           completion:(void (^)(BOOL, NSError*))completionBlock;
 // Called when Chrome has been denied access to the photos or videos and the
 // user can change it.
 // Shows a privacy alert on the main queue, allowing the user to go to Chrome's
@@ -3319,50 +3324,62 @@
       break;
 
     // The application has permission to access the photos.
-    default: {
-      web::WebThread::PostTask(
-          web::WebThread::FILE, FROM_HERE, base::BindBlockArc(^{
-            [self saveImage:data withFileExtension:fileExtension];
-          }));
+    default:
+      __weak BrowserViewController* weakSelf = self;
+      [self saveImage:data
+          withFileExtension:fileExtension
+                 completion:^(BOOL success, NSError* error) {
+                   [weakSelf finishSavingImageWithError:error];
+                 }];
       break;
-    }
   }
 }
 
-- (void)saveImage:(NSData*)data withFileExtension:(NSString*)fileExtension {
-  NSString* fileName = [[[NSProcessInfo processInfo] globallyUniqueString]
-      stringByAppendingString:fileExtension];
-  NSURL* fileURL =
-      [NSURL fileURLWithPath:[NSTemporaryDirectory()
-                                 stringByAppendingPathComponent:fileName]];
-  NSError* error = nil;
-  [data writeToURL:fileURL options:NSDataWritingAtomic error:&error];
+- (void)saveImage:(NSData*)data
+    withFileExtension:(NSString*)fileExtension
+           completion:(void (^)(BOOL, NSError*))completion {
+  base::PostTaskWithTraits(
+      FROM_HERE,
+      {base::MayBlock(), base::TaskPriority::BACKGROUND,
+       base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
+      base::BindBlockArc(^{
+        base::ThreadRestrictions::AssertIOAllowed();
 
-  // Error while writing the image to disk.
-  if (error) {
-    NSString* errorMessage = [NSString
-        stringWithFormat:@"%@ (%@ %" PRIdNS ")", [error localizedDescription],
-                         [error domain], [error code]];
-    [self displayPrivacyErrorAlertOnMainQueue:errorMessage];
-    return;
-  }
+        NSString* fileName = [[[NSProcessInfo processInfo] globallyUniqueString]
+            stringByAppendingString:fileExtension];
+        NSURL* fileURL = [NSURL
+            fileURLWithPath:[NSTemporaryDirectory()
+                                stringByAppendingPathComponent:fileName]];
+        NSError* error = nil;
+        [data writeToURL:fileURL options:NSDataWritingAtomic error:&error];
+        if (error) {
+          if (completion)
+            completion(NO, error);
+          return;
+        }
 
-  // Save the image to photos.
-  [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
-    [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:fileURL];
-  }
-      completionHandler:^(BOOL success, NSError* error) {
-        // Callback for the image saving.
-        [self finishSavingImageWithError:error];
+        [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
+          [PHAssetChangeRequest
+              creationRequestForAssetFromImageAtFileURL:fileURL];
+        }
+            completionHandler:^(BOOL success, NSError* error) {
+              base::PostTaskWithTraits(
+                  FROM_HERE,
+                  {base::MayBlock(), base::TaskPriority::BACKGROUND,
+                   base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
+                  base::BindBlockArc(^{
+                    base::ThreadRestrictions::AssertIOAllowed();
+                    if (completion)
+                      completion(success, error);
 
-        // Cleanup the temporary file.
-        web::WebThread::PostTask(
-            web::WebThread::FILE, FROM_HERE, base::BindBlockArc(^{
-              NSError* error = nil;
-              [[NSFileManager defaultManager] removeItemAtURL:fileURL
-                                                        error:&error];
-            }));
-      }];
+                    // Cleanup the temporary file.
+                    NSError* deleteFileError = nil;
+                    [[NSFileManager defaultManager]
+                        removeItemAtURL:fileURL
+                                  error:&deleteFileError];
+                  }));
+            }];
+      }));
 }
 
 - (void)displayImageErrorAlertWithSettingsOnMainQueue {
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm
index 8ff51fb..7c53b521 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm
@@ -70,7 +70,6 @@
   if (self) {
     _collectionUpdater = [[ContentSuggestionsCollectionUpdater alloc]
         initWithDataSource:dataSource];
-    self.collectionView.prefetchingEnabled = NO;
   }
   return self;
 }
diff --git a/ios/chrome/browser/ui/infobars/infobar_view.mm b/ios/chrome/browser/ui/infobars/infobar_view.mm
index dc3471dd..d4b54623 100644
--- a/ios/chrome/browser/ui/infobars/infobar_view.mm
+++ b/ios/chrome/browser/ui/infobars/infobar_view.mm
@@ -658,9 +658,7 @@
                        action:(SEL)action {
   DCHECK(!closeButton_);
   // TODO(crbug/228611): Add IDR_ constant and use GetNativeImageNamed().
-  NSString* imagePath =
-      [[NSBundle mainBundle] pathForResource:@"infobar_close" ofType:@"png"];
-  UIImage* image = [UIImage imageWithContentsOfFile:imagePath];
+  UIImage* image = [UIImage imageNamed:@"infobar_close"];
   closeButton_ = [UIButton buttonWithType:UIButtonTypeCustom];
   [closeButton_ setExclusiveTouch:YES];
   [closeButton_ setImage:image forState:UIControlStateNormal];
diff --git a/ios/chrome/browser/ui/keyboard/BUILD.gn b/ios/chrome/browser/ui/keyboard/BUILD.gn
index 0a188fc..eec343de 100644
--- a/ios/chrome/browser/ui/keyboard/BUILD.gn
+++ b/ios/chrome/browser/ui/keyboard/BUILD.gn
@@ -7,8 +7,6 @@
   sources = [
     "UIKeyCommand+Chrome.h",
     "UIKeyCommand+Chrome.mm",
-    "hardware_keyboard_watcher.h",
-    "hardware_keyboard_watcher.mm",
   ]
   deps = [
     "//base",
@@ -21,14 +19,11 @@
   testonly = true
   sources = [
     "UIKeyCommand+ChromeTest.mm",
-    "hardware_keyboard_watcher_unittest.mm",
   ]
   deps = [
     ":keyboard",
     "//base",
-    "//base/test:test_support",
     "//ios/chrome/browser/ui/commands",
     "//testing/gtest",
-    "//third_party/ocmock",
   ]
 }
diff --git a/ios/chrome/browser/ui/keyboard/hardware_keyboard_watcher.h b/ios/chrome/browser/ui/keyboard/hardware_keyboard_watcher.h
deleted file mode 100644
index 770181a0..0000000
--- a/ios/chrome/browser/ui/keyboard/hardware_keyboard_watcher.h
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_UI_KEYBOARD_HARDWARE_KEYBOARD_WATCHER_H_
-#define IOS_CHROME_BROWSER_UI_KEYBOARD_HARDWARE_KEYBOARD_WATCHER_H_
-
-#import <UIKit/UIKit.h>
-
-// Watches keyboard events to determine if the keyboard is software (provided by
-// iOS, fully visible on screen when showing) or hardware (external keyboard,
-// only showing a potential input accessory view).
-// It reports the mode for each keyboard frame change via an UMA histogram
-// (Omnibox.HardwareKeyboardModeEnabled).
-@interface HardwareKeyboardWatcher : NSObject
-
-// Pass an accessory view to check for presence in the view hierarchy. Keyboard
-// presentation/dismissal with no input accessory view have a different code
-// path between hardware and software keyboard mode, thus unreliable for
-// metrics comparisons.
-// |accessoryView| must not be nil.
-- (instancetype)initWithAccessoryView:(UIView*)accessoryView
-    NS_DESIGNATED_INITIALIZER;
-
-// Detection of external keyboards only works when an input accessory view is
-// set.
-- (instancetype)init NS_UNAVAILABLE;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_KEYBOARD_HARDWARE_KEYBOARD_WATCHER_H_
diff --git a/ios/chrome/browser/ui/keyboard/hardware_keyboard_watcher.mm b/ios/chrome/browser/ui/keyboard/hardware_keyboard_watcher.mm
deleted file mode 100644
index a13ee7fc..0000000
--- a/ios/chrome/browser/ui/keyboard/hardware_keyboard_watcher.mm
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ios/chrome/browser/ui/keyboard/hardware_keyboard_watcher.h"
-
-#import <CoreGraphics/CoreGraphics.h>
-
-#include "base/logging.h"
-#include "base/mac/scoped_nsobject.h"
-#include "base/metrics/histogram_macros.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-namespace {
-
-// Whether firstRect has a non null rect intersection with secondRect, yet does
-// not fully include it.
-bool IntersectsButDoesNotInclude(CGRect firstRect, CGRect secondRect) {
-  return CGRectIntersectsRect(firstRect, secondRect) &&
-         !CGRectContainsRect(firstRect, secondRect);
-}
-
-}  // namespace
-
-@interface HardwareKeyboardWatcher () {
-  base::scoped_nsobject<UIView> _accessoryView;
-}
-@end
-
-@implementation HardwareKeyboardWatcher
-
-- (instancetype)init {
-  NOTREACHED();
-  return nil;
-}
-
-- (instancetype)initWithAccessoryView:(UIView*)accessoryView {
-  DCHECK(accessoryView);
-  self = [super init];
-  if (self) {
-    _accessoryView.reset(accessoryView);
-    [[NSNotificationCenter defaultCenter]
-        addObserver:self
-           selector:@selector(keyboardWillChangeFrame:)
-               name:UIKeyboardWillChangeFrameNotification
-             object:nil];
-  }
-  return self;
-}
-
-- (void)dealloc {
-  [[NSNotificationCenter defaultCenter] removeObserver:self];
-}
-
-- (void)keyboardWillChangeFrame:(NSNotification*)notification {
-  // Don't handle keyboard notifications not involving the set accessory view.
-  if ([_accessoryView window] == nil)
-    return;
-
-  NSDictionary* userInfo = [notification userInfo];
-  CGRect beginKeyboardFrame =
-      [userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
-  CGRect endKeyboardFrame =
-      [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
-  CGRect screenBounds = [UIScreen mainScreen].bounds;
-
-  // During rotations, the reported keyboard frames are in the screen
-  // coordinates *prior* to the rotation, while the screen already has its
-  // new coordinates. http://crbug.com/511267
-  // To alleviate that, switch the screen bounds width and height if needed.
-  if (CGRectGetHeight(screenBounds) == CGRectGetWidth(beginKeyboardFrame)) {
-    screenBounds.size =
-        CGSizeMake(CGRectGetHeight(screenBounds), CGRectGetWidth(screenBounds));
-  }
-
-  // CGRectZero frames are seen when moving a split dock. Split keyboard means
-  // software keyboard.
-  bool hasCGRectZeroFrames =
-      CGRectEqualToRect(CGRectZero, beginKeyboardFrame) ||
-      CGRectEqualToRect(CGRectZero, endKeyboardFrame);
-
-  bool keyboardIsPartiallyOnScreen =
-      IntersectsButDoesNotInclude(screenBounds, beginKeyboardFrame) ||
-      IntersectsButDoesNotInclude(screenBounds, endKeyboardFrame);
-
-  bool isInHarwareKeyboardMode =
-      !hasCGRectZeroFrames && keyboardIsPartiallyOnScreen;
-
-  UMA_HISTOGRAM_BOOLEAN("Omnibox.HardwareKeyboardModeEnabled",
-                        isInHarwareKeyboardMode);
-}
-
-@end
diff --git a/ios/chrome/browser/ui/keyboard/hardware_keyboard_watcher_unittest.mm b/ios/chrome/browser/ui/keyboard/hardware_keyboard_watcher_unittest.mm
deleted file mode 100644
index a6a87a1..0000000
--- a/ios/chrome/browser/ui/keyboard/hardware_keyboard_watcher_unittest.mm
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/chrome/browser/ui/keyboard/hardware_keyboard_watcher.h"
-
-#include "base/mac/scoped_nsobject.h"
-#include "base/test/histogram_tester.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "testing/platform_test.h"
-#import "third_party/ocmock/OCMock/OCMock.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-namespace {
-
-void PostKeyboardWillChangeNotification(CGRect beginFrame, CGRect endFrame) {
-  [[NSNotificationCenter defaultCenter]
-      postNotificationName:UIKeyboardWillChangeFrameNotification
-                    object:nil
-                  userInfo:@{
-                    UIKeyboardFrameBeginUserInfoKey :
-                        [NSValue valueWithCGRect:beginFrame],
-                    UIKeyboardFrameEndUserInfoKey :
-                        [NSValue valueWithCGRect:endFrame],
-                  }];
-}
-
-typedef PlatformTest HardwareKeyboardWatcherTest;
-
-TEST_F(HardwareKeyboardWatcherTest, AccessoryViewNotInHierarchy_NoHistogram) {
-  base::HistogramTester histogram_tester;
-  id mockView = [OCMockObject niceMockForClass:[UIView class]];
-  [[mockView stub] andReturn:nil];
-  base::scoped_nsobject<HardwareKeyboardWatcher> watcher(
-      [[HardwareKeyboardWatcher alloc] initWithAccessoryView:mockView]);
-
-  PostKeyboardWillChangeNotification(CGRectZero, CGRectZero);
-  PostKeyboardWillChangeNotification(CGRectInfinite, CGRectInfinite);
-
-  histogram_tester.ExpectTotalCount("Omnibox.HardwareKeyboardModeEnabled", 0);
-}
-
-TEST_F(HardwareKeyboardWatcherTest, EmptyBeginFrame_SoftwareKeyboardHistogram) {
-  base::HistogramTester histogram_tester;
-  id mockView = [OCMockObject niceMockForClass:[UIView class]];
-  [[[mockView stub] andReturn:[[UIApplication sharedApplication] keyWindow]]
-      window];
-  base::scoped_nsobject<HardwareKeyboardWatcher> watcher(
-      [[HardwareKeyboardWatcher alloc] initWithAccessoryView:mockView]);
-
-  PostKeyboardWillChangeNotification(CGRectZero, CGRectInfinite);
-
-  histogram_tester.ExpectUniqueSample("Omnibox.HardwareKeyboardModeEnabled",
-                                      false, 1);
-}
-
-TEST_F(HardwareKeyboardWatcherTest, EmptyEndFrame_SoftwareKeyboardHistogram) {
-  base::HistogramTester histogram_tester;
-  id mockView = [OCMockObject niceMockForClass:[UIView class]];
-  [[[mockView stub] andReturn:[[UIApplication sharedApplication] keyWindow]]
-      window];
-  base::scoped_nsobject<HardwareKeyboardWatcher> watcher(
-      [[HardwareKeyboardWatcher alloc] initWithAccessoryView:mockView]);
-
-  PostKeyboardWillChangeNotification(CGRectInfinite, CGRectZero);
-
-  histogram_tester.ExpectUniqueSample("Omnibox.HardwareKeyboardModeEnabled",
-                                      false, 1);
-}
-
-TEST_F(HardwareKeyboardWatcherTest,
-       KeyboardFullyOnScreen_SoftwareKeyboardHistogram) {
-  base::HistogramTester histogram_tester;
-  id mockView = [OCMockObject niceMockForClass:[UIView class]];
-  [[[mockView stub] andReturn:[[UIApplication sharedApplication] keyWindow]]
-      window];
-  base::scoped_nsobject<HardwareKeyboardWatcher> watcher(
-      [[HardwareKeyboardWatcher alloc] initWithAccessoryView:mockView]);
-
-  PostKeyboardWillChangeNotification(CGRectMake(0, 0, 100, 100),
-                                     CGRectMake(0, 100, 100, 100));
-
-  histogram_tester.ExpectUniqueSample("Omnibox.HardwareKeyboardModeEnabled",
-                                      false, 1);
-}
-
-TEST_F(HardwareKeyboardWatcherTest,
-       KeyboardFullyOffScreen_SoftwareKeyboardHistogram) {
-  base::HistogramTester histogram_tester;
-  id mockView = [OCMockObject niceMockForClass:[UIView class]];
-  [[[mockView stub] andReturn:[[UIApplication sharedApplication] keyWindow]]
-      window];
-  base::scoped_nsobject<HardwareKeyboardWatcher> watcher(
-      [[HardwareKeyboardWatcher alloc] initWithAccessoryView:mockView]);
-
-  PostKeyboardWillChangeNotification(CGRectMake(0, -100, 100, 100),
-                                     CGRectMake(0, 0, 100, 100));
-
-  histogram_tester.ExpectUniqueSample("Omnibox.HardwareKeyboardModeEnabled",
-                                      false, 1);
-}
-
-TEST_F(HardwareKeyboardWatcherTest,
-       KeyboardPartiallyOnScreen_SoftwareKeyboardHistogram) {
-  base::HistogramTester histogram_tester;
-  id mockView = [OCMockObject niceMockForClass:[UIView class]];
-  [[[mockView stub] andReturn:[[UIApplication sharedApplication] keyWindow]]
-      window];
-  base::scoped_nsobject<HardwareKeyboardWatcher> watcher(
-      [[HardwareKeyboardWatcher alloc] initWithAccessoryView:mockView]);
-
-  PostKeyboardWillChangeNotification(CGRectMake(0, -50, 100, 100),
-                                     CGRectMake(0, 0, 100, 100));
-
-  histogram_tester.ExpectUniqueSample("Omnibox.HardwareKeyboardModeEnabled",
-                                      true, 1);
-}
-
-}  // namespace
diff --git a/ios/chrome/browser/ui/omnibox/location_bar_controller_impl.mm b/ios/chrome/browser/ui/omnibox/location_bar_controller_impl.mm
index 73ae2e6..8e4da991 100644
--- a/ios/chrome/browser/ui/omnibox/location_bar_controller_impl.mm
+++ b/ios/chrome/browser/ui/omnibox/location_bar_controller_impl.mm
@@ -193,7 +193,6 @@
     }
   }
   UpdateRightDecorations();
-  [delegate_ locationBarChanged];
 
   NSString* placeholderText =
       show_hint_text_ ? l10n_util::GetNSString(IDS_OMNIBOX_EMPTY_HINT) : nil;
diff --git a/ios/chrome/browser/ui/payments/contact_info_selection_coordinator.mm b/ios/chrome/browser/ui/payments/contact_info_selection_coordinator.mm
index 9e3fab3b..e73228e 100644
--- a/ios/chrome/browser/ui/payments/contact_info_selection_coordinator.mm
+++ b/ios/chrome/browser/ui/payments/contact_info_selection_coordinator.mm
@@ -10,6 +10,8 @@
 #include "components/strings/grit/components_strings.h"
 #include "ios/chrome/browser/payments/payment_request.h"
 #import "ios/chrome/browser/payments/payment_request_util.h"
+#import "ios/chrome/browser/ui/collection_view/cells/collection_view_item.h"
+#import "ios/chrome/browser/ui/payments/cells/payments_is_selectable.h"
 #include "ios/chrome/browser/ui/payments/contact_info_selection_mediator.h"
 #include "ios/chrome/browser/ui/payments/payment_request_selector_view_controller.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -86,13 +88,24 @@
 - (BOOL)paymentRequestSelectorViewController:
             (PaymentRequestSelectorViewController*)controller
                         didSelectItemAtIndex:(NSUInteger)index {
-  // Update the data source with the selection.
-  self.mediator.selectedItemIndex = index;
+  CollectionViewItem<PaymentsIsSelectable>* selectedItem =
+      self.mediator.selectableItems[index];
 
   DCHECK(index < self.paymentRequest->contact_profiles().size());
-  [self delayedNotifyDelegateOfSelection:self.paymentRequest
-                                             ->contact_profiles()[index]];
-  return YES;
+  autofill::AutofillProfile* selectedProfile =
+      self.paymentRequest->contact_profiles()[index];
+
+  // Proceed with item selection only if the item has all required info, or
+  // else bring up the contact info editor.
+  if (selectedItem.complete) {
+    // Update the data source with the selection.
+    self.mediator.selectedItemIndex = index;
+    [self delayedNotifyDelegateOfSelection:selectedProfile];
+    return YES;
+  } else {
+    [self startContactInfoEditCoordinatorWithProfile:selectedProfile];
+    return NO;
+  }
 }
 
 - (void)paymentRequestSelectorViewControllerDidFinish:
@@ -126,6 +139,25 @@
   // Update the data source with the new data.
   [self.mediator loadItems];
 
+  const std::vector<autofill::AutofillProfile*>& contactProfiles =
+      self.paymentRequest->contact_profiles();
+  const auto position =
+      std::find(contactProfiles.begin(), contactProfiles.end(), profile);
+  DCHECK(position != contactProfiles.end());
+
+  const size_t index = position - contactProfiles.begin();
+
+  // Mark the edited item as complete meaning all required information has been
+  // filled out.
+  CollectionViewItem<PaymentsIsSelectable>* editedItem =
+      self.mediator.selectableItems[index];
+  editedItem.complete = YES;
+
+  if (![self.viewController isEditing]) {
+    // Update the data source with the selection.
+    self.mediator.selectedItemIndex = index;
+  }
+
   [self.viewController loadModel];
   [self.viewController.collectionView reloadData];
 
diff --git a/ios/chrome/browser/ui/payments/contact_info_selection_coordinator_unittest.mm b/ios/chrome/browser/ui/payments/contact_info_selection_coordinator_unittest.mm
index ca6627e..9aff5c258 100644
--- a/ios/chrome/browser/ui/payments/contact_info_selection_coordinator_unittest.mm
+++ b/ios/chrome/browser/ui/payments/contact_info_selection_coordinator_unittest.mm
@@ -26,11 +26,16 @@
 #error "This file requires ARC support."
 #endif
 
+namespace {
+const int kCompleteProfileIndex = 0;
+const int kIncompleteProfileIndex = 1;
+}  // namespace
+
 class PaymentRequestContactInfoSelectionCoordinatorTest : public PlatformTest {
  protected:
   PaymentRequestContactInfoSelectionCoordinatorTest()
       : autofill_profile_1_(autofill::test::GetFullProfile()),
-        autofill_profile_2_(autofill::test::GetFullProfile2()),
+        autofill_profile_2_(autofill::test::GetIncompleteProfile2()),
         chrome_browser_state_(TestChromeBrowserState::Builder().Build()) {
     // Add testing profiles to autofill::TestPersonalDataManager.
     personal_data_manager_.AddTestingProfile(&autofill_profile_1_);
@@ -99,9 +104,14 @@
   // Mock the coordinator delegate.
   id delegate = [OCMockObject
       mockForProtocol:@protocol(ContactInfoSelectionCoordinatorDelegate)];
-  autofill::AutofillProfile* profile = payment_request_->contact_profiles()[1];
-  [[delegate expect] contactInfoSelectionCoordinator:coordinator
-                             didSelectContactProfile:profile];
+  [[delegate expect]
+      contactInfoSelectionCoordinator:coordinator
+              didSelectContactProfile:payment_request_->contact_profiles()
+                                          [kCompleteProfileIndex]];
+  [[delegate reject]
+      contactInfoSelectionCoordinator:coordinator
+              didSelectContactProfile:payment_request_->contact_profiles()
+                                          [kIncompleteProfileIndex]];
   [coordinator setDelegate:delegate];
 
   EXPECT_EQ(1u, navigation_controller.viewControllers.count);
@@ -115,11 +125,15 @@
   PaymentRequestSelectorViewController* view_controller =
       base::mac::ObjCCastStrict<PaymentRequestSelectorViewController>(
           navigation_controller.visibleViewController);
-  EXPECT_TRUE([coordinator paymentRequestSelectorViewController:view_controller
-                                           didSelectItemAtIndex:1]);
+  EXPECT_TRUE([coordinator
+      paymentRequestSelectorViewController:view_controller
+                      didSelectItemAtIndex:kCompleteProfileIndex]);
 
   // Wait for the coordinator delegate to be notified.
   base::test::ios::SpinRunLoopWithMinDelay(base::TimeDelta::FromSecondsD(0.5));
+  EXPECT_FALSE([coordinator
+      paymentRequestSelectorViewController:view_controller
+                      didSelectItemAtIndex:kIncompleteProfileIndex]);
 
   EXPECT_OCMOCK_VERIFY(delegate);
 }
diff --git a/ios/chrome/browser/ui/payments/contact_info_selection_mediator.mm b/ios/chrome/browser/ui/payments/contact_info_selection_mediator.mm
index a0394d32..2e30016 100644
--- a/ios/chrome/browser/ui/payments/contact_info_selection_mediator.mm
+++ b/ios/chrome/browser/ui/payments/contact_info_selection_mediator.mm
@@ -8,6 +8,7 @@
 
 #include "base/logging.h"
 #include "components/autofill/core/browser/autofill_profile.h"
+#include "components/payments/core/payments_profile_comparator.h"
 #include "components/strings/grit/components_strings.h"
 #include "ios/chrome/browser/payments/payment_request.h"
 #import "ios/chrome/browser/payments/payment_request_util.h"
@@ -101,6 +102,12 @@
       item.phoneNumber =
           GetPhoneNumberLabelFromAutofillProfile(*contactProfile);
     }
+    item.notification =
+        payment_request_util::GetContactNotificationLabelFromAutofillProfile(
+            *_paymentRequest, *contactProfile);
+    item.complete =
+        _paymentRequest->profile_comparator()->IsContactInfoComplete(
+            contactProfile);
     if (_paymentRequest->selected_contact_profile() == contactProfile)
       _selectedItemIndex = index;
 
diff --git a/ios/chrome/browser/ui/payments/contact_info_selection_mediator_unittest.mm b/ios/chrome/browser/ui/payments/contact_info_selection_mediator_unittest.mm
index 8105cfa..e1c3bc9 100644
--- a/ios/chrome/browser/ui/payments/contact_info_selection_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/payments/contact_info_selection_mediator_unittest.mm
@@ -6,6 +6,7 @@
 
 #include "base/mac/foundation_util.h"
 #include "base/memory/ptr_util.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_task_environment.h"
 #include "components/autofill/core/browser/autofill_profile.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
@@ -28,6 +29,7 @@
 using ::payment_request_util::GetNameLabelFromAutofillProfile;
 using ::payment_request_util::GetEmailLabelFromAutofillProfile;
 using ::payment_request_util::GetPhoneNumberLabelFromAutofillProfile;
+using ::payment_request_util::GetContactNotificationLabelFromAutofillProfile;
 }  // namespace
 
 class PaymentRequestContactInfoSelectionMediatorTest : public PlatformTest {
@@ -35,14 +37,24 @@
   PaymentRequestContactInfoSelectionMediatorTest()
       : autofill_profile_1_(autofill::test::GetFullProfile()),
         autofill_profile_2_(autofill::test::GetFullProfile2()),
+        autofill_profile_3_(autofill::test::GetFullProfile()),
         chrome_browser_state_(TestChromeBrowserState::Builder().Build()) {
+    // Change the name of the third profile (to avoid deduplication), and make
+    // it incomplete by removing the phone number.
+    autofill_profile_3_.SetInfo(autofill::AutofillType(autofill::NAME_FULL),
+                                base::ASCIIToUTF16("Richard Roe"), "en-US");
+    autofill_profile_3_.SetInfo(
+        autofill::AutofillType(autofill::PHONE_HOME_WHOLE_NUMBER),
+        base::string16(), "en-US");
+
     // Add testing profiles to autofill::TestPersonalDataManager.
     personal_data_manager_.AddTestingProfile(&autofill_profile_1_);
     personal_data_manager_.AddTestingProfile(&autofill_profile_2_);
-
+    personal_data_manager_.AddTestingProfile(&autofill_profile_3_);
     payment_request_ = base::MakeUnique<payments::TestPaymentRequest>(
         payment_request_test_util::CreateTestWebPaymentRequest(),
         chrome_browser_state_.get(), &web_state_, &personal_data_manager_);
+
     // Override the selected contact profile.
     payment_request_->set_selected_contact_profile(
         payment_request_->contact_profiles()[1]);
@@ -61,6 +73,7 @@
 
   autofill::AutofillProfile autofill_profile_1_;
   autofill::AutofillProfile autofill_profile_2_;
+  autofill::AutofillProfile autofill_profile_3_;
   web::TestWebState web_state_;
   autofill::TestPersonalDataManager personal_data_manager_;
   std::unique_ptr<TestChromeBrowserState> chrome_browser_state_;
@@ -73,8 +86,8 @@
   NSArray<CollectionViewItem*>* selectable_items =
       [GetMediator() selectableItems];
 
-  // There must be two selectable items.
-  ASSERT_EQ(2U, selectable_items.count);
+  // There must be three selectable items.
+  ASSERT_EQ(3U, selectable_items.count);
 
   // The second item must be selected.
   EXPECT_EQ(1U, GetMediator().selectedItemIndex);
@@ -110,6 +123,23 @@
                           *payment_request_->contact_profiles()[1])]);
   EXPECT_EQ(nil, profile_item_2.address);
   EXPECT_EQ(nil, profile_item_2.notification);
+
+  CollectionViewItem* item_3 = [selectable_items objectAtIndex:2];
+  DCHECK([item_3 isKindOfClass:[AutofillProfileItem class]]);
+  AutofillProfileItem* profile_item_3 =
+      base::mac::ObjCCastStrict<AutofillProfileItem>(item_3);
+  EXPECT_TRUE([profile_item_3.name
+      isEqualToString:GetNameLabelFromAutofillProfile(
+                          *payment_request_->contact_profiles()[2])]);
+  EXPECT_TRUE([profile_item_3.email
+      isEqualToString:GetEmailLabelFromAutofillProfile(
+                          *payment_request_->contact_profiles()[2])]);
+  EXPECT_EQ(nil, profile_item_3.phoneNumber);
+  EXPECT_EQ(nil, profile_item_3.address);
+  EXPECT_TRUE([profile_item_3.notification
+      isEqualToString:GetContactNotificationLabelFromAutofillProfile(
+                          *payment_request_.get(),
+                          *payment_request_->contact_profiles()[2])]);
 }
 
 // Tests that the index of the selected item is as expected when there is no
@@ -123,8 +153,8 @@
   NSArray<CollectionViewItem*>* selectable_items =
       [GetMediator() selectableItems];
 
-  // There must be two selectable items.
-  ASSERT_EQ(2U, selectable_items.count);
+  // There must be three selectable items.
+  ASSERT_EQ(3U, selectable_items.count);
 
   // The selected item index must be invalid.
   EXPECT_EQ(NSUIntegerMax, GetMediator().selectedItemIndex);
@@ -149,6 +179,14 @@
   EXPECT_EQ(nil, profile_item.address);
   EXPECT_EQ(nil, profile_item.notification);
 
+  // Incomplete item should display a notification since the phone number is
+  // missing.
+  CollectionViewItem* incomplete_item = [selectable_items objectAtIndex:2];
+  DCHECK([incomplete_item isKindOfClass:[AutofillProfileItem class]]);
+  AutofillProfileItem* incomplete_profile_item =
+      base::mac::ObjCCastStrict<AutofillProfileItem>(incomplete_item);
+  EXPECT_NE(nil, incomplete_profile_item.notification);
+
   // Update the web_payment_request and reload the items.
   payment_request_->web_payment_request().options.request_payer_email = false;
   [GetMediator() loadItems];
@@ -180,4 +218,12 @@
   EXPECT_EQ(nil, profile_item.phoneNumber);
   EXPECT_EQ(nil, profile_item.address);
   EXPECT_EQ(nil, profile_item.notification);
+
+  // Incomplete item should not display a notification, since the phone number
+  // is not requested.
+  incomplete_item = [selectable_items objectAtIndex:2];
+  DCHECK([incomplete_item isKindOfClass:[AutofillProfileItem class]]);
+  profile_item =
+      base::mac::ObjCCastStrict<AutofillProfileItem>(incomplete_item);
+  EXPECT_EQ(nil, profile_item.notification);
 }
diff --git a/ios/chrome/browser/ui/settings/passwords_settings_egtest.mm b/ios/chrome/browser/ui/settings/passwords_settings_egtest.mm
index 555818db..64c58c9 100644
--- a/ios/chrome/browser/ui/settings/passwords_settings_egtest.mm
+++ b/ios/chrome/browser/ui/settings/passwords_settings_egtest.mm
@@ -51,6 +51,7 @@
 using chrome_test_util::ButtonWithAccessibilityLabelId;
 using chrome_test_util::NavigationBarDoneButton;
 using chrome_test_util::SettingsMenuButton;
+using chrome_test_util::SettingsMenuBackButton;
 
 namespace {
 
@@ -365,16 +366,6 @@
       performAction:grey_tap()];
 }
 
-// Tap back arrow, to get one level higher in settings.
-- (void)tapBackArrow {
-  [[EarlGrey
-      selectElementWithMatcher:grey_allOf(
-                                   grey_accessibilityID(@"ic_arrow_back"),
-                                   grey_accessibilityTrait(
-                                       UIAccessibilityTraitButton),
-                                   nil)] performAction:grey_tap()];
-}
-
 // Tap Edit in any settings view.
 - (void)tapEdit {
   [[EarlGrey selectElementWithMatcher:EditButton()] performAction:grey_tap()];
@@ -409,9 +400,11 @@
       selectElementWithMatcher:Entry(@"https://example.com, concrete username")]
       performAction:grey_tap()];
   chrome_test_util::VerifyAccessibilityForCurrentScreen();
-  [self tapBackArrow];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
 
-  [self tapBackArrow];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
   [self tapDone];
   [self clearPasswordStore];
 }
@@ -463,8 +456,10 @@
   [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(snackbarLabel)]
       performAction:grey_tap()];
 
-  [self tapBackArrow];
-  [self tapBackArrow];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
   [self tapDone];
   [self clearPasswordStore];
 }
@@ -496,8 +491,10 @@
   [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(snackbarLabel)]
       performAction:grey_tap()];
 
-  [self tapBackArrow];
-  [self tapBackArrow];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
   [self tapDone];
   [self clearPasswordStore];
 }
@@ -529,8 +526,10 @@
   [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(snackbarLabel)]
       performAction:grey_tap()];
 
-  [self tapBackArrow];
-  [self tapBackArrow];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
   [self tapDone];
   [self clearPasswordStore];
 }
@@ -580,7 +579,8 @@
       selectElementWithMatcher:Entry(@"https://example.com, concrete username")]
       assertWithMatcher:grey_not(grey_sufficientlyVisible())];
 
-  [self tapBackArrow];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
   [self tapDone];
   [self clearPasswordStore];
 }
@@ -623,12 +623,14 @@
 
   // Go back to the list view and verify that the password is still in the
   // list.
-  [self tapBackArrow];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
   [[EarlGrey
       selectElementWithMatcher:Entry(@"https://example.com, concrete username")]
       assertWithMatcher:grey_sufficientlyVisible()];
 
-  [self tapBackArrow];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
   [self tapDone];
   [self clearPasswordStore];
 }
@@ -654,7 +656,8 @@
   [[EarlGrey selectElementWithMatcher:CopyPasswordButton()]
       assertWithMatcher:grey_nil()];
 
-  [self tapBackArrow];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
   [self tapDone];
   [self clearPasswordStore];
 }
@@ -691,8 +694,10 @@
   [[EarlGrey selectElementWithMatcher:PasswordHeader()]
       assertWithMatcher:grey_nil()];
 
-  [self tapBackArrow];
-  [self tapBackArrow];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
   [self tapDone];
   [self clearPasswordStore];
 }
@@ -732,8 +737,10 @@
   [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(snackbarLabel)]
       performAction:grey_tap()];
 
-  [self tapBackArrow];
-  [self tapBackArrow];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
   [self tapDone];
   [self clearPasswordStore];
 }
@@ -774,8 +781,10 @@
   [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(snackbarLabel)]
       performAction:grey_tap()];
 
-  [self tapBackArrow];
-  [self tapBackArrow];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
   [self tapDone];
   [self clearPasswordStore];
 }
@@ -823,8 +832,10 @@
   [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(snackbarLabel)]
       performAction:grey_tap()];
 
-  [self tapBackArrow];
-  [self tapBackArrow];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
   [self tapDone];
   [self clearPasswordStore];
 }
@@ -890,8 +901,10 @@
                                @"PasswordDetailsCollectionViewController")]
       assertWithMatcher:grey_sufficientlyVisible()];
 
-  [self tapBackArrow];
-  [self tapBackArrow];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
   [self tapDone];
   [self clearPasswordStore];
 }
@@ -956,8 +969,10 @@
   [[EarlGrey selectElementWithMatcher:PasswordHeader()]
       assertWithMatcher:grey_nil()];
 
-  [self tapBackArrow];
-  [self tapBackArrow];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+      performAction:grey_tap()];
   [self tapDone];
   [self clearPasswordStore];
 }
diff --git a/ios/chrome/browser/ui/settings/save_passwords_collection_view_controller.mm b/ios/chrome/browser/ui/settings/save_passwords_collection_view_controller.mm
index b38d68c..d34eb1c 100644
--- a/ios/chrome/browser/ui/settings/save_passwords_collection_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/save_passwords_collection_view_controller.mm
@@ -22,6 +22,7 @@
 #include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/prefs/pref_member.h"
 #include "components/prefs/pref_service.h"
+#include "components/strings/grit/components_strings.h"
 #include "components/url_formatter/url_formatter.h"
 #include "ios/chrome/browser/application_context.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
@@ -208,7 +209,7 @@
       CollectionViewTextItem* headerItem =
           [[CollectionViewTextItem alloc] initWithType:ItemTypeHeader];
       headerItem.text =
-          l10n_util::GetNSString(IDS_IOS_SETTINGS_PASSWORDS_SAVED_HEADING);
+          l10n_util::GetNSString(IDS_PASSWORD_MANAGER_SHOW_PASSWORDS_TAB_TITLE);
       headerItem.textColor = [[MDCPalette greyPalette] tint500];
       [model setHeader:headerItem
           forSectionWithIdentifier:SectionIdentifierSavedPasswords];
@@ -222,7 +223,7 @@
       CollectionViewTextItem* headerItem =
           [[CollectionViewTextItem alloc] initWithType:ItemTypeHeader];
       headerItem.text =
-          l10n_util::GetNSString(IDS_IOS_SETTINGS_PASSWORDS_EXCEPTIONS_HEADING);
+          l10n_util::GetNSString(IDS_PASSWORD_MANAGER_EXCEPTIONS_TAB_TITLE);
       headerItem.textColor = [[MDCPalette greyPalette] tint500];
       [model setHeader:headerItem
           forSectionWithIdentifier:SectionIdentifierBlacklist];
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 318c8c49..2eb11dac 100644
--- a/ios/chrome/browser/ui/settings/settings_collection_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/settings_collection_view_controller.mm
@@ -292,14 +292,6 @@
 
 - (void)dealloc {
   [self stopBrowserStateServiceObservers];
-  if (!_signinStarted && _signinPromoViewMediator) {
-    PrefService* prefs = _browserState->GetPrefs();
-    int displayedCount =
-        prefs->GetInteger(prefs::kIosSettingsSigninPromoDisplayedCount);
-    UMA_HISTOGRAM_COUNTS_100(
-        "MobileSignInPromo.SettingsManager.ImpressionsTilDismiss",
-        displayedCount);
-  }
 }
 
 - (void)stopBrowserStateServiceObservers {
@@ -1034,6 +1026,14 @@
 #pragma mark SettingsControllerProtocol
 
 - (void)settingsWillBeDismissed {
+  if (!_signinStarted && _signinPromoViewMediator) {
+    PrefService* prefs = _browserState->GetPrefs();
+    int displayedCount =
+        prefs->GetInteger(prefs::kIosSettingsSigninPromoDisplayedCount);
+    UMA_HISTOGRAM_COUNTS_100(
+        "MobileSignInPromo.SettingsManager.ImpressionsTilDismiss",
+        displayedCount);
+  }
   [_signinInteractionController cancel];
   [self stopBrowserStateServiceObservers];
 }
diff --git a/ios/chrome/browser/ui/toolbar/BUILD.gn b/ios/chrome/browser/ui/toolbar/BUILD.gn
index 972beb4..f9b3f80 100644
--- a/ios/chrome/browser/ui/toolbar/BUILD.gn
+++ b/ios/chrome/browser/ui/toolbar/BUILD.gn
@@ -5,9 +5,7 @@
 source_set("toolbar") {
   configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
-    "keyboard_accessory_view.h",
-    "keyboard_accessory_view.mm",
-    "keyboard_accessory_view_protocol.h",
+    "keyboard_accessory_view_delegate.h",
     "new_keyboard_accessory_view.h",
     "new_keyboard_accessory_view.mm",
     "new_tab_button.h",
diff --git a/ios/chrome/browser/ui/toolbar/keyboard_accessory_view.h b/ios/chrome/browser/ui/toolbar/keyboard_accessory_view.h
deleted file mode 100644
index d6510d1..0000000
--- a/ios/chrome/browser/ui/toolbar/keyboard_accessory_view.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ACCESSORY_VIEW_H_
-#define IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ACCESSORY_VIEW_H_
-
-#import <UIKit/UIKIt.h>
-
-#import "ios/chrome/browser/ui/toolbar/keyboard_accessory_view_protocol.h"
-
-// Accessory View above the keyboard.
-// Supports two modes: one where a Voice Search button is shown, and one where
-// a list of buttons based on |buttonTitles| is shown.
-// The default mode is the Voice Search mode.
-@interface KeyboardAccessoryView
-    : UIInputView<KeyboardAccessoryViewProtocol, UIInputViewAudioFeedback>
-
-// Designated initializer. |buttonTitles| lists the titles of the buttons shown
-// in the KEY_SHORTCUTS mode. Can be nil or empty. |delegate| receives the
-// various events triggered in the view. Not retained, and can be nil.
-- (instancetype)initWithButtons:(NSArray<NSString*>*)buttonTitles
-                       delegate:(id<KeyboardAccessoryViewDelegate>)delegate
-    NS_DESIGNATED_INITIALIZER;
-
-- (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE;
-
-- (instancetype)initWithFrame:(CGRect)frame
-               inputViewStyle:(UIInputViewStyle)inputViewStyle NS_UNAVAILABLE;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ACCESSORY_VIEW_H_
diff --git a/ios/chrome/browser/ui/toolbar/keyboard_accessory_view.mm b/ios/chrome/browser/ui/toolbar/keyboard_accessory_view.mm
deleted file mode 100644
index 23221892..0000000
--- a/ios/chrome/browser/ui/toolbar/keyboard_accessory_view.mm
+++ /dev/null
@@ -1,165 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/chrome/browser/ui/toolbar/keyboard_accessory_view.h"
-
-#include "base/mac/foundation_util.h"
-#include "ios/chrome/browser/ui/commands/ios_command_ids.h"
-#include "ios/chrome/browser/ui/ui_util.h"
-#import "ios/chrome/browser/ui/uikit_ui_util.h"
-#include "ios/chrome/grit/ios_strings.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@interface KeyboardAccessoryView ()
-
-@property(nonatomic, weak) id<KeyboardAccessoryViewDelegate> delegate;
-@property(nonatomic, retain) UIButton* voiceSearchButton;
-
-// Creates a button with the same appearance as a keyboard key.
-- (UIView*)keyboardButtonWithTitle:(NSString*)title frame:(CGRect)frame;
-// Called when a keyboard shortcut button is pressed.
-- (void)keyboardButtonPressed:(NSString*)title;
-
-@end
-
-@implementation KeyboardAccessoryView
-
-@synthesize mode = _mode;
-@synthesize delegate = _delegate;
-@synthesize voiceSearchButton = _voiceSearchButton;
-
-- (instancetype)initWithButtons:(NSArray<NSString*>*)buttonTitles
-                       delegate:(id<KeyboardAccessoryViewDelegate>)delegate {
-  const CGFloat kViewHeight = 70.0;
-  const CGFloat kViewHeightCompact = 43.0;
-  const CGFloat kButtonInset = 5.0;
-  const CGFloat kButtonSizeX = 61.0;
-  const CGFloat kButtonSizeXCompact = 46.0;
-  const CGFloat kButtonSizeY = 62.0;
-  const CGFloat kButtonSizeYCompact = 35.0;
-  const CGFloat kBetweenButtonSpacing = 15.0;
-  const CGFloat kBetweenButtonSpacingCompact = 7.0;
-  const BOOL isCompact = IsCompact();
-
-  CGFloat width = [[UIScreen mainScreen] bounds].size.width;
-  CGFloat height = isCompact ? kViewHeightCompact : kViewHeight;
-  CGRect frame = CGRectMake(0.0, 0.0, width, height);
-
-  self = [super initWithFrame:frame inputViewStyle:UIInputViewStyleKeyboard];
-  if (self) {
-    _delegate = delegate;
-
-    // Center buttons in available space by placing them within a parent view
-    // that auto-centers.
-    CGFloat betweenButtonSpacing =
-        isCompact ? kBetweenButtonSpacingCompact : kBetweenButtonSpacing;
-    const CGFloat buttonWidth = isCompact ? kButtonSizeXCompact : kButtonSizeX;
-
-    CGFloat totalWidth = (buttonTitles.count * buttonWidth) +
-                         ((buttonTitles.count - 1) * betweenButtonSpacing);
-    CGFloat indent = floor((width - totalWidth) / 2.0);
-    if (indent < kButtonInset)
-      indent = kButtonInset;
-    CGRect parentViewRect = CGRectMake(indent, 0.0, totalWidth, height);
-    UIView* parentView = [[UIView alloc] initWithFrame:parentViewRect];
-    [parentView setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin |
-                                    UIViewAutoresizingFlexibleRightMargin];
-    [self addSubview:parentView];
-
-    // Create the shortcut buttons, starting at the left edge of |parentView|.
-    CGRect currentFrame =
-        CGRectMake(0.0, kButtonInset, buttonWidth,
-                   isCompact ? kButtonSizeYCompact : kButtonSizeY);
-
-    for (NSString* title in buttonTitles) {
-      UIView* button = [self keyboardButtonWithTitle:title frame:currentFrame];
-      [parentView addSubview:button];
-      currentFrame.origin.x =
-          CGRectGetMaxX(currentFrame) + betweenButtonSpacing;
-    }
-
-    // Create the voice search button and add it over the text buttons.
-    _voiceSearchButton = [UIButton buttonWithType:UIButtonTypeCustom];
-    [_voiceSearchButton setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
-    SetA11yLabelAndUiAutomationName(
-        _voiceSearchButton, IDS_IOS_ACCNAME_VOICE_SEARCH, @"Voice Search");
-    UIImage* voiceRow = [UIImage imageNamed:@"custom_row_voice"];
-    UIImage* voiceRowPressed = [UIImage imageNamed:@"custom_row_voice_pressed"];
-    [_voiceSearchButton setBackgroundImage:voiceRow
-                                  forState:UIControlStateNormal];
-    [_voiceSearchButton setBackgroundImage:voiceRowPressed
-                                  forState:UIControlStateHighlighted];
-
-    UIImage* voiceIcon = [UIImage imageNamed:@"voice_icon_keyboard_accessory"];
-    [_voiceSearchButton setAdjustsImageWhenHighlighted:NO];
-    [_voiceSearchButton setImage:voiceIcon forState:UIControlStateNormal];
-    [_voiceSearchButton setFrame:[self bounds]];
-
-    [_voiceSearchButton
-               addTarget:delegate
-                  action:@selector(keyboardAccessoryVoiceSearchTouchDown)
-        forControlEvents:UIControlEventTouchDown];
-    [_voiceSearchButton
-               addTarget:delegate
-                  action:@selector(keyboardAccessoryVoiceSearchTouchUpInside)
-        forControlEvents:UIControlEventTouchUpInside];
-    [self addSubview:_voiceSearchButton];
-  }
-
-  return self;
-}
-
-- (void)setMode:(KeyboardAccessoryViewMode)mode {
-  _mode = mode;
-  switch (mode) {
-    case VOICE_SEARCH:
-      [_voiceSearchButton setHidden:NO];
-      break;
-    case KEY_SHORTCUTS:
-      [_voiceSearchButton setHidden:YES];
-      break;
-  }
-}
-
-- (UIView*)keyboardButtonWithTitle:(NSString*)title frame:(CGRect)frame {
-  const CGFloat kIpadButtonTitleFontSize = 20.0;
-  const CGFloat kIphoneButtonTitleFontSize = 15.0;
-
-  UIButton* button = [UIButton buttonWithType:UIButtonTypeCustom];
-  UIFont* font = nil;
-  UIImage* backgroundImage = nil;
-  if (IsIPadIdiom()) {
-    font = GetUIFont(FONT_HELVETICA, false, kIpadButtonTitleFontSize);
-  } else {
-    font = GetUIFont(FONT_HELVETICA, true, kIphoneButtonTitleFontSize);
-  }
-  backgroundImage = [UIImage imageNamed:@"keyboard_button"];
-
-  button.frame = frame;
-  [button setTitle:title forState:UIControlStateNormal];
-  [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
-  [button.titleLabel setFont:font];
-  [button setBackgroundImage:backgroundImage forState:UIControlStateNormal];
-  [button addTarget:self
-                action:@selector(keyboardButtonPressed:)
-      forControlEvents:UIControlEventTouchUpInside];
-  button.isAccessibilityElement = YES;
-  [button setAccessibilityLabel:title];
-  return button;
-}
-
-- (BOOL)enableInputClicksWhenVisible {
-  return YES;
-}
-
-- (void)keyboardButtonPressed:(id)sender {
-  UIButton* button = base::mac::ObjCCastStrict<UIButton>(sender);
-  [[UIDevice currentDevice] playInputClick];
-  [_delegate keyPressed:[button currentTitle]];
-}
-
-@end
diff --git a/ios/chrome/browser/ui/toolbar/keyboard_accessory_view_delegate.h b/ios/chrome/browser/ui/toolbar/keyboard_accessory_view_delegate.h
new file mode 100644
index 0000000..426be79
--- /dev/null
+++ b/ios/chrome/browser/ui/toolbar/keyboard_accessory_view_delegate.h
@@ -0,0 +1,28 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ACCESSORY_VIEW_DELEGATE_H_
+#define IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ACCESSORY_VIEW_DELEGATE_H_
+
+#import <UIKit/UIKIt.h>
+
+// Delegate protocol for the KeyboardAccessoryView.
+@protocol KeyboardAccessoryViewDelegate
+
+// Notifies the delegate that the Voice Search button was pressed.
+- (void)keyboardAccessoryVoiceSearchTouchDown:(UIView*)view;
+
+// Notifies the delegate that a touch up occured in the the Voice Search button.
+- (void)keyboardAccessoryVoiceSearchTouchUpInside:(UIView*)view;
+
+// Notifies the delegate that a touch up occured in the the Camera Search
+// button.
+- (void)keyboardAccessoryCameraSearchTouchUpInside:(UIView*)view;
+
+// Notifies the delegate that a key with the title |title| was pressed.
+- (void)keyPressed:(NSString*)title;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ACCESSORY_VIEW_DELEGATE_H_
diff --git a/ios/chrome/browser/ui/toolbar/keyboard_accessory_view_protocol.h b/ios/chrome/browser/ui/toolbar/keyboard_accessory_view_protocol.h
deleted file mode 100644
index cca0c48b..0000000
--- a/ios/chrome/browser/ui/toolbar/keyboard_accessory_view_protocol.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ACCESSORY_VIEW_PROTOCOL_H_
-#define IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ACCESSORY_VIEW_PROTOCOL_H_
-
-#import <UIKit/UIKIt.h>
-
-// Delegate protocol for the KeyboardAccessoryView.
-@protocol KeyboardAccessoryViewDelegate
-
-// Notifies the delegate that the Voice Search button was pressed.
-- (void)keyboardAccessoryVoiceSearchTouchDown;
-
-// Notifies the delegate that a touch up occured in the the Voice Search button.
-- (void)keyboardAccessoryVoiceSearchTouchUpInside;
-
-// Notifies the delegate that a touch up occured in the the Camera Search
-// button.
-- (void)keyboardAccessoryCameraSearchTouchUpInside;
-
-// Notifies the delegate that a key with the title |title| was pressed.
-- (void)keyPressed:(NSString*)title;
-
-@end
-
-typedef NS_ENUM(NSInteger, KeyboardAccessoryViewMode) {
-  VOICE_SEARCH = 0,
-  KEY_SHORTCUTS
-};
-
-// Protocol the keyboard accessory views must implement.
-@protocol KeyboardAccessoryViewProtocol
-
-// The mode in which the KeyboardAccessoryView is in.
-@property(nonatomic) KeyboardAccessoryViewMode mode;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ACCESSORY_VIEW_PROTOCOL_H_
diff --git a/ios/chrome/browser/ui/toolbar/new_keyboard_accessory_view.h b/ios/chrome/browser/ui/toolbar/new_keyboard_accessory_view.h
index 136bda0..242b6ff 100644
--- a/ios/chrome/browser/ui/toolbar/new_keyboard_accessory_view.h
+++ b/ios/chrome/browser/ui/toolbar/new_keyboard_accessory_view.h
@@ -7,13 +7,12 @@
 
 #import <UIKit/UIKIt.h>
 
-#import "ios/chrome/browser/ui/toolbar/keyboard_accessory_view_protocol.h"
+#import "ios/chrome/browser/ui/toolbar/keyboard_accessory_view_delegate.h"
 
 // Accessory View above the keyboard.
 // Shows keys that are shortcuts to commonly used characters or strings,
 // and buttons to start Voice Search or a Camera Search.
-@interface NewKeyboardAccessoryView
-    : UIInputView<KeyboardAccessoryViewProtocol, UIInputViewAudioFeedback>
+@interface NewKeyboardAccessoryView : UIInputView<UIInputViewAudioFeedback>
 
 // Designated initializer. |buttonTitles| lists the titles of the shortcut
 // buttons. |delegate| receives the various events triggered in the view. Not
diff --git a/ios/chrome/browser/ui/toolbar/new_keyboard_accessory_view.mm b/ios/chrome/browser/ui/toolbar/new_keyboard_accessory_view.mm
index a87cfa0..8e5a007d 100644
--- a/ios/chrome/browser/ui/toolbar/new_keyboard_accessory_view.mm
+++ b/ios/chrome/browser/ui/toolbar/new_keyboard_accessory_view.mm
@@ -34,9 +34,6 @@
 
 @implementation NewKeyboardAccessoryView
 
-// Unused by this implementation of |KeyboardAccessoryViewProtocol|.
-@synthesize mode = _mode;
-
 @synthesize buttonTitles = _buttonTitles;
 @synthesize delegate = _delegate;
 
@@ -84,18 +81,18 @@
   UIButton* voiceSearchButton =
       [self iconButton:@"keyboard_accessory_voice_search"];
   [voiceSearchButton addTarget:_delegate
-                        action:@selector(keyboardAccessoryVoiceSearchTouchDown)
+                        action:@selector(keyboardAccessoryVoiceSearchTouchDown:)
               forControlEvents:UIControlEventTouchDown];
   SetA11yLabelAndUiAutomationName(voiceSearchButton,
                                   IDS_IOS_KEYBOARD_ACCESSORY_VIEW_VOICE_SEARCH,
                                   @"Voice Search");
   [voiceSearchButton
              addTarget:_delegate
-                action:@selector(keyboardAccessoryVoiceSearchTouchUpInside)
+                action:@selector(keyboardAccessoryVoiceSearchTouchUpInside:)
       forControlEvents:UIControlEventTouchUpInside];
   UIButton* cameraButton = [self iconButton:@"keyboard_accessory_qr_scanner"];
   [cameraButton addTarget:_delegate
-                   action:@selector(keyboardAccessoryCameraSearchTouchUpInside)
+                   action:@selector(keyboardAccessoryCameraSearchTouchUpInside:)
          forControlEvents:UIControlEventTouchUpInside];
   SetA11yLabelAndUiAutomationName(
       cameraButton, IDS_IOS_KEYBOARD_ACCESSORY_VIEW_QR_CODE_SEARCH,
diff --git a/ios/chrome/browser/ui/toolbar/web_toolbar_controller.mm b/ios/chrome/browser/ui/toolbar/web_toolbar_controller.mm
index df0c80ef..b7debf0 100644
--- a/ios/chrome/browser/ui/toolbar/web_toolbar_controller.mm
+++ b/ios/chrome/browser/ui/toolbar/web_toolbar_controller.mm
@@ -42,14 +42,12 @@
 #include "ios/chrome/browser/ui/commands/ios_command_ids.h"
 #import "ios/chrome/browser/ui/history/tab_history_popup_controller.h"
 #import "ios/chrome/browser/ui/image_util.h"
-#import "ios/chrome/browser/ui/keyboard/hardware_keyboard_watcher.h"
 #include "ios/chrome/browser/ui/omnibox/location_bar_controller_impl.h"
 #include "ios/chrome/browser/ui/omnibox/omnibox_view_ios.h"
 #import "ios/chrome/browser/ui/popup_menu/popup_menu_view.h"
 #import "ios/chrome/browser/ui/reversed_animation.h"
 #include "ios/chrome/browser/ui/rtl_geometry.h"
-#import "ios/chrome/browser/ui/toolbar/keyboard_accessory_view.h"
-#import "ios/chrome/browser/ui/toolbar/keyboard_accessory_view_protocol.h"
+#import "ios/chrome/browser/ui/toolbar/keyboard_accessory_view_delegate.h"
 #import "ios/chrome/browser/ui/toolbar/new_keyboard_accessory_view.h"
 #import "ios/chrome/browser/ui/toolbar/toolbar_controller+protected.h"
 #import "ios/chrome/browser/ui/toolbar/toolbar_controller.h"
@@ -246,7 +244,6 @@
   UIButton* _voiceSearchButton;
   OmniboxTextFieldIOS* _omniBox;
   UIButton* _cancelButton;
-  UIView<KeyboardAccessoryViewProtocol>* _keyboardAccessoryView;
   // Progress bar used to show what fraction of the page has loaded.
   MDCProgressView* _determinateProgressView;
   UIImageView* _omniboxBackground;
@@ -286,10 +283,6 @@
   // back or forward button. nil if not visible.
   TabHistoryPopupController* _tabHistoryPopupController;
 
-  // Hardware keyboard watcher, to detect the type of keyboard currently
-  // attached.
-  HardwareKeyboardWatcher* _hardwareKeyboardWatcher;
-
   // The current browser state.
   ios::ChromeBrowserState* _browserState;  // weak
 }
@@ -901,11 +894,6 @@
     [_stopButton setHidden:isCompactTabletView];
     [self updateToolbarState];
 
-    // Update keyboard accessory views.
-    auto mode = _keyboardAccessoryView.mode;
-    _keyboardAccessoryView = nil;
-    [self configureAssistiveKeyboardViews];
-    _keyboardAccessoryView.mode = mode;
     if ([_omniBox isFirstResponder]) {
       [_omniBox reloadInputViews];
     }
@@ -1288,8 +1276,6 @@
   [self.delegate locationBarDidBecomeFirstResponder:self];
   [self animateMaterialOmnibox];
 
-  _keyboardAccessoryView.mode = VOICE_SEARCH;
-
   // Record the appropriate user action for focusing the omnibox.
   web::WebState* webState = [self.delegate currentWebState];
   if (webState) {
@@ -1318,24 +1304,6 @@
   [self.delegate locationBarBeganEdit:self];
 }
 
-- (void)locationBarChanged {
-  // Hide the voice search button once the user starts editing the omnibox but
-  // show it if the omnibox is empty.
-  bool isEditingOrEmpty = _locationBar->GetLocationEntry()->IsEditingOrEmpty();
-  BOOL editingAndNotEmpty = isEditingOrEmpty && _omniBox.text.length != 0;
-  // If the voice search button is visible but about to be hidden (i.e.
-  // the omnibox is no longer empty) then this is the first omnibox text so
-  // record a user action.
-  if (_keyboardAccessoryView.mode == VOICE_SEARCH && editingAndNotEmpty) {
-    base::RecordAction(UserMetricsAction("MobileFirstTextInOmnibox"));
-  }
-  if (editingAndNotEmpty) {
-    _keyboardAccessoryView.mode = KEY_SHORTCUTS;
-  } else {
-    _keyboardAccessoryView.mode = VOICE_SEARCH;
-  }
-}
-
 - (web::WebState*)getWebState {
   return [self.delegate currentWebState];
 }
@@ -1487,31 +1455,29 @@
 #pragma mark -
 #pragma mark KeyboardAccessoryViewDelegate
 
-- (void)keyboardAccessoryVoiceSearchTouchDown {
+- (void)keyboardAccessoryVoiceSearchTouchDown:(UIView*)view {
   if (ios::GetChromeBrowserProvider()
           ->GetVoiceSearchProvider()
           ->IsVoiceSearchEnabled()) {
-    [self preloadVoiceSearch:_keyboardAccessoryView];
+    [self preloadVoiceSearch:view];
   }
 }
 
-- (void)keyboardAccessoryVoiceSearchTouchUpInside {
+- (void)keyboardAccessoryVoiceSearchTouchUpInside:(UIView*)view {
   if (ios::GetChromeBrowserProvider()
           ->GetVoiceSearchProvider()
           ->IsVoiceSearchEnabled()) {
     base::RecordAction(UserMetricsAction("MobileCustomRowVoiceSearch"));
     GenericChromeCommand* command =
         [[GenericChromeCommand alloc] initWithTag:IDC_VOICE_SEARCH];
-    [_keyboardAccessoryView chromeExecuteCommand:command];
-  } else {
-    _keyboardAccessoryView.mode = KEY_SHORTCUTS;
+    [view chromeExecuteCommand:command];
   }
 }
 
-- (void)keyboardAccessoryCameraSearchTouchUpInside {
+- (void)keyboardAccessoryCameraSearchTouchUpInside:(UIView*)view {
   GenericChromeCommand* command =
       [[GenericChromeCommand alloc] initWithTag:IDC_SHOW_QR_SCANNER];
-  [_keyboardAccessoryView chromeExecuteCommand:command];
+  [view chromeExecuteCommand:command];
 }
 
 - (void)keyPressed:(NSString*)title {
@@ -1875,37 +1841,19 @@
 }
 
 - (UIView*)keyboardAccessoryView {
-  if (!_keyboardAccessoryView) {
-    if (experimental_flags::IsKeyboardAccessoryViewWithCameraSearchEnabled()) {
-      // The '.' shortcut is left out because the new keyboard accessory view
-      // has less free space for the shortcut buttons, and the '.' is already
-      // present in the standard iOS keyboard.
-      NSArray<NSString*>* buttonTitles = @[ @":", @"-", @"/", kDotComTLD ];
-      _keyboardAccessoryView =
-          [[NewKeyboardAccessoryView alloc] initWithButtons:buttonTitles
-                                                   delegate:self];
-    } else {
-      NSArray<NSString*>* buttonTitles =
-          @[ @":", @".", @"-", @"/", kDotComTLD ];
-      _keyboardAccessoryView =
-          [[KeyboardAccessoryView alloc] initWithButtons:buttonTitles
-                                                delegate:self];
-    }
-    [_keyboardAccessoryView
-        setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
-    _hardwareKeyboardWatcher = [[HardwareKeyboardWatcher alloc]
-        initWithAccessoryView:_keyboardAccessoryView];
-  }
-  return _keyboardAccessoryView;
+  NSArray<NSString*>* buttonTitles = @[ @":", @"-", @"/", kDotComTLD ];
+  UIView* keyboardAccessoryView =
+      [[NewKeyboardAccessoryView alloc] initWithButtons:buttonTitles
+                                               delegate:self];
+  [keyboardAccessoryView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
+  return keyboardAccessoryView;
 }
 
 - (void)configureAssistiveKeyboardViews {
-  if (experimental_flags::IsKeyboardAccessoryViewWithCameraSearchEnabled()) {
-    // The InputAssistantItems are disabled when the new Keyboard Accessory View
-    // is enabled.
-    _omniBox.inputAssistantItem.leadingBarButtonGroups = @[];
-    _omniBox.inputAssistantItem.trailingBarButtonGroups = @[];
-  }
+  // The InputAssistantItems are disabled when the new Keyboard Accessory View
+  // is enabled.
+  _omniBox.inputAssistantItem.leadingBarButtonGroups = @[];
+  _omniBox.inputAssistantItem.trailingBarButtonGroups = @[];
   [_omniBox setInputAccessoryView:[self keyboardAccessoryView]];
 }
 
diff --git a/ios/chrome/browser/web/chrome_web_client.h b/ios/chrome/browser/web/chrome_web_client.h
index 7a34dbd..e0105d46 100644
--- a/ios/chrome/browser/web/chrome_web_client.h
+++ b/ios/chrome/browser/web/chrome_web_client.h
@@ -44,8 +44,6 @@
       const GURL& request_url,
       bool overridable,
       const base::Callback<void(bool)>& callback) override;
-  std::unique_ptr<base::TaskScheduler::InitParams> GetTaskSchedulerInitParams()
-      override;
   bool IsSlimNavigationManagerEnabled() const override;
 
  private:
diff --git a/ios/chrome/browser/web/chrome_web_client.mm b/ios/chrome/browser/web/chrome_web_client.mm
index 92f5cbb..2bb53aec 100644
--- a/ios/chrome/browser/web/chrome_web_client.mm
+++ b/ios/chrome/browser/web/chrome_web_client.mm
@@ -15,7 +15,6 @@
 #include "components/payments/core/features.h"
 #include "components/prefs/pref_service.h"
 #include "components/strings/grit/components_strings.h"
-#include "components/task_scheduler_util/browser/initialization.h"
 #include "components/version_info/version_info.h"
 #include "ios/chrome/browser/application_context.h"
 #include "ios/chrome/browser/browser_about_rewriter.h"
@@ -187,11 +186,6 @@
                                      overridable, callback);
 }
 
-std::unique_ptr<base::TaskScheduler::InitParams>
-ChromeWebClient::GetTaskSchedulerInitParams() {
-  return task_scheduler_util::GetBrowserTaskSchedulerInitParamsFromVariations();
-}
-
 bool ChromeWebClient::IsSlimNavigationManagerEnabled() const {
   return experimental_flags::IsSlimNavigationManagerEnabled();
 }
diff --git a/ios/clean/DEPS b/ios/clean/DEPS
new file mode 100644
index 0000000..b273ae3
--- /dev/null
+++ b/ios/clean/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+ui/gfx",
+]
diff --git a/ios/clean/chrome/browser/ui/omnibox/location_bar_mediator.mm b/ios/clean/chrome/browser/ui/omnibox/location_bar_mediator.mm
index f9ca92d6..d02227b 100644
--- a/ios/clean/chrome/browser/ui/omnibox/location_bar_mediator.mm
+++ b/ios/clean/chrome/browser/ui/omnibox/location_bar_mediator.mm
@@ -123,11 +123,6 @@
   // explanation of what this method needs to do.
 }
 
-- (void)locationBarChanged {
-  // TODO(crbug.com/708341): Implement this method or edit this comment with an
-  // explanation of what this method needs to do.
-}
-
 - (web::WebState*)getWebState {
   return _webStateList->GetActiveWebState();
 }
diff --git a/ios/clean/chrome/browser/ui/tab_collection/BUILD.gn b/ios/clean/chrome/browser/ui/tab_collection/BUILD.gn
index 7bdd24da..565a91a3 100644
--- a/ios/clean/chrome/browser/ui/tab_collection/BUILD.gn
+++ b/ios/clean/chrome/browser/ui/tab_collection/BUILD.gn
@@ -10,9 +10,11 @@
   deps = [
     ":tab_collection_ui",
     "//base",
+    "//ios/chrome/browser/snapshots",
     "//ios/chrome/browser/web",
     "//ios/chrome/browser/web_state_list",
     "//ios/web",
+    "//ui/gfx",
   ]
   configs += [ "//build/config/compiler:enable_arc" ]
 }
diff --git a/ios/clean/chrome/browser/ui/tab_collection/tab_collection_consumer.h b/ios/clean/chrome/browser/ui/tab_collection/tab_collection_consumer.h
index c771697..cd2a1b6b 100644
--- a/ios/clean/chrome/browser/ui/tab_collection/tab_collection_consumer.h
+++ b/ios/clean/chrome/browser/ui/tab_collection/tab_collection_consumer.h
@@ -36,6 +36,10 @@
 // of the selected item in the tab collection.
 - (void)populateItems:(NSArray<TabCollectionItem*>*)items
         selectedIndex:(int)selectedIndex;
+
+// Updates the snapshot |index|.
+- (void)updateSnapshotAtIndex:(int)index;
+
 @end
 
 #endif  // IOS_CLEAN_CHROME_BROWSER_UI_TAB_COLLECTION_TAB_COLLECTION_CONSUMER_H_
diff --git a/ios/clean/chrome/browser/ui/tab_collection/tab_collection_mediator.h b/ios/clean/chrome/browser/ui/tab_collection/tab_collection_mediator.h
index 37f120f0..f761b679 100644
--- a/ios/clean/chrome/browser/ui/tab_collection/tab_collection_mediator.h
+++ b/ios/clean/chrome/browser/ui/tab_collection/tab_collection_mediator.h
@@ -7,6 +7,7 @@
 
 #import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
 
+@class SnapshotCache;
 @protocol TabCollectionConsumer;
 class WebStateList;
 
@@ -21,6 +22,9 @@
 // list.
 @property(nonatomic, weak) id<TabCollectionConsumer> consumer;
 
+// Takes a snapshot of the active webState and updates the consumer.
+- (void)takeSnapshotWithCache:(SnapshotCache*)snapshotCache;
+
 // Stops observing all objects.
 - (void)disconnect;
 
diff --git a/ios/clean/chrome/browser/ui/tab_collection/tab_collection_mediator.mm b/ios/clean/chrome/browser/ui/tab_collection/tab_collection_mediator.mm
index d99ad20..248ab6e 100644
--- a/ios/clean/chrome/browser/ui/tab_collection/tab_collection_mediator.mm
+++ b/ios/clean/chrome/browser/ui/tab_collection/tab_collection_mediator.mm
@@ -4,15 +4,19 @@
 
 #import "ios/clean/chrome/browser/ui/tab_collection/tab_collection_mediator.h"
 
+#include "base/mac/bind_objc_block.h"
 #include "base/memory/ptr_util.h"
 #include "base/scoped_observer.h"
 #include "base/strings/sys_string_conversions.h"
+#import "ios/chrome/browser/snapshots/snapshot_cache.h"
+#import "ios/chrome/browser/snapshots/snapshot_constants.h"
 #import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/clean/chrome/browser/ui/tab_collection/tab_collection_consumer.h"
 #import "ios/clean/chrome/browser/ui/tab_collection/tab_collection_item.h"
 #include "ios/web/public/web_state/web_state.h"
 #import "ios/web/public/web_state/web_state_observer_bridge.h"
+#include "ui/gfx/image/image.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -48,6 +52,21 @@
 
 #pragma mark - Public
 
+- (void)takeSnapshotWithCache:(SnapshotCache*)snapshotCache {
+  web::WebState* webState = self.webStateList->GetActiveWebState();
+  TabIdTabHelper* tabHelper = TabIdTabHelper::FromWebState(webState);
+  DCHECK(tabHelper);
+  NSString* tabID = tabHelper->tab_id();
+  int index = self.webStateList->active_index();
+  __weak TabCollectionMediator* weakSelf = self;
+  webState->TakeSnapshot(base::BindBlockArc(^(const gfx::Image& snapshot) {
+                           [snapshotCache setImage:snapshot.ToUIImage()
+                                     withSessionID:tabID];
+                           [weakSelf.consumer updateSnapshotAtIndex:index];
+                         }),
+                         kSnapshotThumbnailSize);
+}
+
 - (void)disconnect {
   _webStateList = nullptr;
   _webStateObserver.reset();
diff --git a/ios/clean/chrome/browser/ui/tab_collection/tab_collection_mediator_unittest.mm b/ios/clean/chrome/browser/ui/tab_collection/tab_collection_mediator_unittest.mm
index 2a74641..a9f98dfd 100644
--- a/ios/clean/chrome/browser/ui/tab_collection/tab_collection_mediator_unittest.mm
+++ b/ios/clean/chrome/browser/ui/tab_collection/tab_collection_mediator_unittest.mm
@@ -5,6 +5,7 @@
 #import "ios/clean/chrome/browser/ui/tab_collection/tab_collection_mediator.h"
 
 #include "base/memory/ptr_util.h"
+#import "ios/chrome/browser/snapshots/snapshot_cache.h"
 #import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #include "ios/chrome/browser/web_state_list/fake_web_state_list_delegate.h"
 #include "ios/chrome/browser/web_state_list/web_state_list.h"
@@ -95,5 +96,23 @@
 // webStateList.
 TEST_F(TabCollectionMediatorTest, TestChangeActiveWebState) {
   web_state_list_->ActivateWebStateAt(2);
-  [[consumer_ verify] setSelectedIndex:2];
+  // Due to use of id for OCMock objects, naming collisions can exist. In this
+  // case, the method -setSelectedIndex: collides with a property setter in
+  // UIKit's UITabBarController class. The fix is to cast after calling -verify.
+  auto consumer = static_cast<id<TabCollectionConsumer>>([consumer_ verify]);
+  [consumer setSelectedIndex:2];
+}
+
+// Tests that the consumer is notified that a snapshot has been updated.
+TEST_F(TabCollectionMediatorTest, TestTakeSnapshot) {
+  web::TestWebState* web_state = GetWebStateAt(0);
+  TabIdTabHelper::CreateForWebState(web_state);
+  TabIdTabHelper* tab_helper = TabIdTabHelper::FromWebState(web_state);
+  NSString* tab_id = tab_helper->tab_id();
+
+  id snapshot_cache = OCMClassMock([SnapshotCache class]);
+  [mediator_ takeSnapshotWithCache:snapshot_cache];
+
+  [[snapshot_cache verify] setImage:[OCMArg any] withSessionID:tab_id];
+  [[consumer_ verify] updateSnapshotAtIndex:0];
 }
diff --git a/ios/clean/chrome/browser/ui/tab_collection/tab_collection_tab_cell.mm b/ios/clean/chrome/browser/ui/tab_collection/tab_collection_tab_cell.mm
index 686b367..aabb798 100644
--- a/ios/clean/chrome/browser/ui/tab_collection/tab_collection_tab_cell.mm
+++ b/ios/clean/chrome/browser/ui/tab_collection/tab_collection_tab_cell.mm
@@ -64,7 +64,7 @@
                        callback:^(UIImage* snapshot) {
                          // PLACEHOLDER: This operation will be cancellable.
                          if ([weakSelf.item.tabID isEqualToString:item.tabID]) {
-                           [weakSelf setSnapshot:snapshot];
+                           weakSelf.snapshot = snapshot;
                          }
                        }];
 }
diff --git a/ios/clean/chrome/browser/ui/tab_collection/tab_collection_view_controller.mm b/ios/clean/chrome/browser/ui/tab_collection/tab_collection_view_controller.mm
index 87b9107..eb433e9a 100644
--- a/ios/clean/chrome/browser/ui/tab_collection/tab_collection_view_controller.mm
+++ b/ios/clean/chrome/browser/ui/tab_collection/tab_collection_view_controller.mm
@@ -157,6 +157,7 @@
 - (void)insertItem:(TabCollectionItem*)item
            atIndex:(int)index
      selectedIndex:(int)selectedIndex {
+  DCHECK(item);
   DCHECK_GE(index, 0);
   DCHECK_LE(static_cast<NSUInteger>(index), self.items.count);
   [self.items insertObject:item atIndex:index];
@@ -204,4 +205,13 @@
   self.selectedIndex = selectedIndex;
 }
 
+- (void)updateSnapshotAtIndex:(int)index {
+  DCHECK_GE(index, 0);
+  DCHECK_LT(static_cast<NSUInteger>(index), self.items.count);
+  TabCollectionTabCell* cell = base::mac::ObjCCastStrict<TabCollectionTabCell>(
+      [self.tabs cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index
+                                                            inSection:0]]);
+  [cell configureCell:self.items[index] snapshotCache:self.snapshotCache];
+}
+
 @end
diff --git a/ios/clean/chrome/browser/ui/tab_collection/tab_collection_view_controller_unittest.mm b/ios/clean/chrome/browser/ui/tab_collection/tab_collection_view_controller_unittest.mm
index f6b5cf8..b3dc7f7 100644
--- a/ios/clean/chrome/browser/ui/tab_collection/tab_collection_view_controller_unittest.mm
+++ b/ios/clean/chrome/browser/ui/tab_collection/tab_collection_view_controller_unittest.mm
@@ -6,9 +6,11 @@
 
 #import "ios/clean/chrome/browser/ui/tab_collection/tab_collection_consumer.h"
 #import "ios/clean/chrome/browser/ui/tab_collection/tab_collection_item.h"
+#import "ios/clean/chrome/browser/ui/tab_collection/tab_collection_tab_cell.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #import "testing/gtest_mac.h"
 #include "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
 #include "third_party/ocmock/gtest_support.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -17,10 +19,12 @@
 
 @interface TestTabCollectionViewController : TabCollectionViewController
 @property(nonatomic, readwrite) NSMutableArray<TabCollectionItem*>* items;
+@property(nonatomic, readwrite) UICollectionView* tabs;
 @end
 
 @implementation TestTabCollectionViewController
 @dynamic items;
+@dynamic tabs;
 @end
 
 class TabCollectionViewControllerTest : public PlatformTest {
@@ -32,10 +36,14 @@
     TabCollectionItem* item1 = [[TabCollectionItem alloc] init];
     item1.title = @"Item1";
     view_controller_.items = [@[ item0, item1 ] mutableCopy];
+
+    tabs_ = OCMClassMock([UICollectionView class]);
+    view_controller_.tabs = tabs_;
   }
 
  protected:
   TestTabCollectionViewController* view_controller_;
+  id tabs_;
 };
 
 // Tests that an item is inserted.
@@ -73,3 +81,13 @@
   [view_controller_ populateItems:@[ item ] selectedIndex:0];
   EXPECT_NSEQ(@"NewItem", view_controller_.items[0].title);
 }
+
+// Tests that a snapshot is updated.
+TEST_F(TabCollectionViewControllerTest, TestUpdateSnapshot) {
+  id cell = OCMClassMock([TabCollectionTabCell class]);
+  NSIndexPath* indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
+  OCMStub([tabs_ cellForItemAtIndexPath:indexPath]).andReturn(cell);
+  [view_controller_ updateSnapshotAtIndex:0];
+  [[cell verify] configureCell:view_controller_.items[0]
+                 snapshotCache:[OCMArg any]];
+}
diff --git a/ios/clean/chrome/browser/ui/tab_grid/tab_grid_coordinator.mm b/ios/clean/chrome/browser/ui/tab_grid/tab_grid_coordinator.mm
index 26e1b554..cc51c6b 100644
--- a/ios/clean/chrome/browser/ui/tab_grid/tab_grid_coordinator.mm
+++ b/ios/clean/chrome/browser/ui/tab_grid/tab_grid_coordinator.mm
@@ -184,6 +184,7 @@
 }
 
 - (void)showTabGrid {
+  [self.mediator takeSnapshotWithCache:self.snapshotCache];
   // This object should only ever have at most one child.
   DCHECK_LE(self.children.count, 1UL);
   BrowserCoordinator* child = [self.children anyObject];
diff --git a/ios/public/provider/chrome/browser/signin/BUILD.gn b/ios/public/provider/chrome/browser/signin/BUILD.gn
index fd8141a..8820007 100644
--- a/ios/public/provider/chrome/browser/signin/BUILD.gn
+++ b/ios/public/provider/chrome/browser/signin/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 source_set("signin") {
+  configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
     "chrome_identity.h",
     "chrome_identity.mm",
diff --git a/ios/public/provider/chrome/browser/signin/chrome_identity.h b/ios/public/provider/chrome/browser/signin/chrome_identity.h
index d427bd4..c755a07 100644
--- a/ios/public/provider/chrome/browser/signin/chrome_identity.h
+++ b/ios/public/provider/chrome/browser/signin/chrome_identity.h
@@ -14,19 +14,19 @@
 
 // Identity/account email address. This can be shown to the user, but is not a
 // unique identifier (@see gaiaID).
-@property(nonatomic, readonly) NSString* userEmail;
+@property(strong, nonatomic, readonly) NSString* userEmail;
 
 // The unique GAIA user identifier for this identity/account.
 // You may use this as a unique identifier to remember a particular identity.
-@property(nonatomic, readonly) NSString* gaiaID;
+@property(strong, nonatomic, readonly) NSString* gaiaID;
 
 // Returns the full name of the identity.
 // Could be nil if no full name has been fetched for this account yet.
-@property(nonatomic, readonly) NSString* userFullName;
+@property(strong, nonatomic, readonly) NSString* userFullName;
 
 // Cached Hashed Gaia ID. This is used to pass the currently signed in account
 // between apps.
-@property(nonatomic, readonly) NSString* hashedGaiaID;
+@property(strong, nonatomic, readonly) NSString* hashedGaiaID;
 
 @end
 
diff --git a/ios/public/provider/chrome/browser/signin/chrome_identity.mm b/ios/public/provider/chrome/browser/signin/chrome_identity.mm
index b2a04968..90284ad 100644
--- a/ios/public/provider/chrome/browser/signin/chrome_identity.mm
+++ b/ios/public/provider/chrome/browser/signin/chrome_identity.mm
@@ -6,6 +6,10 @@
 
 #include "base/logging.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 @implementation ChromeIdentity
 
 - (NSString*)userEmail {
diff --git a/ios/public/provider/chrome/browser/signin/chrome_identity_interaction_manager.h b/ios/public/provider/chrome/browser/signin/chrome_identity_interaction_manager.h
index 4116256..1e259175 100644
--- a/ios/public/provider/chrome/browser/signin/chrome_identity_interaction_manager.h
+++ b/ios/public/provider/chrome/browser/signin/chrome_identity_interaction_manager.h
@@ -30,7 +30,7 @@
 @interface ChromeIdentityInteractionManager : NSObject
 
 // Delegate used to present and dismiss the view controllers.
-@property(nonatomic, assign) id<ChromeIdentityInteractionManagerDelegate>
+@property(nonatomic, weak) id<ChromeIdentityInteractionManagerDelegate>
     delegate;
 
 // Whether the manager is currently being canceled. Delegates may inquire if the
diff --git a/ios/public/provider/chrome/browser/signin/chrome_identity_interaction_manager.mm b/ios/public/provider/chrome/browser/signin/chrome_identity_interaction_manager.mm
index 6e5b223..4be6723 100644
--- a/ios/public/provider/chrome/browser/signin/chrome_identity_interaction_manager.mm
+++ b/ios/public/provider/chrome/browser/signin/chrome_identity_interaction_manager.mm
@@ -4,23 +4,14 @@
 
 #import "ios/public/provider/chrome/browser/signin/chrome_identity_interaction_manager.h"
 
-#import "base/ios/weak_nsobject.h"
 #include "base/logging.h"
 
-@interface ChromeIdentityInteractionManager () {
-  base::WeakNSProtocol<id<ChromeIdentityInteractionManagerDelegate>> _delegate;
-}
-@end
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
 
 @implementation ChromeIdentityInteractionManager
-
-- (id<ChromeIdentityInteractionManagerDelegate>)delegate {
-  return _delegate;
-}
-
-- (void)setDelegate:(id<ChromeIdentityInteractionManagerDelegate>)delegate {
-  _delegate.reset(delegate);
-}
+@synthesize delegate = _delegate;
 
 - (BOOL)isCanceling {
   return NO;
diff --git a/ios/public/provider/chrome/browser/signin/chrome_identity_service.mm b/ios/public/provider/chrome/browser/signin/chrome_identity_service.mm
index be02f63..d32e9ea 100644
--- a/ios/public/provider/chrome/browser/signin/chrome_identity_service.mm
+++ b/ios/public/provider/chrome/browser/signin/chrome_identity_service.mm
@@ -6,6 +6,10 @@
 
 #include "ios/public/provider/chrome/browser/signin/chrome_identity_interaction_manager.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace ios {
 
 ChromeIdentityService::ChromeIdentityService() {}
diff --git a/ios/public/provider/chrome/browser/signin/signin_error_provider.mm b/ios/public/provider/chrome/browser/signin/signin_error_provider.mm
index f9eeba60..fe49446 100644
--- a/ios/public/provider/chrome/browser/signin/signin_error_provider.mm
+++ b/ios/public/provider/chrome/browser/signin/signin_error_provider.mm
@@ -4,6 +4,10 @@
 
 #include "ios/public/provider/chrome/browser/signin/signin_error_provider.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace ios {
 
 SigninErrorProvider::SigninErrorProvider() {}
diff --git a/ios/public/provider/chrome/browser/signin/signin_resources_provider.mm b/ios/public/provider/chrome/browser/signin/signin_resources_provider.mm
index 0d66297..ebd9bf056 100644
--- a/ios/public/provider/chrome/browser/signin/signin_resources_provider.mm
+++ b/ios/public/provider/chrome/browser/signin/signin_resources_provider.mm
@@ -6,6 +6,10 @@
 
 #include <MacTypes.h>
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace ios {
 
 SigninResourcesProvider::SigninResourcesProvider() {
diff --git a/ios/shared/chrome/browser/ui/omnibox/location_bar_delegate.h b/ios/shared/chrome/browser/ui/omnibox/location_bar_delegate.h
index 7c86a1c..e4b3113 100644
--- a/ios/shared/chrome/browser/ui/omnibox/location_bar_delegate.h
+++ b/ios/shared/chrome/browser/ui/omnibox/location_bar_delegate.h
@@ -24,7 +24,6 @@
 - (void)locationBarHasBecomeFirstResponder;
 - (void)locationBarHasResignedFirstResponder;
 - (void)locationBarBeganEdit;
-- (void)locationBarChanged;
 - (web::WebState*)getWebState;
 - (ToolbarModel*)toolbarModel;
 @end
diff --git a/ios/web/app/BUILD.gn b/ios/web/app/BUILD.gn
index 801f351..10b9705 100644
--- a/ios/web/app/BUILD.gn
+++ b/ios/web/app/BUILD.gn
@@ -19,6 +19,7 @@
     "//base:i18n",
     "//crypto",
     "//ios/web",
+    "//ios/web/public/global_state",
     "//mojo/edk/system",
     "//net",
     "//ui/base",
diff --git a/ios/web/app/web_main.mm b/ios/web/app/web_main.mm
index e0d7f99..4b60364 100644
--- a/ios/web/app/web_main.mm
+++ b/ios/web/app/web_main.mm
@@ -11,9 +11,24 @@
 
 namespace web {
 
-WebMain::WebMain(const WebMainParams& params) {
+WebMainParams::WebMainParams() : WebMainParams(nullptr) {}
+
+WebMainParams::WebMainParams(WebMainDelegate* delegate)
+    : delegate(delegate),
+      register_exit_manager(true),
+      get_task_scheduler_init_params_callback(nullptr),
+      argc(0),
+      argv(nullptr) {}
+
+WebMainParams::~WebMainParams() = default;
+
+WebMainParams::WebMainParams(WebMainParams&& other) = default;
+
+WebMainParams& WebMainParams::operator=(web::WebMainParams&& other) = default;
+
+WebMain::WebMain(WebMainParams params) {
   web_main_runner_.reset(WebMainRunner::Create());
-  web_main_runner_->Initialize(params);
+  web_main_runner_->Initialize(std::move(params));
 }
 
 WebMain::~WebMain() {
diff --git a/ios/web/app/web_main_loop.h b/ios/web/app/web_main_loop.h
index 739bcd7..415da9c 100644
--- a/ios/web/app/web_main_loop.h
+++ b/ios/web/app/web_main_loop.h
@@ -9,6 +9,7 @@
 
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "ios/web/public/app/task_scheduler_init_params_callback.h"
 
 namespace base {
 class MessageLoop;
@@ -38,8 +39,10 @@
   void EarlyInitialization();
   void MainMessageLoopStart();
 
-  // Creates and starts running the tasks needed to complete startup.
-  void CreateStartupTasks();
+  // Creates and starts running the tasks needed to complete startup. The
+  // |init_params_callback| may be null or supply InitParams to be used to start
+  // the global TaskScheduler instead of using the defaults.
+  void CreateStartupTasks(TaskSchedulerInitParamsCallback init_params_callback);
 
   // Performs the shutdown sequence, starting with PostMainMessageLoopRun
   // through stopping threads to PostDestroyThreads.
@@ -53,8 +56,10 @@
   // Called just before creating the threads
   int PreCreateThreads();
 
-  // Creates all secondary threads.
-  int CreateThreads();
+  // Creates all secondary threads. The |init_params_callback| may be null or
+  // supply InitParams to be used to start the global TaskScheduler instead of
+  // using the defaults.
+  int CreateThreads(TaskSchedulerInitParamsCallback init_params_callback);
 
   // Called right after the web threads have been started.
   int WebThreadsStarted();
diff --git a/ios/web/app/web_main_loop.mm b/ios/web/app/web_main_loop.mm
index eaa0ee0..6a259973 100644
--- a/ios/web/app/web_main_loop.mm
+++ b/ios/web/app/web_main_loop.mm
@@ -18,13 +18,13 @@
 #include "base/power_monitor/power_monitor_device_source.h"
 #include "base/process/process_metrics.h"
 #include "base/system_monitor/system_monitor.h"
-#include "base/task_scheduler/initialization_util.h"
 #include "base/task_scheduler/scheduler_worker_pool_params.h"
 #include "base/task_scheduler/task_scheduler.h"
 #include "base/threading/sequenced_worker_pool.h"
 #include "base/threading/thread_restrictions.h"
 #import "ios/web/net/cookie_notification_bridge.h"
 #include "ios/web/public/app/web_main_parts.h"
+#import "ios/web/public/global_state/ios_global_state.h"
 #import "ios/web/public/web_client.h"
 #include "ios/web/service_manager_context.h"
 #include "ios/web/web_thread_impl.h"
@@ -37,33 +37,6 @@
 
 namespace web {
 
-namespace {
-
-std::unique_ptr<base::TaskScheduler::InitParams>
-GetDefaultTaskSchedulerInitParams() {
-  using StandbyThreadPolicy =
-      base::SchedulerWorkerPoolParams::StandbyThreadPolicy;
-  return base::MakeUnique<base::TaskScheduler::InitParams>(
-      base::SchedulerWorkerPoolParams(
-          StandbyThreadPolicy::ONE,
-          base::RecommendedMaxNumberOfThreadsInPool(2, 8, 0.1, 0),
-          base::TimeDelta::FromSeconds(30)),
-      base::SchedulerWorkerPoolParams(
-          StandbyThreadPolicy::ONE,
-          base::RecommendedMaxNumberOfThreadsInPool(2, 8, 0.1, 0),
-          base::TimeDelta::FromSeconds(30)),
-      base::SchedulerWorkerPoolParams(
-          StandbyThreadPolicy::ONE,
-          base::RecommendedMaxNumberOfThreadsInPool(3, 8, 0.3, 0),
-          base::TimeDelta::FromSeconds(30)),
-      base::SchedulerWorkerPoolParams(
-          StandbyThreadPolicy::ONE,
-          base::RecommendedMaxNumberOfThreadsInPool(3, 8, 0.3, 0),
-          base::TimeDelta::FromSeconds(60)));
-}
-
-}  // namespace
-
 // The currently-running WebMainLoop.  There can be one or zero.
 // TODO(rohitrao): Desktop uses this to implement
 // ImmediateShutdownAndExitProcess.  If we don't need that functionality, we can
@@ -74,9 +47,7 @@
   DCHECK(!g_current_web_main_loop);
   g_current_web_main_loop = this;
 
-  // Use an empty string as TaskScheduler name to match the suffix of browser
-  // process TaskScheduler histograms.
-  base::TaskScheduler::Create("");
+  ios_global_state::Create();
 }
 
 WebMainLoop::~WebMainLoop() {
@@ -123,13 +94,14 @@
   }
 }
 
-void WebMainLoop::CreateStartupTasks() {
+void WebMainLoop::CreateStartupTasks(
+    TaskSchedulerInitParamsCallback init_params_callback) {
   int result = 0;
   result = PreCreateThreads();
   if (result > 0)
     return;
 
-  result = CreateThreads();
+  result = CreateThreads(std::move(init_params_callback));
   if (result > 0)
     return;
 
@@ -150,16 +122,13 @@
   return result_code_;
 }
 
-int WebMainLoop::CreateThreads() {
-  {
-    auto task_scheduler_init_params =
-        GetWebClient()->GetTaskSchedulerInitParams();
-    if (!task_scheduler_init_params)
-      task_scheduler_init_params = GetDefaultTaskSchedulerInitParams();
-    DCHECK(task_scheduler_init_params);
-    base::TaskScheduler::GetInstance()->Start(
-        *task_scheduler_init_params.get());
+int WebMainLoop::CreateThreads(
+    TaskSchedulerInitParamsCallback init_params_callback) {
+  std::unique_ptr<base::TaskScheduler::InitParams> init_params;
+  if (!init_params_callback.is_null()) {
+    init_params = std::move(init_params_callback).Run();
   }
+  ios_global_state::StartTaskScheduler(init_params.get());
 
   base::SequencedWorkerPool::EnableWithRedirectionToTaskSchedulerForProcess();
 
diff --git a/ios/web/app/web_main_runner.mm b/ios/web/app/web_main_runner.mm
index 53849ff..855dcf77 100644
--- a/ios/web/app/web_main_runner.mm
+++ b/ios/web/app/web_main_runner.mm
@@ -36,7 +36,7 @@
     }
   }
 
-  int Initialize(const WebMainParams& params) override {
+  int Initialize(WebMainParams params) override {
     ////////////////////////////////////////////////////////////////////////
     // ContentMainRunnerImpl::Initialize()
     //
@@ -73,7 +73,8 @@
     main_loop_->Init();
     main_loop_->EarlyInitialization();
     main_loop_->MainMessageLoopStart();
-    main_loop_->CreateStartupTasks();
+    main_loop_->CreateStartupTasks(
+        std::move(params.get_task_scheduler_init_params_callback));
     int result_code = main_loop_->GetResultCode();
     if (result_code > 0)
       return result_code;
diff --git a/ios/web/public/app/BUILD.gn b/ios/web/public/app/BUILD.gn
index 45aa701..45fcc363 100644
--- a/ios/web/public/app/BUILD.gn
+++ b/ios/web/public/app/BUILD.gn
@@ -6,6 +6,7 @@
 
 source_set("app") {
   sources = [
+    "task_scheduler_init_params_callback.h",
     "web_main.h",
     "web_main_delegate.h",
     "web_main_parts.h",
diff --git a/ios/web/public/app/task_scheduler_init_params_callback.h b/ios/web/public/app/task_scheduler_init_params_callback.h
new file mode 100644
index 0000000..3fef718
--- /dev/null
+++ b/ios/web/public/app/task_scheduler_init_params_callback.h
@@ -0,0 +1,19 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_WEB_PUBLIC_GLOBAL_STATE_TASK_SCHEDULER_INIT_PARAMS_CALLBACK_H_
+#define IOS_WEB_PUBLIC_GLOBAL_STATE_TASK_SCHEDULER_INIT_PARAMS_CALLBACK_H_
+
+#include "base/callback_forward.h"
+#include "base/task_scheduler/task_scheduler.h"
+
+namespace web {
+
+// Callback which returns a pointer to InitParams for base::TaskScheduler.
+typedef base::OnceCallback<std::unique_ptr<base::TaskScheduler::InitParams>()>
+    TaskSchedulerInitParamsCallback;
+
+}  // namespace web
+
+#endif  // IOS_WEB_PUBLIC_GLOBAL_STATE_TASK_SCHEDULER_INIT_PARAMS_CALLBACK_H_
diff --git a/ios/web/public/app/web_main.h b/ios/web/public/app/web_main.h
index 8ae8ce8..2b5db8b9 100644
--- a/ios/web/public/app/web_main.h
+++ b/ios/web/public/app/web_main.h
@@ -7,6 +7,8 @@
 
 #include <memory>
 
+#include "base/macros.h"
+#include "ios/web/public/app/task_scheduler_init_params_callback.h"
 #include "ios/web/public/app/web_main_delegate.h"
 
 namespace web {
@@ -14,18 +16,23 @@
 
 // Contains parameters passed to WebMain.
 struct WebMainParams {
-  explicit WebMainParams(WebMainDelegate* delegate)
-      : delegate(delegate),
-        register_exit_manager(true),
-        argc(0),
-        argv(nullptr) {}
+  WebMainParams();
+  explicit WebMainParams(WebMainDelegate* delegate);
+  ~WebMainParams();
+
+  // WebMainParams is moveable.
+  WebMainParams(WebMainParams&& other);
+  WebMainParams& operator=(WebMainParams&& other);
 
   WebMainDelegate* delegate;
 
   bool register_exit_manager;
+  TaskSchedulerInitParamsCallback get_task_scheduler_init_params_callback;
 
   int argc;
   const char** argv;
+
+  DISALLOW_COPY_AND_ASSIGN(WebMainParams);
 };
 
 // Encapsulates any setup and initialization that is needed by common
@@ -37,7 +44,7 @@
 // in WebMainDelegate and WebMainParts.
 class WebMain {
  public:
-  explicit WebMain(const WebMainParams& params);
+  explicit WebMain(WebMainParams params);
   ~WebMain();
 
  private:
diff --git a/ios/web/public/app/web_main_runner.h b/ios/web/public/app/web_main_runner.h
index aa940f4..9cb5a9a 100644
--- a/ios/web/public/app/web_main_runner.h
+++ b/ios/web/public/app/web_main_runner.h
@@ -18,7 +18,7 @@
   static WebMainRunner* Create();
 
   // Initialize all necessary web state.
-  virtual int Initialize(const WebMainParams& params) = 0;
+  virtual int Initialize(WebMainParams params) = 0;
 
   // Shut down the web state.
   virtual void ShutDown() = 0;
diff --git a/ios/web/public/global_state/BUILD.gn b/ios/web/public/global_state/BUILD.gn
new file mode 100644
index 0000000..a433b21
--- /dev/null
+++ b/ios/web/public/global_state/BUILD.gn
@@ -0,0 +1,16 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("global_state") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+
+  deps = [
+    "//base",
+  ]
+
+  sources = [
+    "ios_global_state.h",
+    "ios_global_state.mm",
+  ]
+}
diff --git a/ios/web/public/global_state/ios_global_state.h b/ios/web/public/global_state/ios_global_state.h
new file mode 100644
index 0000000..35c6c62
--- /dev/null
+++ b/ios/web/public/global_state/ios_global_state.h
@@ -0,0 +1,25 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_WEB_PUBLIC_GLOBAL_STATE_IOS_GLOBAL_STATE_H_
+#define IOS_WEB_PUBLIC_GLOBAL_STATE_IOS_GLOBAL_STATE_H_
+
+#include "base/task_scheduler/task_scheduler.h"
+
+namespace ios_global_state {
+
+// Creates global state for iOS. This should be called as early as possible in
+// the application lifecycle. It is safe to call this method more than once, the
+// initialization will only be performed once.
+void Create();
+
+// Starts a global base::TaskScheduler. This method must be called to start
+// the Task Scheduler that is created in |Create|. If |init_params| is null,
+// default InitParams will be used. It is safe to call this method more than
+// once, the task scheduler will only be started once.
+void StartTaskScheduler(base::TaskScheduler::InitParams* init_params);
+
+}  // namespace ios_global_state
+
+#endif  // IOS_WEB_PUBLIC_GLOBAL_STATE_IOS_GLOBAL_STATE_H_
diff --git a/ios/web/public/global_state/ios_global_state.mm b/ios/web/public/global_state/ios_global_state.mm
new file mode 100644
index 0000000..ab7ec88
--- /dev/null
+++ b/ios/web/public/global_state/ios_global_state.mm
@@ -0,0 +1,59 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ios/web/public/global_state/ios_global_state.h"
+
+#include "base/memory/ptr_util.h"
+#include "base/task_scheduler/initialization_util.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+
+base::TaskScheduler::InitParams GetDefaultTaskSchedulerInitParams() {
+  using StandbyThreadPolicy =
+      base::SchedulerWorkerPoolParams::StandbyThreadPolicy;
+  return base::TaskScheduler::InitParams(
+      base::SchedulerWorkerPoolParams(
+          StandbyThreadPolicy::ONE,
+          base::RecommendedMaxNumberOfThreadsInPool(2, 8, 0.1, 0),
+          base::TimeDelta::FromSeconds(30)),
+      base::SchedulerWorkerPoolParams(
+          StandbyThreadPolicy::ONE,
+          base::RecommendedMaxNumberOfThreadsInPool(2, 8, 0.1, 0),
+          base::TimeDelta::FromSeconds(30)),
+      base::SchedulerWorkerPoolParams(
+          StandbyThreadPolicy::ONE,
+          base::RecommendedMaxNumberOfThreadsInPool(3, 8, 0.3, 0),
+          base::TimeDelta::FromSeconds(30)),
+      base::SchedulerWorkerPoolParams(
+          StandbyThreadPolicy::ONE,
+          base::RecommendedMaxNumberOfThreadsInPool(3, 8, 0.3, 0),
+          base::TimeDelta::FromSeconds(60)));
+}
+
+}  // namespace
+
+namespace ios_global_state {
+
+void Create() {
+  static dispatch_once_t once_token;
+  dispatch_once(&once_token, ^{
+    // Use an empty string as TaskScheduler name to match the suffix of browser
+    // process TaskScheduler histograms.
+    base::TaskScheduler::Create("");
+  });
+}
+
+void StartTaskScheduler(base::TaskScheduler::InitParams* params) {
+  static dispatch_once_t once_token;
+  dispatch_once(&once_token, ^{
+    auto init_params = params ? *params : GetDefaultTaskSchedulerInitParams();
+    base::TaskScheduler::GetInstance()->Start(init_params);
+  });
+}
+
+}  // namespace ios_global_state
diff --git a/ios/web/public/test/fakes/test_web_state.mm b/ios/web/public/test/fakes/test_web_state.mm
index 97b631d..f5a4b995 100644
--- a/ios/web/public/test/fakes/test_web_state.mm
+++ b/ios/web/public/test/fakes/test_web_state.mm
@@ -235,8 +235,7 @@
 
 void TestWebState::TakeSnapshot(const SnapshotCallback& callback,
                                 CGSize target_size) const {
-  base::SequencedTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::Bind(callback, gfx::Image()));
+  callback.Run(gfx::Image([[UIImage alloc] init]));
 }
 
 base::WeakPtr<WebState> TestWebState::AsWeakPtr() {
diff --git a/ios/web/public/web_client.h b/ios/web/public/web_client.h
index 400efa1..ff8b96e 100644
--- a/ios/web/public/web_client.h
+++ b/ios/web/public/web_client.h
@@ -141,11 +141,6 @@
       bool overridable,
       const base::Callback<void(bool)>& callback);
 
-  // Provides parameters for initializing the global task scheduler. Default
-  // params are used if this returns nullptr.
-  virtual std::unique_ptr<base::TaskScheduler::InitParams>
-  GetTaskSchedulerInitParams();
-
   // Allows upper layers to inject experimental flags to the web layer.
   // TODO(crbug.com/734150): Clean up this flag after experiment. If need for a
   // second flag arises before clean up, consider generalizing to an experiment
diff --git a/ios/web/public/web_client.mm b/ios/web/public/web_client.mm
index 81bd02b2..ef02e6b 100644
--- a/ios/web/public/web_client.mm
+++ b/ios/web/public/web_client.mm
@@ -91,11 +91,6 @@
   callback.Run(false);
 }
 
-std::unique_ptr<base::TaskScheduler::InitParams>
-WebClient::GetTaskSchedulerInitParams() {
-  return nullptr;
-}
-
 bool WebClient::IsSlimNavigationManagerEnabled() const {
   return false;
 }
diff --git a/ios/web/shell/app_delegate.mm b/ios/web/shell/app_delegate.mm
index abc7bac..4274183 100644
--- a/ios/web/shell/app_delegate.mm
+++ b/ios/web/shell/app_delegate.mm
@@ -7,6 +7,7 @@
 #include <memory>
 
 #import "base/mac/scoped_nsobject.h"
+#include "base/memory/ptr_util.h"
 #include "ios/web/public/app/web_main.h"
 #import "ios/web/public/web_client.h"
 #import "ios/web/public/web_state/web_state.h"
@@ -36,8 +37,9 @@
   self.window.backgroundColor = [UIColor whiteColor];
 
   _delegate.reset(new web::ShellMainDelegate());
+
   web::WebMainParams params(_delegate.get());
-  _webMain.reset(new web::WebMain(params));
+  _webMain = base::MakeUnique<web::WebMain>(std::move(params));
 
   web::ShellWebClient* client =
       static_cast<web::ShellWebClient*>(web::GetWebClient());
diff --git a/ios/web_view/internal/web_view_global_state_util.mm b/ios/web_view/internal/web_view_global_state_util.mm
index 49fd8500..8491b48 100644
--- a/ios/web_view/internal/web_view_global_state_util.mm
+++ b/ios/web_view/internal/web_view_global_state_util.mm
@@ -24,7 +24,7 @@
     web_main_delegate =
         base::MakeUnique<ios_web_view::WebViewWebMainDelegate>();
     web::WebMainParams params(web_main_delegate.get());
-    web_main = base::MakeUnique<web::WebMain>(params);
+    web_main = base::MakeUnique<web::WebMain>(std::move(params));
   });
 }
 
diff --git a/media/cdm/cdm_adapter.cc b/media/cdm/cdm_adapter.cc
index 3a32303..a0bd452 100644
--- a/media/cdm/cdm_adapter.cc
+++ b/media/cdm/cdm_adapter.cc
@@ -279,8 +279,14 @@
 
   input_buffer->data = encrypted_buffer->data();
   input_buffer->data_size = encrypted_buffer->data_size();
+  input_buffer->timestamp = encrypted_buffer->timestamp().InMicroseconds();
 
   const DecryptConfig* decrypt_config = encrypted_buffer->decrypt_config();
+  if (!decrypt_config) {
+    DVLOG(2) << __func__ << ": Clear buffer.";
+    return;
+  }
+
   input_buffer->key_id =
       reinterpret_cast<const uint8_t*>(decrypt_config->key_id().data());
   input_buffer->key_id_size = decrypt_config->key_id().size();
@@ -300,7 +306,6 @@
 
   input_buffer->subsamples = subsamples->data();
   input_buffer->num_subsamples = num_subsamples;
-  input_buffer->timestamp = encrypted_buffer->timestamp().InMicroseconds();
 }
 
 void* GetCdmHost(int host_interface_version, void* user_data) {
diff --git a/media/filters/gpu_video_decoder.cc b/media/filters/gpu_video_decoder.cc
index c92214fa..f3c920d 100644
--- a/media/filters/gpu_video_decoder.cc
+++ b/media/filters/gpu_video_decoder.cc
@@ -40,10 +40,6 @@
 namespace media {
 namespace {
 
-// Size of shared-memory segments we allocate.  Since we reuse them we let them
-// be on the beefy side.
-static const size_t kSharedMemorySegmentBytes = 100 << 10;
-
 #if defined(OS_ANDROID) && BUILDFLAG(USE_PROPRIETARY_CODECS)
 // Extract the SPS and PPS lists from |extra_data|. Each SPS and PPS is prefixed
 // with 0x0001, the Annex B framing bytes. The out parameters are not modified
@@ -82,6 +78,10 @@
 // resources.
 enum { kMaxInFlightDecodes = 4 };
 
+// Number of bitstream buffers returned before GC is attempted on shared memory
+// segments. Value chosen arbitrarily.
+enum { kBufferCountBeforeGC = 1024 };
+
 struct GpuVideoDecoder::PendingDecoderBuffer {
   PendingDecoderBuffer(std::unique_ptr<base::SharedMemory> s,
                        const scoped_refptr<DecoderBuffer>& b,
@@ -120,6 +120,8 @@
       supports_deferred_initialization_(false),
       requires_texture_copy_(false),
       cdm_id_(CdmContext::kInvalidCdmId),
+      min_shared_memory_segment_size_(0),
+      bitstream_buffer_id_of_last_gc_(0),
       weak_factory_(this) {
   DCHECK(factories_);
 }
@@ -244,6 +246,24 @@
       VideoDecodeAccelerator::Capabilities::SUPPORTS_DEFERRED_INITIALIZATION);
   output_cb_ = output_cb;
 
+  // Attempt to choose a reasonable size for the shared memory segments based on
+  // the size of video. These values are chosen based on experiments with common
+  // videos from the web. Too small and you'll end up creating too many segments
+  // too large and you end up wasting significant amounts of memory.
+  const int height = config.coded_size().height();
+  if (height >= 4000)  // ~4320p
+    min_shared_memory_segment_size_ = 384 * 1024;
+  else if (height >= 2000)  // ~2160p
+    min_shared_memory_segment_size_ = 192 * 1024;
+  else if (height >= 1000)  // ~1080p
+    min_shared_memory_segment_size_ = 96 * 1024;
+  else if (height >= 700)  // ~720p
+    min_shared_memory_segment_size_ = 72 * 1024;
+  else if (height >= 400)  // ~480p
+    min_shared_memory_segment_size_ = 48 * 1024;
+  else  // ~360p or less
+    min_shared_memory_segment_size_ = 32 * 1024;
+
   if (config.is_encrypted() && !supports_deferred_initialization_) {
     DVLOG(1) << __func__
              << " Encrypted stream requires deferred initialialization.";
@@ -745,21 +765,40 @@
 std::unique_ptr<base::SharedMemory> GpuVideoDecoder::GetSharedMemory(
     size_t min_size) {
   DCheckGpuVideoAcceleratorFactoriesTaskRunnerIsCurrent();
-  if (available_shm_segments_.empty() ||
-      available_shm_segments_.back()->mapped_size() < min_size) {
-    size_t size_to_allocate = std::max(min_size, kSharedMemorySegmentBytes);
-    // CreateSharedMemory() can return NULL during Shutdown.
-    return factories_->CreateSharedMemory(size_to_allocate);
+  auto it = std::lower_bound(available_shm_segments_.begin(),
+                             available_shm_segments_.end(), min_size,
+                             [](const ShMemEntry& entry, const size_t size) {
+                               return entry.first->mapped_size() < size;
+                             });
+  if (it != available_shm_segments_.end()) {
+    auto ret = std::move(it->first);
+    available_shm_segments_.erase(it);
+    return ret;
   }
-  auto ret = std::move(available_shm_segments_.back());
-  available_shm_segments_.pop_back();
-  return ret;
+
+  return factories_->CreateSharedMemory(
+      std::max(min_shared_memory_segment_size_, min_size));
 }
 
 void GpuVideoDecoder::PutSharedMemory(
-    std::unique_ptr<base::SharedMemory> shared_memory) {
+    std::unique_ptr<base::SharedMemory> shared_memory,
+    int32_t last_bitstream_buffer_id) {
   DCheckGpuVideoAcceleratorFactoriesTaskRunnerIsCurrent();
-  available_shm_segments_.push_back(std::move(shared_memory));
+  available_shm_segments_.emplace(std::move(shared_memory),
+                                  last_bitstream_buffer_id);
+
+  if (next_bitstream_buffer_id_ < bitstream_buffer_id_of_last_gc_ ||
+      next_bitstream_buffer_id_ - bitstream_buffer_id_of_last_gc_ >
+          kBufferCountBeforeGC) {
+    base::EraseIf(available_shm_segments_, [this](const ShMemEntry& entry) {
+      // Check for overflow rollover...
+      if (next_bitstream_buffer_id_ < entry.second)
+        return next_bitstream_buffer_id_ > kBufferCountBeforeGC;
+
+      return next_bitstream_buffer_id_ - entry.second > kBufferCountBeforeGC;
+    });
+    bitstream_buffer_id_of_last_gc_ = next_bitstream_buffer_id_;
+  }
 }
 
 void GpuVideoDecoder::NotifyEndOfBitstreamBuffer(int32_t id) {
@@ -774,7 +813,7 @@
     return;
   }
 
-  PutSharedMemory(std::move(it->second.shared_memory));
+  PutSharedMemory(std::move(it->second.shared_memory), id);
   it->second.done_cb.Run(state_ == kError ? DecodeStatus::DECODE_ERROR
                                           : DecodeStatus::OK);
   bitstream_buffers_in_decoder_.erase(it);
@@ -811,6 +850,10 @@
   DCHECK_EQ(state_, kDrainingDecoder);
   state_ = kDecoderDrained;
   base::ResetAndReturn(&eos_decode_cb_).Run(DecodeStatus::OK);
+
+  // Assume flush is for a config change, so drop shared memory segments in
+  // anticipation of a resize occurring.
+  available_shm_segments_.clear();
 }
 
 void GpuVideoDecoder::NotifyResetDone() {
diff --git a/media/filters/gpu_video_decoder.h b/media/filters/gpu_video_decoder.h
index c765d31c..4bc69d5 100644
--- a/media/filters/gpu_video_decoder.h
+++ b/media/filters/gpu_video_decoder.h
@@ -8,12 +8,14 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <deque>
 #include <list>
 #include <map>
 #include <set>
 #include <utility>
 #include <vector>
 
+#include "base/containers/flat_set.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "gpu/command_buffer/common/sync_token.h"
@@ -116,11 +118,12 @@
   void DestroyVDA();
 
   // Request a shared-memory segment of at least |min_size| bytes.  Will
-  // allocate as necessary.
+  // allocate as necessary. May return nullptr during Shutdown.
   std::unique_ptr<base::SharedMemory> GetSharedMemory(size_t min_size);
 
   // Return a shared-memory segment to the available pool.
-  void PutSharedMemory(std::unique_ptr<base::SharedMemory> shm_buffer);
+  void PutSharedMemory(std::unique_ptr<base::SharedMemory> shm_buffer,
+                       int32_t last_bitstream_buffer_id);
 
   // Destroy all PictureBuffers in |buffers|, and delete their textures.
   void DestroyPictureBuffers(PictureBufferMap* buffers);
@@ -176,10 +179,20 @@
 
   VideoDecoderConfig config_;
 
-  // Shared-memory buffer pool.  Since allocating SHM segments requires a
-  // round-trip to the browser process, we keep allocation out of the
-  // steady-state of the decoder.
-  std::vector<std::unique_ptr<base::SharedMemory>> available_shm_segments_;
+  // Shared-memory buffer pool.  Since allocating SHM segments requires a round-
+  // trip to the browser process, we try to keep allocation out of the steady-
+  // state of the decoder.
+  //
+  // The second value in the ShMemEntry is the last bitstream buffer id assigned
+  // to that segment; it's used to erase segments which are no longer active.
+  using ShMemEntry = std::pair<std::unique_ptr<base::SharedMemory>, int32_t>;
+  class ShMemEntrySortedBySize {
+   public:
+    bool operator()(const ShMemEntry& lhs, const ShMemEntry& rhs) const {
+      return lhs.first->mapped_size() < rhs.first->mapped_size();
+    }
+  };
+  base::flat_set<ShMemEntry, ShMemEntrySortedBySize> available_shm_segments_;
 
   // Placeholder sync token that was created and validated after the most
   // recent picture buffers were created.
@@ -235,6 +248,16 @@
   // encrypted content.
   int cdm_id_;
 
+  // Minimum size for shared memory segments. Ideally chosen to optimize the
+  // number of segments and total size of allocations over the course of a
+  // playback.  See Initialize() for more details.
+  size_t min_shared_memory_segment_size_;
+
+  // |next_bitstream_buffer_id_| at the time we last performed a GC of no longer
+  // used ShMemEntry objects in |available_shm_segments_|.  Updated whenever
+  // PutSharedMemory() performs a GC.
+  int32_t bitstream_buffer_id_of_last_gc_;
+
   // Bound to factories_->GetMessageLoop().
   // NOTE: Weak pointers must be invalidated before all other member variables.
   base::WeakPtrFactory<GpuVideoDecoder> weak_factory_;
diff --git a/media/gpu/mojo/jpeg_decoder.mojom b/media/gpu/mojo/jpeg_decoder.mojom
index c9532a3..d5a1fb0 100644
--- a/media/gpu/mojo/jpeg_decoder.mojom
+++ b/media/gpu/mojo/jpeg_decoder.mojom
@@ -24,7 +24,7 @@
   int32 id;
   handle<shared_buffer> memory_handle;
   uint32 size;
-  uint64 offset;
+  int64 offset;
   mojo.common.mojom.TimeDelta timestamp;
   string key_id;
   string iv;
diff --git a/media/gpu/mojo/jpeg_decoder_typemap_traits.cc b/media/gpu/mojo/jpeg_decoder_typemap_traits.cc
index 7ef9d36..a16301a6 100644
--- a/media/gpu/mojo/jpeg_decoder_typemap_traits.cc
+++ b/media/gpu/mojo/jpeg_decoder_typemap_traits.cc
@@ -102,7 +102,8 @@
     return false;
 
   media::BitstreamBuffer bitstream_buffer(
-      input.id(), memory_handle, input.size(), input.offset(), timestamp);
+      input.id(), memory_handle, input.size(),
+      base::checked_cast<off_t>(input.offset()), timestamp);
   bitstream_buffer.SetDecryptConfig(
       media::DecryptConfig(key_id, iv, subsamples));
   *output = bitstream_buffer;
diff --git a/media/gpu/mojo/jpeg_decoder_typemap_traits.h b/media/gpu/mojo/jpeg_decoder_typemap_traits.h
index 6312993..b5ec4995 100644
--- a/media/gpu/mojo/jpeg_decoder_typemap_traits.h
+++ b/media/gpu/mojo/jpeg_decoder_typemap_traits.h
@@ -34,8 +34,8 @@
     return base::checked_cast<uint32_t>(input.size());
   }
 
-  static uint64_t offset(const media::BitstreamBuffer& input) {
-    return input.offset();
+  static int64_t offset(const media::BitstreamBuffer& input) {
+    return base::checked_cast<int64_t>(input.offset());
   }
 
   static base::TimeDelta timestamp(const media::BitstreamBuffer& input) {
diff --git a/mojo/edk/system/ports/node.cc b/mojo/edk/system/ports/node.cc
index 8554533..c2edd87 100644
--- a/mojo/edk/system/ports/node.cc
+++ b/mojo/edk/system/ports/node.cc
@@ -722,17 +722,19 @@
   // first as otherwise its peer receiving port could be left stranded
   // indefinitely.
   if (AcceptPort(event->new_port_name(), event->new_port_descriptor()) != OK) {
-    ClosePort(port_ref);
+    if (port_ref.is_valid())
+      ClosePort(port_ref);
     return ERROR_PORT_STATE_UNEXPECTED;
   }
 
   PortRef new_port_ref;
   GetPort(event->new_port_name(), &new_port_ref);
-  DCHECK(new_port_ref.is_valid());
-
-  if (!port_ref.is_valid()) {
-    ClosePort(port_ref);
+  if (!port_ref.is_valid() && new_port_ref.is_valid()) {
     ClosePort(new_port_ref);
+    return ERROR_PORT_UNKNOWN;
+  } else if (port_ref.is_valid() && !new_port_ref.is_valid()) {
+    ClosePort(port_ref);
+    return ERROR_PORT_UNKNOWN;
   }
 
   return MergePortsInternal(port_ref, new_port_ref,
diff --git a/mojo/edk/system/ports/port_locker.cc b/mojo/edk/system/ports/port_locker.cc
index f9c627c..e84d0d0 100644
--- a/mojo/edk/system/ports/port_locker.cc
+++ b/mojo/edk/system/ports/port_locker.cc
@@ -37,9 +37,14 @@
 #endif
 
   // Sort the ports by address to lock them in a globally consistent order.
-  std::sort(port_refs_, port_refs_ + num_ports_, ComparePortRefsByPortAddress);
-  for (size_t i = 0; i < num_ports_; ++i)
+  std::sort(
+      port_refs_, port_refs_ + num_ports_,
+      [](const PortRef* a, const PortRef* b) { return a->port() < b->port(); });
+  for (size_t i = 0; i < num_ports_; ++i) {
+    // TODO(crbug.com/725605): Remove this CHECK.
+    CHECK(port_refs_[i]->port());
     port_refs_[i]->port()->lock_.Acquire();
+  }
 }
 
 PortLocker::~PortLocker() {
@@ -59,12 +64,6 @@
 }
 #endif
 
-// static
-bool PortLocker::ComparePortRefsByPortAddress(const PortRef* a,
-                                              const PortRef* b) {
-  return a->port() < b->port();
-}
-
 SinglePortLocker::SinglePortLocker(const PortRef* port_ref)
     : port_ref_(port_ref), locker_(&port_ref_, 1) {}
 
diff --git a/mojo/edk/system/ports/port_locker.h b/mojo/edk/system/ports/port_locker.h
index 0deeb87..38782068 100644
--- a/mojo/edk/system/ports/port_locker.h
+++ b/mojo/edk/system/ports/port_locker.h
@@ -58,8 +58,6 @@
 #endif
 
  private:
-  static bool ComparePortRefsByPortAddress(const PortRef* a, const PortRef* b);
-
   const PortRef** const port_refs_;
   const size_t num_ports_;
 
diff --git a/mojo/edk/system/watcher_dispatcher.cc b/mojo/edk/system/watcher_dispatcher.cc
index 4f7e982..3b8d156 100644
--- a/mojo/edk/system/watcher_dispatcher.cc
+++ b/mojo/edk/system/watcher_dispatcher.cc
@@ -228,7 +228,10 @@
   return MOJO_RESULT_FAILED_PRECONDITION;
 }
 
-WatcherDispatcher::~WatcherDispatcher() {}
+WatcherDispatcher::~WatcherDispatcher() {
+  // TODO(crbug.com/74044): Remove this.
+  CHECK(closed_);
+}
 
 }  // namespace edk
 }  // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
index 819143f..4b68323 100644
--- a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
+++ b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
@@ -167,6 +167,9 @@
 InterfaceEndpointClient::~InterfaceEndpointClient() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  // TODO(crbug.com/741047): Remove this.
+  CHECK(task_runner_->RunsTasksInCurrentSequence());
+
   if (controller_)
     handle_.group_controller()->DetachEndpointClient(handle_);
 }
diff --git a/net/http2/hpack/decoder/hpack_varint_decoder_test.cc b/net/http2/hpack/decoder/hpack_varint_decoder_test.cc
index 91a3d3530..6ca7994 100644
--- a/net/http2/hpack/decoder/hpack_varint_decoder_test.cc
+++ b/net/http2/hpack/decoder/hpack_varint_decoder_test.cc
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 #include "net/http2/hpack/decoder/hpack_varint_decoder.h"
-#include "net/http2/platform/api/http2_string_utils.h"
 
 // Tests of HpackVarintDecoder.
 
@@ -20,6 +19,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "net/http2/hpack/tools/hpack_block_builder.h"
 #include "net/http2/platform/api/http2_string_piece.h"
+#include "net/http2/platform/api/http2_string_utils.h"
 #include "net/http2/tools/failure.h"
 #include "net/http2/tools/http2_random.h"
 #include "net/http2/tools/random_decoder_test.h"
diff --git a/net/quic/core/quic_headers_stream_test.cc b/net/quic/core/quic_headers_stream_test.cc
index 4f3b9d4..0993c61a 100644
--- a/net/quic/core/quic_headers_stream_test.cc
+++ b/net/quic/core/quic_headers_stream_test.cc
@@ -133,69 +133,17 @@
   DISALLOW_COPY_AND_ASSIGN(ForceHolAckListener);
 };
 
-enum Http2DecoderChoice {
-  HTTP2_DECODER_SPDY,
-  HTTP2_DECODER_NEW
-};
-std::ostream& operator<<(std::ostream& os, Http2DecoderChoice v) {
-  switch (v) {
-    case HTTP2_DECODER_SPDY:
-      return os << "SPDY";
-    case HTTP2_DECODER_NEW:
-      return os << "NEW";
-  }
-  return os;
-}
-
-enum HpackDecoderChoice { HPACK_DECODER_SPDY, HPACK_DECODER3 };
-std::ostream& operator<<(std::ostream& os, HpackDecoderChoice v) {
-  switch (v) {
-    case HPACK_DECODER_SPDY:
-      return os << "SPDY";
-    case HPACK_DECODER3:
-      return os << "HPACK_DECODER3";
-  }
-  return os;
-}
-
-typedef testing::
-    tuple<QuicVersion, Perspective, Http2DecoderChoice, HpackDecoderChoice>
-        TestParamsTuple;
+typedef testing::tuple<QuicVersion, Perspective> TestParamsTuple;
 
 struct TestParams {
   explicit TestParams(TestParamsTuple params)
-      : version(testing::get<0>(params)),
-        perspective(testing::get<1>(params)),
-        http2_decoder(testing::get<2>(params)),
-        hpack_decoder(testing::get<3>(params)) {
-    switch (http2_decoder) {
-      case HTTP2_DECODER_SPDY:
-        FLAGS_chromium_http2_flag_spdy_use_http2_frame_decoder_adapter = false;
-        break;
-      case HTTP2_DECODER_NEW:
-        FLAGS_chromium_http2_flag_spdy_use_http2_frame_decoder_adapter = true;
-        // Http2FrameDecoderAdapter needs the new header methods, else
-        // --use_http2_frame_decoder_adapter=true will be ignored.
-        break;
-    }
-    switch (hpack_decoder) {
-      case HPACK_DECODER_SPDY:
-        FLAGS_chromium_http2_flag_spdy_use_hpack_decoder3 = false;
-        break;
-      case HPACK_DECODER3:
-        FLAGS_chromium_http2_flag_spdy_use_hpack_decoder3 = true;
-        break;
-    }
+      : version(testing::get<0>(params)), perspective(testing::get<1>(params)) {
     QUIC_LOG(INFO) << "TestParams: version: " << QuicVersionToString(version)
-                   << ", perspective: " << perspective
-                   << ", http2_decoder: " << http2_decoder
-                   << ", hpack_decoder: " << hpack_decoder;
+                   << ", perspective: " << perspective;
   }
 
   QuicVersion version;
   Perspective perspective;
-  Http2DecoderChoice http2_decoder;
-  HpackDecoderChoice hpack_decoder;
 };
 
 class QuicHeadersStreamTest : public QuicTestWithParam<TestParamsTuple> {
@@ -401,17 +349,13 @@
   QuicStreamId next_stream_id_;
 };
 
-// Run all tests with each version, perspective (client or server),
-// HTTP/2 and HPACK decoder.
+// Run all tests with each version, perspective (client or server)..
 INSTANTIATE_TEST_CASE_P(
     Tests,
     QuicHeadersStreamTest,
     ::testing::Combine(::testing::ValuesIn(AllSupportedVersions()),
                        ::testing::Values(Perspective::IS_CLIENT,
-                                         Perspective::IS_SERVER),
-                       ::testing::Values(HTTP2_DECODER_SPDY,
-                                         HTTP2_DECODER_NEW),
-                       ::testing::Values(HPACK_DECODER_SPDY, HPACK_DECODER3)));
+                                         Perspective::IS_SERVER)));
 
 TEST_P(QuicHeadersStreamTest, StreamId) {
   EXPECT_EQ(3u, headers_stream_->id());
diff --git a/net/spdy/chromium/spdy_flags.cc b/net/spdy/chromium/spdy_flags.cc
index b2c279b..ba57f7a 100644
--- a/net/spdy/chromium/spdy_flags.cc
+++ b/net/spdy/chromium/spdy_flags.cc
@@ -6,16 +6,6 @@
 
 namespace net {
 
-// Log compressed size of HTTP/2 requests.
-bool FLAGS_chromium_http2_flag_log_compressed_size = true;
-
-// Use //net/http2/hpack/decoder as complete HPACK decoder.
-bool FLAGS_chromium_http2_flag_spdy_use_hpack_decoder3 = true;
-
-// Use Http2FrameDecoderAdapter.
-// TODO(jamessynge): Remove flag once no longer set by scripts.
-bool FLAGS_chromium_http2_flag_spdy_use_http2_frame_decoder_adapter = true;
-
 // Use NestedSpdyFramerDecoder.
 bool FLAGS_use_nested_spdy_framer_decoder = false;
 
diff --git a/net/spdy/chromium/spdy_flags.h b/net/spdy/chromium/spdy_flags.h
index ab2907f..715331d 100644
--- a/net/spdy/chromium/spdy_flags.h
+++ b/net/spdy/chromium/spdy_flags.h
@@ -9,11 +9,7 @@
 
 namespace net {
 
-NET_EXPORT_PRIVATE extern bool
-    FLAGS_chromium_http2_flag_spdy_use_hpack_decoder3;
 NET_EXPORT_PRIVATE extern bool FLAGS_use_nested_spdy_framer_decoder;
-NET_EXPORT_PRIVATE extern bool
-    FLAGS_chromium_http2_flag_spdy_use_http2_frame_decoder_adapter;
 
 }  // namespace net
 
diff --git a/net/spdy/core/spdy_framer.cc b/net/spdy/core/spdy_framer.cc
index fc131bb..68aee37 100644
--- a/net/spdy/core/spdy_framer.cc
+++ b/net/spdy/core/spdy_framer.cc
@@ -20,7 +20,6 @@
 #include "net/quic/platform/api/quic_flags.h"
 #include "net/spdy/chromium/spdy_flags.h"
 #include "net/spdy/core/hpack/hpack_constants.h"
-#include "net/spdy/core/hpack/hpack_decoder.h"
 #include "net/spdy/core/hpack/hpack_decoder3.h"
 #include "net/spdy/core/http2_frame_decoder_adapter.h"
 #include "net/spdy/core/spdy_bitmasks.h"
@@ -61,12 +60,8 @@
 // used. This code is isolated to hopefully make merging into Chromium easier.
 std::unique_ptr<SpdyFramerDecoderAdapter> DecoderAdapterFactory(
     SpdyFramer* outer) {
-  if (FLAGS_chromium_http2_flag_spdy_use_http2_frame_decoder_adapter) {
-    DVLOG(1) << "Creating Http2FrameDecoderAdapter.";
-    return CreateHttp2FrameDecoderAdapter(outer);
-  }
-
-  return nullptr;
+  DVLOG(1) << "Creating Http2FrameDecoderAdapter.";
+  return CreateHttp2FrameDecoderAdapter(outer);
 }
 
 // Used to indicate no flags in a HTTP2 flags field.
@@ -2819,11 +2814,7 @@
 
 HpackDecoderInterface* SpdyFramer::GetHpackDecoder() {
   if (hpack_decoder_.get() == nullptr) {
-    if (FLAGS_chromium_http2_flag_spdy_use_hpack_decoder3) {
-      hpack_decoder_ = SpdyMakeUnique<HpackDecoder3>();
-    } else {
-      hpack_decoder_ = SpdyMakeUnique<HpackDecoder>();
-    }
+    hpack_decoder_ = SpdyMakeUnique<HpackDecoder3>();
   }
   return hpack_decoder_.get();
 }
diff --git a/net/spdy/core/spdy_framer_test.cc b/net/spdy/core/spdy_framer_test.cc
index afcbc64..1002192f 100644
--- a/net/spdy/core/spdy_framer_test.cc
+++ b/net/spdy/core/spdy_framer_test.cc
@@ -744,35 +744,15 @@
                          frame.size() - framer.GetHeadersMinimumSize());
 }
 
-enum DecoderChoice { DECODER_SELF, DECODER_HTTP2 };
-enum HpackChoice { HPACK_DECODER_1, HPACK_DECODER_3 };
 enum Output { USE, NOT_USE };
 
-class SpdyFramerTest : public ::testing::TestWithParam<
-                           std::tuple<DecoderChoice, HpackChoice, Output>> {
+class SpdyFramerTest : public ::testing::TestWithParam<Output> {
  public:
   SpdyFramerTest() : output_(output_buffer, kSize) {}
 
  protected:
   void SetUp() override {
-    auto param = GetParam();
-    switch (std::get<0>(param)) {
-      case DECODER_SELF:
-        FLAGS_chromium_http2_flag_spdy_use_http2_frame_decoder_adapter = false;
-        break;
-      case DECODER_HTTP2:
-        FLAGS_chromium_http2_flag_spdy_use_http2_frame_decoder_adapter = true;
-        break;
-    }
-    switch (std::get<1>(param)) {
-      case HPACK_DECODER_1:
-        FLAGS_chromium_http2_flag_spdy_use_hpack_decoder3 = false;
-        break;
-      case HPACK_DECODER_3:
-        FLAGS_chromium_http2_flag_spdy_use_hpack_decoder3 = true;
-        break;
-    }
-    switch (std::get<2>(param)) {
+    switch (GetParam()) {
       case USE:
         use_output_ = true;
         break;
@@ -811,11 +791,7 @@
 
 INSTANTIATE_TEST_CASE_P(SpdyFramerTests,
                         SpdyFramerTest,
-                        ::testing::Combine(::testing::Values(DECODER_SELF,
-                                                             DECODER_HTTP2),
-                                           ::testing::Values(HPACK_DECODER_1,
-                                                             HPACK_DECODER_3),
-                                           ::testing::Values(USE, NOT_USE)));
+                        ::testing::Values(USE, NOT_USE));
 
 // Test that we can encode and decode a SpdyHeaderBlock in serialized form.
 TEST_P(SpdyFramerTest, HeaderBlockInBuffer) {
diff --git a/remoting/client/chromoting_session.cc b/remoting/client/chromoting_session.cc
index ae7eda4..50e831e 100644
--- a/remoting/client/chromoting_session.cc
+++ b/remoting/client/chromoting_session.cc
@@ -145,6 +145,12 @@
                                       const std::string& device_name) {
   DCHECK(runtime_->ui_task_runner()->BelongsToCurrentThread());
 
+  // TODO(nicholss): |pin| here is not used. Maybe there was an api refactor and
+  // this was not cleaned up. The auth pin providing mechanism seems to be call
+  // ProvideSecret, and then call the auth callback. When session moves to
+  // Connected state, this chromoing session calls RequestPairing  based on
+  // create_pairing.
+
   create_pairing_ = create_pairing;
 
   if (create_pairing)
diff --git a/remoting/ios/app/client_connection_view_controller.mm b/remoting/ios/app/client_connection_view_controller.mm
index 272fc2f..ec589b0 100644
--- a/remoting/ios/app/client_connection_view_controller.mm
+++ b/remoting/ios/app/client_connection_view_controller.mm
@@ -536,8 +536,12 @@
   [[NSNotificationCenter defaultCenter]
       postNotificationName:kHostSessionPinProvided
                     object:self
-                  userInfo:[NSDictionary dictionaryWithObject:pin
-                                                       forKey:kHostSessionPin]];
+                  userInfo:@{
+                    kHostSessionHostName : _remoteHostName,
+                    kHostSessionPin : pin,
+                    kHostSessionCreatePairing :
+                        [NSNumber numberWithBool:createPairing]
+                  }];
 }
 
 - (void)didTapCancel:(id)sender {
@@ -559,6 +563,8 @@
       state = ClientViewConnecting;
       break;
     case SessionPinPrompt:
+      _pinEntryView.supportsPairing = [[[notification userInfo]
+          objectForKey:kSessionSupportsPairing] boolValue];
       state = ClientViewPinPrompt;
       break;
     case SessionConnected:
@@ -571,6 +577,9 @@
       // If the session closes, offer the user to reconnect.
       state = ClientViewReconnect;
       break;
+    case SessionCancelled:
+      state = ClientViewClosed;
+      break;
     default:
       LOG(ERROR) << "Unknown State for Session, " << sessionDetails.state;
       return;
diff --git a/remoting/ios/app/pin_entry_view.h b/remoting/ios/app/pin_entry_view.h
index 926831f..00c0551b 100644
--- a/remoting/ios/app/pin_entry_view.h
+++ b/remoting/ios/app/pin_entry_view.h
@@ -26,6 +26,9 @@
 // This delegate will handle interactions on the cells in the collection.
 @property(weak, nonatomic) id<PinEntryDelegate> delegate;
 
+// |supportsPairing| false will hide the remember pin checkbox.
+@property(nonatomic) BOOL supportsPairing;
+
 @end
 
 #endif  // REMOTING_IOS_APP_PIN_ENTRY_VIEW_H_
diff --git a/remoting/ios/app/pin_entry_view.mm b/remoting/ios/app/pin_entry_view.mm
index 9037e0f..ee3c3ef 100644
--- a/remoting/ios/app/pin_entry_view.mm
+++ b/remoting/ios/app/pin_entry_view.mm
@@ -28,6 +28,7 @@
 @implementation PinEntryView
 
 @synthesize delegate = _delegate;
+@synthesize supportsPairing = _supportsPairing;
 
 - (id)initWithFrame:(CGRect)frame {
   self = [super initWithFrame:frame];
@@ -82,6 +83,8 @@
         initializeLayoutConstraintsWithViews:NSDictionaryOfVariableBindings(
                                                  _pairingSwitch, _pairingLabel,
                                                  _pinButton, _pinEntry)];
+
+    _supportsPairing = YES;
   }
   return self;
 }
@@ -133,6 +136,15 @@
   return [_pinEntry endEditing:force];
 }
 
+#pragma mark - Properties
+
+- (void)setSupportsPairing:(BOOL)supportsPairing {
+  _supportsPairing = supportsPairing;
+  _pairingSwitch.hidden = !_supportsPairing;
+  [_pairingSwitch setOn:NO animated:NO];
+  _pairingLabel.hidden = !_supportsPairing;
+}
+
 #pragma mark - UITextFieldDelegate
 
 - (BOOL)textField:(UITextField*)textField
diff --git a/remoting/ios/domain/client_session_details.h b/remoting/ios/domain/client_session_details.h
index ee839b8..b52f7df 100644
--- a/remoting/ios/domain/client_session_details.h
+++ b/remoting/ios/domain/client_session_details.h
@@ -20,6 +20,7 @@
   SessionConnected,
   SessionFailed,
   SessionClosed,
+  SessionCancelled,
 };
 
 // Session states that map to |remoting::protocol::ConnectionToHost::Error|.
diff --git a/remoting/ios/facade/remoting_oauth_authentication.mm b/remoting/ios/facade/remoting_oauth_authentication.mm
index 5c0045de..f5b93137 100644
--- a/remoting/ios/facade/remoting_oauth_authentication.mm
+++ b/remoting/ios/facade/remoting_oauth_authentication.mm
@@ -93,7 +93,7 @@
 - (instancetype)init {
   self = [super init];
   if (self) {
-    _keychainWrapper = [[KeychainWrapper alloc] init];
+    _keychainWrapper = KeychainWrapper.instance;
     _user = nil;
     _firstLoadUserAttempt = YES;
   }
diff --git a/remoting/ios/facade/remoting_service.mm b/remoting/ios/facade/remoting_service.mm
index 79753a8..ba202b5 100644
--- a/remoting/ios/facade/remoting_service.mm
+++ b/remoting/ios/facade/remoting_service.mm
@@ -168,7 +168,21 @@
   [_authentication
       callbackWithAccessToken:^(RemotingAuthenticationStatus status,
                                 NSString* userEmail, NSString* accessToken) {
-        [self startHostListFetchWith:accessToken];
+        switch (status) {
+          case RemotingAuthenticationStatusSuccess:
+            [self startHostListFetchWith:accessToken];
+            break;
+          case RemotingAuthenticationStatusNetworkError:
+            NSLog(
+                @"TODO(nicholss): implement this, "
+                @"RemotingAuthenticationStatusNetworkError.");
+            break;
+          case RemotingAuthenticationStatusAuthError:
+            NSLog(
+                @"TODO(nicholss): implement this, "
+                @"RemotingAuthenticationStatusAuthError.");
+            break;
+        }
       }];
 }
 
diff --git a/remoting/ios/keychain_wrapper.h b/remoting/ios/keychain_wrapper.h
index e2cabbb4..51a57d0 100644
--- a/remoting/ios/keychain_wrapper.h
+++ b/remoting/ios/keychain_wrapper.h
@@ -9,6 +9,12 @@
 
 @class UserInfo;
 
+extern NSString* const kKeychainPairingId;
+extern NSString* const kKeychainPairingSecret;
+
+typedef void (^PairingCredentialsCallback)(NSString* pairingId,
+                                           NSString* secret);
+
 // Class to abstract the details from how iOS wants to write to the keychain.
 // TODO(nicholss): This will have to be futher refactored when we integrate
 // with the private Google auth.
@@ -18,9 +24,18 @@
 - (void)setRefreshToken:(NSString*)refreshToken;
 // Get the refresh token from the keychain, if there is one.
 - (NSString*)refreshToken;
+// Save the pairing credentials for the given host id.
+- (void)commitPairingCredentialsForHost:(NSString*)host
+                                     id:(NSString*)pairingId
+                                 secret:(NSString*)secret;
+// Get the pairing credentials for the given host id.
+- (NSDictionary*)pairingCredentialsForHost:(NSString*)host;
 // Reset the keychain and the cache.
 - (void)resetKeychainItem;
 
+// Access to the singleton shared instance from this property.
+@property(nonatomic, readonly, class) KeychainWrapper* instance;
+
 @end
 
 #endif  //  REMOTING_IOS_KEYCHAIN_WRAPPER_H_
diff --git a/remoting/ios/keychain_wrapper.mm b/remoting/ios/keychain_wrapper.mm
index 1992e45..4e03b08 100644
--- a/remoting/ios/keychain_wrapper.mm
+++ b/remoting/ios/keychain_wrapper.mm
@@ -8,10 +8,17 @@
 
 #import "remoting/ios/keychain_wrapper.h"
 
+#include "base/logging.h"
+
 #import "remoting/ios/domain/host_info.h"
 
 static const UInt8 kKeychainItemIdentifier[] = "org.chromium.RemoteDesktop\0";
 
+NSString* const kPairingSecretSeperator = @"|";
+
+NSString* const kKeychainPairingId = @"kKeychainPairingId";
+NSString* const kKeychainPairingSecret = @"kKeychainPairingSecret";
+
 @interface KeychainWrapper () {
   NSMutableDictionary* _keychainData;
   NSMutableDictionary* _userInfoQuery;
@@ -20,6 +27,16 @@
 
 @implementation KeychainWrapper
 
+// KeychainWrapper is a singleton.
++ (KeychainWrapper*)instance {
+  static KeychainWrapper* sharedInstance = nil;
+  static dispatch_once_t guard;
+  dispatch_once(&guard, ^{
+    sharedInstance = [[KeychainWrapper alloc] init];
+  });
+  return sharedInstance;
+}
+
 - (id)init {
   if ((self = [super init])) {
     OSStatus keychainErr = noErr;
@@ -30,7 +47,7 @@
         [NSData dataWithBytes:kKeychainItemIdentifier
                        length:strlen((const char*)kKeychainItemIdentifier)];
     [_userInfoQuery setObject:keychainItemID
-                       forKey:(__bridge id)kSecAttrGeneric];
+                       forKey:(__bridge id)kSecAttrService];
     [_userInfoQuery setObject:(__bridge id)kSecMatchLimitOne
                        forKey:(__bridge id)kSecMatchLimit];
     [_userInfoQuery setObject:(__bridge id)kCFBooleanTrue
@@ -61,6 +78,8 @@
   return self;
 }
 
+#pragma mark - Public
+
 - (void)setRefreshToken:(NSString*)refreshToken {
   [self setObject:refreshToken forKey:(__bridge id)kSecValueData];
 }
@@ -69,6 +88,74 @@
   return [self objectForKey:(__bridge id)kSecValueData];
 }
 
+- (void)commitPairingCredentialsForHost:(NSString*)host
+                                     id:(NSString*)pairingId
+                                 secret:(NSString*)secret {
+  NSString* keysString = [self objectForKey:(__bridge id)kSecAttrGeneric];
+  NSMutableDictionary* keys = [self stringToMap:keysString];
+  NSString* pairingIdAndSecret = [NSString
+      stringWithFormat:@"%@%@%@", pairingId, kPairingSecretSeperator, secret];
+  [keys setObject:pairingIdAndSecret forKey:host];
+  [self setObject:[self mapToString:keys] forKey:(__bridge id)kSecAttrGeneric];
+}
+
+- (NSDictionary*)pairingCredentialsForHost:(NSString*)host {
+  NSString* keysString = [self objectForKey:(__bridge id)kSecAttrGeneric];
+  NSMutableDictionary* keys = [self stringToMap:keysString];
+  NSString* pairingIdAndSecret = [keys objectForKey:host];
+  if (!pairingIdAndSecret ||
+      [pairingIdAndSecret rangeOfString:kPairingSecretSeperator].location ==
+          NSNotFound) {
+    return nil;
+  }
+  NSArray* components =
+      [pairingIdAndSecret componentsSeparatedByString:kPairingSecretSeperator];
+  DCHECK(components.count == 2);
+  return @{
+    kKeychainPairingId : components[0],
+    kKeychainPairingSecret : components[1],
+  };
+}
+
+#pragma mark - Map to String helpers
+
+- (NSMutableDictionary*)stringToMap:(NSString*)mapString {
+  NSError* err;
+
+  if (mapString &&
+      [mapString respondsToSelector:@selector(dataUsingEncoding:)]) {
+    NSData* data = [mapString dataUsingEncoding:NSUTF8StringEncoding];
+    NSDictionary* pairingMap;
+    if (data) {
+      pairingMap = (NSDictionary*)[NSJSONSerialization
+          JSONObjectWithData:data
+                     options:NSJSONReadingMutableContainers
+                       error:&err];
+    }
+    if (!err) {
+      return [NSMutableDictionary dictionaryWithDictionary:pairingMap];
+    }
+  }
+  // failed to load a dictionary, make a new one.
+  return [NSMutableDictionary dictionaryWithCapacity:1];
+}
+
+- (NSString*)mapToString:(NSDictionary*)map {
+  if (map) {
+    NSError* err;
+    NSData* jsonData =
+        [NSJSONSerialization dataWithJSONObject:map options:0 error:&err];
+    if (!err) {
+      return [[NSString alloc] initWithData:jsonData
+                                   encoding:NSUTF8StringEncoding];
+    }
+  }
+  // failed to convert the map, make nil string.
+  return nil;
+}
+
+#pragma mark - Private
+
 // Implement the mySetObject:forKey method, which writes attributes to the
 // keychain:
 - (void)setObject:(id)inObject forKey:(id)key {
@@ -106,6 +193,8 @@
   [_keychainData setObject:@"Gaia fresh token"
                     forKey:(__bridge id)kSecAttrDescription];
   [_keychainData setObject:@"" forKey:(__bridge id)kSecValueData];
+  [_keychainData setObject:@"" forKey:(__bridge id)kSecClass];
+  [_keychainData setObject:@"" forKey:(__bridge id)kSecAttrGeneric];
 }
 
 - (NSMutableDictionary*)dictionaryToSecItemFormat:
@@ -117,7 +206,7 @@
       [NSData dataWithBytes:kKeychainItemIdentifier
                      length:strlen((const char*)kKeychainItemIdentifier)];
   [returnDictionary setObject:keychainItemID
-                       forKey:(__bridge id)kSecAttrGeneric];
+                       forKey:(__bridge id)kSecAttrService];
   [returnDictionary setObject:(__bridge id)kSecClassGenericPassword
                        forKey:(__bridge id)kSecClass];
 
diff --git a/remoting/ios/session/remoting_client.h b/remoting/ios/session/remoting_client.h
index b8dd100f..817ce146 100644
--- a/remoting/ios/session/remoting_client.h
+++ b/remoting/ios/session/remoting_client.h
@@ -30,7 +30,11 @@
 
 // List of keys in user info from events.
 extern NSString* const kSessionDetails;
+extern NSString* const kSessionSupportsPairing;
 extern NSString* const kSessonStateErrorCode;
+
+extern NSString* const kHostSessionCreatePairing;
+extern NSString* const kHostSessionHostName;
 extern NSString* const kHostSessionPin;
 
 // Remoting Client is the entry point for starting a session with a remote
diff --git a/remoting/ios/session/remoting_client.mm b/remoting/ios/session/remoting_client.mm
index 3154bdc..10e6844 100644
--- a/remoting/ios/session/remoting_client.mm
+++ b/remoting/ios/session/remoting_client.mm
@@ -12,9 +12,11 @@
 
 #import "base/mac/bind_objc_block.h"
 #import "ios/third_party/material_components_ios/src/components/Dialogs/src/MaterialDialogs.h"
+#import "ios/third_party/material_components_ios/src/components/Snackbar/src/MaterialSnackbar.h"
 #import "remoting/ios/display/gl_display_handler.h"
 #import "remoting/ios/domain/client_session_details.h"
 #import "remoting/ios/domain/host_info.h"
+#import "remoting/ios/keychain_wrapper.h"
 
 #include "base/strings/sys_string_conversions.h"
 #include "remoting/client/chromoting_client_runtime.h"
@@ -31,7 +33,11 @@
 NSString* const kHostSessionPinProvided = @"kHostSessionPinProvided";
 
 NSString* const kSessionDetails = @"kSessionDetails";
+NSString* const kSessionSupportsPairing = @"kSessionSupportsPairing";
 NSString* const kSessonStateErrorCode = @"kSessonStateErrorCode";
+
+NSString* const kHostSessionCreatePairing = @"kHostSessionCreatePairing";
+NSString* const kHostSessionHostName = @"kHostSessionHostName";
 NSString* const kHostSessionPin = @"kHostSessionPin";
 
 @interface RemotingClient () {
@@ -91,10 +97,18 @@
   info.host_os = base::SysNSStringToUTF8(hostInfo.hostOs);
   info.host_os_version = base::SysNSStringToUTF8(hostInfo.hostOsVersion);
   info.host_version = base::SysNSStringToUTF8(hostInfo.hostVersion);
-  // TODO(nicholss): If iOS supports pairing, pull the stored data and
-  // insert it here.
-  info.pairing_id = "";
-  info.pairing_secret = "";
+
+  NSDictionary* pairing =
+      [KeychainWrapper.instance pairingCredentialsForHost:hostInfo.hostId];
+  if (pairing) {
+    info.pairing_id =
+        base::SysNSStringToUTF8([pairing objectForKey:kKeychainPairingId]);
+    info.pairing_secret =
+        base::SysNSStringToUTF8([pairing objectForKey:kKeychainPairingSecret]);
+  } else {
+    info.pairing_id = "";
+    info.pairing_secret = "";
+  }
 
   // TODO(nicholss): I am not sure about the following fields yet.
   // info.capabilities =
@@ -120,10 +134,11 @@
         [[NSNotificationCenter defaultCenter]
             postNotificationName:kHostSessionStatusChanged
                           object:weakSelf
-                        userInfo:[NSDictionary
-                                     dictionaryWithObject:strongSelf
-                                                              ->_sessionDetails
-                                                   forKey:kSessionDetails]];
+                        userInfo:@{
+                          kSessionDetails : strongSelf->_sessionDetails,
+                          kSessionSupportsPairing :
+                              [NSNumber numberWithBool:pairing_supported],
+                        }];
       });
 
   // TODO(nicholss): Add audio support to iOS.
@@ -164,6 +179,18 @@
 
 - (void)hostSessionPinProvided:(NSNotification*)notification {
   NSString* pin = [[notification userInfo] objectForKey:kHostSessionPin];
+  NSString* name = UIDevice.currentDevice.name;
+  BOOL createPairing = [[[notification userInfo]
+      objectForKey:kHostSessionCreatePairing] boolValue];
+
+  // TODO(nicholss): Look into refactoring ProvideSecret. It is mis-named and
+  // does not use pin.
+  if (_session) {
+    _session->ProvideSecret(base::SysNSStringToUTF8(pin),
+                            (createPairing == YES),
+                            base::SysNSStringToUTF8(name));
+  }
+
   if (_secretFetchedCallback) {
     remoting::protocol::SecretFetchedCallback callback = _secretFetchedCallback;
     _runtime->network_task_runner()->PostTask(
@@ -267,13 +294,26 @@
 - (void)commitPairingCredentialsForHost:(NSString*)host
                                      id:(NSString*)id
                                  secret:(NSString*)secret {
-  NSLog(@"TODO(nicholss): implement this, commitPairingCredentialsForHost.");
+  [KeychainWrapper.instance commitPairingCredentialsForHost:host
+                                                         id:id
+                                                     secret:secret];
 }
 
 - (void)fetchThirdPartyTokenForUrl:(NSString*)tokenUrl
                           clientId:(NSString*)clientId
                              scope:(NSString*)scope {
-  NSLog(@"TODO(nicholss): implement this, fetchThirdPartyTokenForUrl.");
+  // Not supported for iOS yet.
+  _sessionDetails.state = SessionCancelled;
+  [self disconnectFromHost];
+  NSString* message = [NSString
+      stringWithFormat:@"[ThirdPartyAuth] Unable to authenticate with %@.",
+                       _sessionDetails.hostInfo.hostName];
+  [MDCSnackbarManager showMessage:[MDCSnackbarMessage messageWithText:message]];
+  [[NSNotificationCenter defaultCenter]
+      postNotificationName:kHostSessionStatusChanged
+                    object:self
+                  userInfo:[NSDictionary dictionaryWithObject:_sessionDetails
+                                                       forKey:kSessionDetails]];
 }
 
 - (void)setCapabilities:(NSString*)capabilities {
diff --git a/services/ui/gpu/gpu_service.cc b/services/ui/gpu/gpu_service.cc
index d466940..777a29a 100644
--- a/services/ui/gpu/gpu_service.cc
+++ b/services/ui/gpu/gpu_service.cc
@@ -429,13 +429,13 @@
   gpu_channel_manager_->RemoveChannel(client_id);
 }
 
-void GpuService::LoadedShader(const std::string& data) {
+void GpuService::LoadedShader(const std::string& key, const std::string& data) {
   if (io_runner_->BelongsToCurrentThread()) {
     main_runner_->PostTask(
-        FROM_HERE, base::Bind(&GpuService::LoadedShader, weak_ptr_, data));
+        FROM_HERE, base::Bind(&GpuService::LoadedShader, weak_ptr_, key, data));
     return;
   }
-  gpu_channel_manager_->PopulateShaderCache(data);
+  gpu_channel_manager_->PopulateShaderCache(key, data);
 }
 
 void GpuService::DestroyingVideoSurface(
diff --git a/services/ui/gpu/gpu_service.h b/services/ui/gpu/gpu_service.h
index def7f70..e92a25a 100644
--- a/services/ui/gpu/gpu_service.h
+++ b/services/ui/gpu/gpu_service.h
@@ -153,7 +153,7 @@
       const GetVideoMemoryUsageStatsCallback& callback) override;
   void RequestCompleteGpuInfo(
       const RequestCompleteGpuInfoCallback& callback) override;
-  void LoadedShader(const std::string& data) override;
+  void LoadedShader(const std::string& key, const std::string& data) override;
   void DestroyingVideoSurface(
       int32_t surface_id,
       const DestroyingVideoSurfaceCallback& callback) override;
diff --git a/services/ui/gpu/interfaces/gpu_service.mojom b/services/ui/gpu/interfaces/gpu_service.mojom
index b4020f6..2bf2abd 100644
--- a/services/ui/gpu/interfaces/gpu_service.mojom
+++ b/services/ui/gpu/interfaces/gpu_service.mojom
@@ -47,8 +47,10 @@
 
   RequestCompleteGpuInfo() => (gpu.mojom.GpuInfo gpu_info);
 
-  // Notify GPU that a shader was loaded from disk.
-  LoadedShader(string data);
+  // Notify GPU that a shader program was loaded from disk. Key is an
+  // SHA-1 hash, and data a binary blob with serialized program info.
+  // Note that this method is used only from a trusted process.
+  LoadedShader(string key, string data);
 
   // Tells GPU to release the surface because it's being destroyed.
   DestroyingVideoSurface(int32 surface_id) => ();
diff --git a/testing/scripts/run_telemetry_benchmark_as_googletest.py b/testing/scripts/run_telemetry_benchmark_as_googletest.py
index cadb30bd..a1343b3 100755
--- a/testing/scripts/run_telemetry_benchmark_as_googletest.py
+++ b/testing/scripts/run_telemetry_benchmark_as_googletest.py
@@ -66,6 +66,8 @@
     failures = []
     chartjson_results_present = '--output-format=chartjson' in rest_args
     chartresults = None
+    json_test_results_present = '--output-format=json-test-results' in rest_args
+    json_test_results = None
 
     results = None
     try:
@@ -97,6 +99,11 @@
             failures.append(name)
         valid = bool(rc == 0 or failures)
 
+      if json_test_results_present:
+        tempfile_name = os.path.join(tempfile_dir, 'test-results.json')
+        with open(tempfile_name) as f:
+          json_test_results = json.load(f)
+
     except Exception:
       traceback.print_exc()
       if results:
@@ -116,10 +123,13 @@
         open(args.isolated_script_test_chartjson_output, 'w')
       json.dump(chartresults, chartjson_output_file)
 
-    json.dump({
-        'valid': valid,
-        'failures': failures
-    }, args.isolated_script_test_output)
+    if not json_test_results_present:
+      json_test_results = {
+          'valid': valid,
+          'failures': failures
+      }
+
+    json.dump(json_test_results, args.isolated_script_test_output)
     return rc
 
   finally:
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/enable-features=NetworkService b/third_party/WebKit/LayoutTests/FlagExpectations/enable-features=NetworkService
index 3c5589f3..f6abf57a 100644
--- a/third_party/WebKit/LayoutTests/FlagExpectations/enable-features=NetworkService
+++ b/third_party/WebKit/LayoutTests/FlagExpectations/enable-features=NetworkService
@@ -1856,8 +1856,8 @@
 Bug(none) http/tests/appcache/offline-access.html [ Timeout ]
 Bug(none) http/tests/appcache/online-fallback-layering.html [ Timeout ]
 Bug(none) http/tests/appcache/online-whitelist.html [ Failure ]
-Bug(none) http/tests/appcache/reload.html [ Failure Timeout ]
-Bug(none) http/tests/appcache/remove-cache.html [ Timeout ]
+Bug(none) http/tests/appcache/reload.html [ Crash Failure Timeout ]
+Bug(none) http/tests/appcache/remove-cache.html [ Crash Failure Timeout ]
 Bug(none) http/tests/appcache/simple.html [ Timeout ]
 Bug(none) http/tests/appcache/top-frame-1.html [ Timeout ]
 Bug(none) http/tests/appcache/top-frame-2.html [ Timeout ]
@@ -2055,30 +2055,30 @@
 Bug(none) http/tests/fetch/serviceworker/thorough/scheme-data-base-https-other-https.html [ Timeout ]
 Bug(none) http/tests/fetch/serviceworker/thorough/scheme-data-other-https.html [ Timeout ]
 Bug(none) http/tests/fetch/serviceworker/thorough/scheme-data.html [ Timeout ]
-Bug(none) http/tests/fetch/window/block-mixed-content-base-https.html [ Timeout ]
-Bug(none) http/tests/fetch/window/block-mixed-content-nocors-base-https.html [ Timeout ]
+Bug(none) http/tests/fetch/window/block-mixed-content-base-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/block-mixed-content-nocors-base-https.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/window/body-mixin-base-https-other-https.html [ Failure Timeout ]
-Bug(none) http/tests/fetch/window/body-mixin.html [ Failure ]
-Bug(none) http/tests/fetch/window/cache-override-base-https-other-https.html [ Timeout ]
+Bug(none) http/tests/fetch/window/body-mixin.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/cache-override-base-https-other-https.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/window/fetch-base-https-other-https.html [ Failure Timeout ]
-Bug(none) http/tests/fetch/window/fetch.html [ Failure ]
-Bug(none) http/tests/fetch/window/filtered-response-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/window/filtered-response-other-https.html [ Failure ]
-Bug(none) http/tests/fetch/window/headers-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/window/headers-guard-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/window/referrer-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/window/request-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/window/request.html [ Failure ]
-Bug(none) http/tests/fetch/window/response-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/window/response-content-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/window/response-content.html [ Failure ]
-Bug(none) http/tests/fetch/window/response.html [ Failure ]
+Bug(none) http/tests/fetch/window/fetch.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/filtered-response-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/filtered-response-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/headers-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/headers-guard-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/referrer-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/request-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/request.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/response-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/response-content-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/response-content.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/response.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/window/stream-reader-base-https-other-https.html [ Failure Timeout ]
-Bug(none) http/tests/fetch/window/stream-reader.html [ Failure ]
+Bug(none) http/tests/fetch/window/stream-reader.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/window/thorough/access-control-base-https-other-https.html [ Failure Timeout ]
-Bug(none) http/tests/fetch/window/thorough/access-control.html [ Timeout ]
+Bug(none) http/tests/fetch/window/thorough/access-control.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/window/thorough/auth-base-https-other-https.html [ Failure Timeout ]
-Bug(none) http/tests/fetch/window/thorough/auth-nocors-base-https-other-https.html [ Timeout ]
+Bug(none) http/tests/fetch/window/thorough/auth-nocors-base-https-other-https.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/window/thorough/auth-nocors-other-https.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/window/thorough/auth-nocors.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/window/thorough/auth-other-https.html [ Failure Timeout ]
@@ -2089,65 +2089,65 @@
 Bug(none) http/tests/fetch/window/thorough/cookie-nocors.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/window/thorough/cookie-other-https.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/window/thorough/cookie.html [ Failure Timeout ]
-Bug(none) http/tests/fetch/window/thorough/cors-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/window/thorough/cors-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/window/thorough/cors-preflight-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/window/thorough/cors-preflight-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/window/thorough/cors-preflight.html [ Timeout ]
-Bug(none) http/tests/fetch/window/thorough/cors-preflight2-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/window/thorough/cors-preflight2-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/window/thorough/cors-preflight2.html [ Timeout ]
-Bug(none) http/tests/fetch/window/thorough/cors.html [ Timeout ]
+Bug(none) http/tests/fetch/window/thorough/cors-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/thorough/cors-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/thorough/cors-preflight-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/thorough/cors-preflight-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/thorough/cors-preflight.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/thorough/cors-preflight2-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/thorough/cors-preflight2-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/thorough/cors-preflight2.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/thorough/cors.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/window/thorough/nocors-base-https-other-https.html [ Failure Timeout ]
-Bug(none) http/tests/fetch/window/thorough/nocors-other-https.html [ Timeout ]
+Bug(none) http/tests/fetch/window/thorough/nocors-other-https.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/window/thorough/nocors.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/window/thorough/redirect-base-https-other-https.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/window/thorough/redirect-credentials-base-https-other-https.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/window/thorough/redirect-credentials-other-https.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/window/thorough/redirect-credentials.html [ Failure Timeout ]
-Bug(none) http/tests/fetch/window/thorough/redirect-loop-base-https-other-https.html [ Timeout ]
+Bug(none) http/tests/fetch/window/thorough/redirect-loop-base-https-other-https.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/window/thorough/redirect-loop-other-https.html [ Failure Timeout ]
-Bug(none) http/tests/fetch/window/thorough/redirect-loop.html [ Timeout ]
+Bug(none) http/tests/fetch/window/thorough/redirect-loop.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/window/thorough/redirect-nocors-base-https-other-https.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/window/thorough/redirect-nocors-other-https.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/window/thorough/redirect-nocors.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/window/thorough/redirect-other-https.html [ Failure Timeout ]
-Bug(none) http/tests/fetch/window/thorough/redirect-password-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/window/thorough/redirect-password-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/window/thorough/redirect-password.html [ Timeout ]
-Bug(none) http/tests/fetch/window/thorough/redirect.html [ Timeout ]
-Bug(none) http/tests/fetch/window/thorough/scheme-blob-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/window/thorough/scheme-blob-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/window/thorough/scheme-blob.html [ Timeout ]
-Bug(none) http/tests/fetch/window/thorough/scheme-data-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/window/thorough/scheme-data-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/window/thorough/scheme-data.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/block-mixed-content-base-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/block-mixed-content-nocors-base-https.html [ Timeout ]
+Bug(none) http/tests/fetch/window/thorough/redirect-password-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/thorough/redirect-password-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/thorough/redirect-password.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/thorough/redirect.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/thorough/scheme-blob-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/thorough/scheme-blob-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/thorough/scheme-blob.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/thorough/scheme-data-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/thorough/scheme-data-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/window/thorough/scheme-data.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/block-mixed-content-base-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/block-mixed-content-nocors-base-https.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/workers/body-mixin-base-https-other-https.html [ Failure Timeout ]
-Bug(none) http/tests/fetch/workers/body-mixin.html [ Failure ]
-Bug(none) http/tests/fetch/workers/cache-override-base-https-other-https.html [ Timeout ]
+Bug(none) http/tests/fetch/workers/body-mixin.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/cache-override-base-https-other-https.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/workers/fetch-base-https-other-https.html [ Failure Timeout ]
-Bug(none) http/tests/fetch/workers/fetch.html [ Failure ]
-Bug(none) http/tests/fetch/workers/filtered-response-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/filtered-response-other-https.html [ Failure ]
-Bug(none) http/tests/fetch/workers/headers-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/headers-guard-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/referrer-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/request-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/request.html [ Failure ]
-Bug(none) http/tests/fetch/workers/response-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/response-content-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/response-content.html [ Failure ]
-Bug(none) http/tests/fetch/workers/response.html [ Failure ]
+Bug(none) http/tests/fetch/workers/fetch.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/filtered-response-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/filtered-response-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/headers-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/headers-guard-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/referrer-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/request-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/request.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/response-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/response-content-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/response-content.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/response.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/workers/stream-reader-base-https-other-https.html [ Failure Timeout ]
-Bug(none) http/tests/fetch/workers/stream-reader.html [ Failure ]
-Bug(none) http/tests/fetch/workers/thorough/access-control-base-https-other-https.html [ Timeout ]
+Bug(none) http/tests/fetch/workers/stream-reader.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/access-control-base-https-other-https.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/workers/thorough/access-control.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/workers/thorough/auth-base-https-other-https.html [ Failure Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/auth-nocors-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/auth-nocors-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/auth-nocors.html [ Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/auth-nocors-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/auth-nocors-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/auth-nocors.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/workers/thorough/auth-other-https.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/workers/thorough/auth.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/workers/thorough/cookie-base-https-other-https.html [ Failure Timeout ]
@@ -2156,39 +2156,39 @@
 Bug(none) http/tests/fetch/workers/thorough/cookie-nocors.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/workers/thorough/cookie-other-https.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/workers/thorough/cookie.html [ Failure Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/cors-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/cors-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/cors-preflight-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/cors-preflight-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/cors-preflight.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/cors-preflight2-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/cors-preflight2-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/cors-preflight2.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/cors.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/nocors-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/nocors-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/nocors.html [ Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/cors-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/cors-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/cors-preflight-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/cors-preflight-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/cors-preflight.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/cors-preflight2-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/cors-preflight2-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/cors-preflight2.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/cors.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/nocors-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/nocors-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/nocors.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/workers/thorough/redirect-base-https-other-https.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/workers/thorough/redirect-credentials-base-https-other-https.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/workers/thorough/redirect-credentials-other-https.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/workers/thorough/redirect-credentials.html [ Failure Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/redirect-loop-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/redirect-loop-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/redirect-loop.html [ Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/redirect-loop-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/redirect-loop-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/redirect-loop.html [ Failure Timeout ]
 Bug(none) http/tests/fetch/workers/thorough/redirect-nocors-base-https-other-https.html [ Failure Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/redirect-nocors-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/redirect-nocors.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/redirect-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/redirect-password-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/redirect-password-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/redirect-password.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/redirect.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/scheme-blob-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/scheme-blob-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/scheme-blob.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/scheme-data-base-https-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/scheme-data-other-https.html [ Timeout ]
-Bug(none) http/tests/fetch/workers/thorough/scheme-data.html [ Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/redirect-nocors-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/redirect-nocors.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/redirect-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/redirect-password-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/redirect-password-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/redirect-password.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/redirect.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/scheme-blob-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/scheme-blob-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/scheme-blob.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/scheme-data-base-https-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/scheme-data-other-https.html [ Failure Timeout ]
+Bug(none) http/tests/fetch/workers/thorough/scheme-data.html [ Failure Timeout ]
 Bug(none) http/tests/fileapi/blob-url-in-subframe.html [ Crash Timeout ]
 Bug(none) http/tests/history/post-replace-state-reload.html [ Failure ]
 Bug(none) http/tests/history/push-state-in-new-frame.html [ Timeout ]
@@ -2291,7 +2291,7 @@
 Bug(none) http/tests/media/video-controls-overflow-menu-download-button.html [ Timeout ]
 Bug(none) http/tests/media/video-cookie.html [ Timeout ]
 Bug(none) http/tests/media/video-error-abort.html [ Timeout ]
-Bug(none) http/tests/media/video-in-iframe-crash.html [ Crash ]
+Bug(none) http/tests/media/video-in-iframe-crash.html [ Crash Timeout ]
 Bug(none) http/tests/media/video-load-metadata-decode-error.html [ Timeout ]
 Bug(none) http/tests/media/video-load-suspend.html [ Timeout ]
 Bug(none) http/tests/media/video-load-twice.html [ Timeout ]
@@ -2723,7 +2723,7 @@
 Bug(none) inspector/console/console-log-short-hand-method.html [ Failure ]
 Bug(none) inspector/console/console-object-constructor-name.html [ Failure ]
 Bug(none) inspector/console/console-tests.html [ Failure ]
-Bug(none) inspector/console/console-uncaught-promise.html [ Failure ]
+Bug(none) inspector/console/console-uncaught-promise.html [ Failure Timeout ]
 Bug(none) inspector/coverage/coverage-repeated.html [ Failure Timeout ]
 Bug(none) inspector/coverage/coverage-view-filter.html [ Failure ]
 Bug(none) inspector/coverage/coverage-view.html [ Failure Timeout ]
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index b9e4821..36156a8 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -92,9 +92,6 @@
 
 crbug.com/693568 virtual/gpu/fast/canvas/canvas-imageSmoothingQuality.html [ Failure ]
 
-# Can be broken into smaller tests, probably.
-crbug.com/709009 virtual/gpu/fast/canvas/getPutImageDataPairTest.html [ Timeout Pass ]
-
 # Looks like a filuare to get a paint on time. Test could be changed to use runAfterLayoutAndPaint
 # instead of a timeout, which may fix things.
 crbug.com/713049 images/color-profile-reflection.html [ Failure Pass ]
diff --git a/third_party/WebKit/LayoutTests/animations/font-size-using-ems.html b/third_party/WebKit/LayoutTests/animations/font-size-using-ems.html-disabled
similarity index 100%
rename from third_party/WebKit/LayoutTests/animations/font-size-using-ems.html
rename to third_party/WebKit/LayoutTests/animations/font-size-using-ems.html-disabled
diff --git a/third_party/WebKit/LayoutTests/crypto/subtle/aes-cbc/decrypt-failures-expected.txt b/third_party/WebKit/LayoutTests/crypto/subtle/aes-cbc/decrypt-failures-expected.txt
index c2e4a20..ce29588 100644
--- a/third_party/WebKit/LayoutTests/crypto/subtle/aes-cbc/decrypt-failures-expected.txt
+++ b/third_party/WebKit/LayoutTests/crypto/subtle/aes-cbc/decrypt-failures-expected.txt
@@ -4,19 +4,19 @@
 
 
 PASS: Decryption succeeded
-error is: OperationError: 
+error is: OperationError
 PASS: decrypting failed. ciphertext length: 0
-error is: OperationError: 
+error is: OperationError
 PASS: decrypting failed. ciphertext length: 79
-error is: OperationError: 
+error is: OperationError
 PASS: decrypting failed. ciphertext length: 64
-error is: OperationError: 
+error is: OperationError
 PASS: decrypting failed. ciphertext length: 1
-error is: OperationError: 
+error is: OperationError
 PASS: decrypting failed. ciphertext length: 15
-error is: OperationError: 
+error is: OperationError
 PASS: decrypting failed. ciphertext length: 16
-error is: OperationError: 
+error is: OperationError
 PASS: decrypting failed. ciphertext length: 17
 PASS successfullyParsed is true
 
diff --git a/third_party/WebKit/LayoutTests/crypto/subtle/ecdh/import-export-raw-expected.txt b/third_party/WebKit/LayoutTests/crypto/subtle/ecdh/import-export-raw-expected.txt
index f5abcf0cf..9ef69a168 100644
--- a/third_party/WebKit/LayoutTests/crypto/subtle/ecdh/import-export-raw-expected.txt
+++ b/third_party/WebKit/LayoutTests/crypto/subtle/ecdh/import-export-raw-expected.txt
@@ -20,7 +20,7 @@
 Exporting to raw...
 PASS: Exported to raw should be [044ea34391aa73885454bc45df3fdcc4a70262fa4621ffe25b5790590c340a4bd9265ef2b3f9a86e2959a960d90323465d60cd4a90d314c5de3f869ad0d4bf6c10] and was
 Importing invalid raw public key...
-error is: DataError: 
+error is: DataError
 PASS successfullyParsed is true
 
 TEST COMPLETE
diff --git a/third_party/WebKit/LayoutTests/crypto/subtle/rsa-oaep/plaintext-length-expected.txt b/third_party/WebKit/LayoutTests/crypto/subtle/rsa-oaep/plaintext-length-expected.txt
index 11f3ffd..6b6d992 100644
--- a/third_party/WebKit/LayoutTests/crypto/subtle/rsa-oaep/plaintext-length-expected.txt
+++ b/third_party/WebKit/LayoutTests/crypto/subtle/rsa-oaep/plaintext-length-expected.txt
@@ -7,7 +7,7 @@
 Encrypting a 214 byte buffer with RSA-OAEP SHA-1, 2048 bit key...
 PASS Succeeded
 Encrypting a 215 byte buffer...
-error is: OperationError: 
+error is: OperationError
 PASS Rejected
 PASS successfullyParsed is true
 
diff --git a/third_party/WebKit/LayoutTests/css-parser/line-break-values.html b/third_party/WebKit/LayoutTests/css-parser/line-break-values.html
new file mode 100644
index 0000000..40c9493e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/css-parser/line-break-values.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script>
+test(function() {
+    assert_true(CSS.supports("-webkit-line-break", "auto"));
+    assert_true(CSS.supports("-webkit-line-break", "loose"));
+    assert_true(CSS.supports("-webkit-line-break", "normal"));
+    assert_true(CSS.supports("-webkit-line-break", "strict"));
+}, '-webkit-line-break accepts valid keyword properties.');
+
+test(function() {
+    assert_true(CSS.supports("-webkit-line-break", "after-white-space"));
+}, '-webkit-line-break accepts the value after-white-space.');
+
+test(function() {
+    assert_false(CSS.supports("-webkit-line-break", "bogus"));
+}, '-webkit-line-break does not except obviously invalid values.');
+
+test(function() {
+    assert_true(CSS.supports("line-break", "auto"));
+    assert_true(CSS.supports("line-break", "loose"));
+    assert_true(CSS.supports("line-break", "normal"));
+    assert_true(CSS.supports("line-break", "strict"));
+}, 'line-break accepts valid keyword properties.');
+
+test(function() {
+    assert_false(CSS.supports("line-break", "after-white-space"));
+}, 'line-break does not accept the value after-white-space.');
+
+test(function() {
+    assert_false(CSS.supports("line-break", "bogus"));
+}, 'line-break does not except obviously invalid values.');
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json
index 92f98e3..85a7079e 100644
--- a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json
+++ b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json
@@ -32381,6 +32381,150 @@
      {}
     ]
    ],
+   "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-001.html": [
+    [
+     "/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-001.html",
+     [
+      [
+       "/css/reference/ref-filled-green-100px-square.xht",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-002.html": [
+    [
+     "/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-002.html",
+     [
+      [
+       "/css/reference/ref-filled-green-100px-square.xht",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-003.html": [
+    [
+     "/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-003.html",
+     [
+      [
+       "/css/reference/ref-filled-green-100px-square.xht",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-004.html": [
+    [
+     "/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-004.html",
+     [
+      [
+       "/css/reference/ref-filled-green-100px-square.xht",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-005.html": [
+    [
+     "/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-005.html",
+     [
+      [
+       "/css/reference/ref-filled-green-100px-square.xht",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-006.html": [
+    [
+     "/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-006.html",
+     [
+      [
+       "/css/reference/ref-filled-green-100px-square.xht",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-007.html": [
+    [
+     "/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-007.html",
+     [
+      [
+       "/css/reference/ref-filled-green-100px-square.xht",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-008.html": [
+    [
+     "/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-008.html",
+     [
+      [
+       "/css/reference/ref-filled-green-100px-square.xht",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-009.html": [
+    [
+     "/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-009.html",
+     [
+      [
+       "/css/reference/ref-filled-green-100px-square.xht",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-010.html": [
+    [
+     "/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-010.html",
+     [
+      [
+       "/css/reference/ref-filled-green-100px-square.xht",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-011.html": [
+    [
+     "/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-011.html",
+     [
+      [
+       "/css/reference/ref-filled-green-100px-square.xht",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-012.html": [
+    [
+     "/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-012.html",
+     [
+      [
+       "/css/reference/ref-filled-green-100px-square.xht",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-grid-1/grid-definition/fr-unit-with-percentage.html": [
     [
      "/css/css-grid-1/grid-definition/fr-unit-with-percentage.html",
@@ -57701,6 +57845,66 @@
      {}
     ]
    ],
+   "cssom-view/scrollingElement-quirks-dynamic-001.html": [
+    [
+     "/cssom-view/scrollingElement-quirks-dynamic-001.html",
+     [
+      [
+       "/cssom-view/scrollingElement-quirks-dynamic-001-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "cssom-view/scrollingElement-quirks-dynamic-002.html": [
+    [
+     "/cssom-view/scrollingElement-quirks-dynamic-002.html",
+     [
+      [
+       "/cssom-view/scrollingElement-quirks-dynamic-002-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "fonts/variations/variable-box-font.html": [
+    [
+     "/fonts/variations/variable-box-font.html",
+     [
+      [
+       "/fonts/variations/variable-box-font-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "fonts/variations/variable-gpos-m2b.html": [
+    [
+     "/fonts/variations/variable-gpos-m2b.html",
+     [
+      [
+       "/fonts/variations/variable-gpos-m2b-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "fonts/variations/variable-gsub.html": [
+    [
+     "/fonts/variations/variable-gsub.html",
+     [
+      [
+       "/fonts/variations/variable-gsub-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "html/dom/elements/global-attributes/dir_auto-EN-L.html": [
     [
      "/html/dom/elements/global-attributes/dir_auto-EN-L.html",
@@ -59405,6 +59609,18 @@
      {}
     ]
    ],
+   "html/rendering/non-replaced-elements/tables/table-direction.html": [
+    [
+     "/html/rendering/non-replaced-elements/tables/table-direction.html",
+     [
+      [
+       "/html/rendering/non-replaced-elements/tables/table-direction-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "html/rendering/non-replaced-elements/tables/table-layout.html": [
     [
      "/html/rendering/non-replaced-elements/tables/table-layout.html",
@@ -59417,6 +59633,30 @@
      {}
     ]
    ],
+   "html/rendering/non-replaced-elements/tables/table-row-direction.html": [
+    [
+     "/html/rendering/non-replaced-elements/tables/table-row-direction.html",
+     [
+      [
+       "/html/rendering/non-replaced-elements/tables/table-row-direction-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "html/rendering/non-replaced-elements/tables/table-row-group-direction.html": [
+    [
+     "/html/rendering/non-replaced-elements/tables/table-row-group-direction.html",
+     [
+      [
+       "/html/rendering/non-replaced-elements/tables/table-row-group-direction-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "html/rendering/non-replaced-elements/tables/table-width-150percent.html": [
     [
      "/html/rendering/non-replaced-elements/tables/table-width-150percent.html",
@@ -66359,11 +66599,6 @@
      {}
     ]
    ],
-   "WebIDL/ecmascript-binding/es-exceptions/exceptions-expected.txt": [
-    [
-     {}
-    ]
-   ],
    "WebIDL/ecmascript-binding/interface-prototype-object-expected.txt": [
     [
      {}
@@ -83744,6 +83979,16 @@
      {}
     ]
    ],
+   "cssom-view/scrollingElement-quirks-dynamic-001-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "cssom-view/scrollingElement-quirks-dynamic-002-ref.html": [
+    [
+     {}
+    ]
+   ],
    "cssom-view/support/1x1-green.png": [
     [
      {}
@@ -84379,6 +84624,11 @@
      {}
     ]
    ],
+   "dom/interfaces-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "dom/lists/DOMTokenList-coverage-for-attributes-expected.txt": [
     [
      {}
@@ -86919,6 +87169,26 @@
      {}
     ]
    ],
+   "fonts/variations/resources/variabletest_box.ttf": [
+    [
+     {}
+    ]
+   ],
+   "fonts/variations/variable-box-font-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "fonts/variations/variable-gpos-m2b-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "fonts/variations/variable-gsub-ref.html": [
+    [
+     {}
+    ]
+   ],
    "fullscreen/api/document-exit-fullscreen-active-document-expected.txt": [
     [
      {}
@@ -87004,6 +87274,11 @@
      {}
     ]
    ],
+   "hr-time/idlharness-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "hr-time/resources/now_frame.html": [
     [
      {}
@@ -94924,6 +95199,11 @@
      {}
     ]
    ],
+   "html/rendering/non-replaced-elements/tables/table-direction-ref.html": [
+    [
+     {}
+    ]
+   ],
    "html/rendering/non-replaced-elements/tables/table-layout-notref.html": [
     [
      {}
@@ -94934,6 +95214,16 @@
      {}
     ]
    ],
+   "html/rendering/non-replaced-elements/tables/table-row-direction-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "html/rendering/non-replaced-elements/tables/table-row-group-direction-ref.html": [
+    [
+     {}
+    ]
+   ],
    "html/rendering/non-replaced-elements/tables/table-width-150percent-ref.html": [
     [
      {}
@@ -100684,7 +100974,12 @@
      {}
     ]
    ],
-   "payment-request/allowpaymentrequest/setting-allowpaymentrequest-timing.https.sub-expected.txt": [
+   "payment-request/allowpaymentrequest/removing-allowpaymentrequest.https.sub-expected.txt": [
+    [
+     {}
+    ]
+   ],
+   "payment-request/allowpaymentrequest/setting-allowpaymentrequest.https.sub-expected.txt": [
     [
      {}
     ]
@@ -100704,6 +100999,11 @@
      {}
     ]
    ],
+   "payment-request/payment-request-canmakepayment-method.https-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "payment-request/payment-request-response-id.html": [
     [
      {}
@@ -100724,6 +101024,11 @@
      {}
     ]
    ],
+   "performance-timeline/idlharness-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "performance-timeline/performanceobservers.js": [
     [
      {}
@@ -103584,6 +103889,11 @@
      {}
     ]
    ],
+   "resource-timing/idlharness-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "resource-timing/iframe-setdomain.sub.html": [
     [
      {}
@@ -106384,6 +106694,11 @@
      {}
     ]
    ],
+   "url/urlsearchparams-delete-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "url/urlsearchparams-foreach-expected.txt": [
     [
      {}
@@ -106639,11 +106954,26 @@
      {}
     ]
    ],
+   "web-share/idlharness.https-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "web-share/resources/manual-helper.js": [
     [
      {}
     ]
    ],
+   "web-share/share-empty.https-expected.txt": [
+    [
+     {}
+    ]
+   ],
+   "web-share/share-without-user-gesture.https-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "webaudio/.gitignore": [
     [
      {}
@@ -106894,6 +107224,11 @@
      {}
     ]
    ],
+   "webrtc/RTCConfiguration-bundlePolicy-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "webrtc/RTCConfiguration-iceCandidatePoolSize-expected.txt": [
     [
      {}
@@ -106954,6 +107289,11 @@
      {}
     ]
    ],
+   "webrtc/RTCPeerConnection-getDefaultIceServers-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "webrtc/RTCPeerConnection-getTransceivers-expected.txt": [
     [
      {}
@@ -106999,6 +107339,11 @@
      {}
     ]
    ],
+   "webrtc/RTCRtpTransceiver-setDirection-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "webrtc/RTCSctpTransport-constructor-expected.txt": [
     [
      {}
@@ -160690,6 +161035,12 @@
      {}
     ]
    ],
+   "shadow-dom/slotchange-customelements.html": [
+    [
+     "/shadow-dom/slotchange-customelements.html",
+     {}
+    ]
+   ],
    "shadow-dom/slotchange-event.html": [
     [
      "/shadow-dom/slotchange-event.html",
@@ -178395,10 +178746,6 @@
    "f20cbaff1efb774748241b90778a0964f5671fee",
    "testharness"
   ],
-  "WebIDL/ecmascript-binding/es-exceptions/exceptions-expected.txt": [
-   "483ec81bab319fb4e50f4cdc5b1e8bb170e11d64",
-   "support"
-  ],
   "WebIDL/ecmascript-binding/es-exceptions/exceptions.html": [
    "fc4d6cf93ff64192ee325d7309ac267cf8ff5d6c",
    "testharness"
@@ -200395,6 +200742,54 @@
    "749d78928a228bb67878b3c088d36bcfd010aa08",
    "testharness"
   ],
+  "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-001.html": [
+   "afb6c282b7e952878c52c198579541ddca6afdb6",
+   "reftest"
+  ],
+  "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-002.html": [
+   "67a739c055f22f9de478d7d62a56bb9e65415cfb",
+   "reftest"
+  ],
+  "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-003.html": [
+   "3ccf5136030949e11a305349d317af193fe4f0ff",
+   "reftest"
+  ],
+  "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-004.html": [
+   "30a0be3371587b0871bbc480aa7e7098c4bc51e6",
+   "reftest"
+  ],
+  "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-005.html": [
+   "b4782cea1a8ec242087271555d55bae0f574fa91",
+   "reftest"
+  ],
+  "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-006.html": [
+   "c6a597f7e720491cd09ba3007cc50234639cdc33",
+   "reftest"
+  ],
+  "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-007.html": [
+   "2593b5f169c3fd0778cda370821572057ae25f0d",
+   "reftest"
+  ],
+  "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-008.html": [
+   "0eb8c0b70ff376de7608cb843838ea35556bef08",
+   "reftest"
+  ],
+  "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-009.html": [
+   "bc146287841851768ff6040e5fbc1d829677413e",
+   "reftest"
+  ],
+  "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-010.html": [
+   "2fe23a2f58c96b29b8741e35ca77b856431c5d19",
+   "reftest"
+  ],
+  "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-011.html": [
+   "83b810467e538bb0b8982224e2008aef0f48e84a",
+   "reftest"
+  ],
+  "css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-012.html": [
+   "1b2f571a79fe01b247ade6d6ea8514c7b46e2fd6",
+   "reftest"
+  ],
   "css/css-grid-1/grid-definition/fr-unit-with-percentage.html": [
    "e29ef66eb632f1f9834df3233f741fb51fed5eea",
    "reftest"
@@ -218799,6 +219194,22 @@
    "a799c737e7962865c3ed3e380a664cafe97dcfe8",
    "testharness"
   ],
+  "cssom-view/scrollingElement-quirks-dynamic-001-ref.html": [
+   "61f1de81876db66564e3edb8945312ed213d0b30",
+   "support"
+  ],
+  "cssom-view/scrollingElement-quirks-dynamic-001.html": [
+   "2bc3eee1754a051874ae5e8e229c661d7d8a9c1f",
+   "reftest"
+  ],
+  "cssom-view/scrollingElement-quirks-dynamic-002-ref.html": [
+   "85d4a5786e41af9ff89d6622fa329fbf79303c35",
+   "support"
+  ],
+  "cssom-view/scrollingElement-quirks-dynamic-002.html": [
+   "fcb2ceb93ba7b84c86e4c00e59e8febdc8ff8fed",
+   "reftest"
+  ],
   "cssom-view/scrollingElement.html": [
    "e3bc7ab9a646c1275e5dab9394df97d72ef8a42e",
    "testharness"
@@ -219931,6 +220342,10 @@
    "144554e0a9d53cdbb2c1f01d3dc169010db693b3",
    "testharness"
   ],
+  "dom/interfaces-expected.txt": [
+   "3c2b60141884d25035e1eadecff1f6d4fc815665",
+   "support"
+  ],
   "dom/interfaces.html": [
    "aae8f328bc52cbb17f47f78ace6d20c25a9c3acc",
    "testharness"
@@ -224743,6 +225158,34 @@
    "eb192c5fe03811a1b69578c92bf77d8abab89f29",
    "support"
   ],
+  "fonts/variations/resources/variabletest_box.ttf": [
+   "2fc122ff444d3ddef1f29ebde9a87827244ceeb0",
+   "support"
+  ],
+  "fonts/variations/variable-box-font-ref.html": [
+   "65ccabcdeaa322eeceaa646863eba52654e3149b",
+   "support"
+  ],
+  "fonts/variations/variable-box-font.html": [
+   "f3836fd9ea898b84bcef5dc259eeadbd4ecaeb68",
+   "reftest"
+  ],
+  "fonts/variations/variable-gpos-m2b-ref.html": [
+   "769b04d218db5f7d882512b17c70683281499481",
+   "support"
+  ],
+  "fonts/variations/variable-gpos-m2b.html": [
+   "af751a58338e49cc18b0b54c9451662d223f4977",
+   "reftest"
+  ],
+  "fonts/variations/variable-gsub-ref.html": [
+   "1b80955335d4a14f3e0d545a6b5165aadff05a87",
+   "support"
+  ],
+  "fonts/variations/variable-gsub.html": [
+   "2ae8392efc584c909f11ca04fb33a77f1b3c65ba",
+   "reftest"
+  ],
   "fullscreen/api/document-exit-fullscreen-active-document-expected.txt": [
    "0fe00e0f1b873cef51a3c142190ce821a76549ed",
    "support"
@@ -225059,6 +225502,10 @@
    "5c727eed4efd84b4b280b2584b7338217971a9e7",
    "testharness"
   ],
+  "hr-time/idlharness-expected.txt": [
+   "0e4c8b54fc09dce8b64143477ccb1253cc234f0c",
+   "support"
+  ],
   "hr-time/idlharness.html": [
    "2f10557869a4841ec9d826906d542109f606df43",
    "testharness"
@@ -234403,6 +234850,14 @@
    "699050ab3bcc8fc11d51c2ae4b9b10aee46876a5",
    "reftest"
   ],
+  "html/rendering/non-replaced-elements/tables/table-direction-ref.html": [
+   "97b303c46c7afcc29e0c56856028409c34682baa",
+   "support"
+  ],
+  "html/rendering/non-replaced-elements/tables/table-direction.html": [
+   "4c890c9b43bfec7a82a6abeee94d558db2b8f83e",
+   "reftest"
+  ],
   "html/rendering/non-replaced-elements/tables/table-layout-notref.html": [
    "a05fad3c06f94c3a617f2efdc589bbb3f5525983",
    "support"
@@ -234415,6 +234870,22 @@
    "ec05c435cfd09291184360db7e8b0c5af9c7ba31",
    "reftest"
   ],
+  "html/rendering/non-replaced-elements/tables/table-row-direction-ref.html": [
+   "5cf01204381d870738a7454080ea40904b2d0f0c",
+   "support"
+  ],
+  "html/rendering/non-replaced-elements/tables/table-row-direction.html": [
+   "433565959474b716d385c2d98d0976ae2121d6d5",
+   "reftest"
+  ],
+  "html/rendering/non-replaced-elements/tables/table-row-group-direction-ref.html": [
+   "23616d2840feaafbc6d06b1a1db18dd75d221a23",
+   "support"
+  ],
+  "html/rendering/non-replaced-elements/tables/table-row-group-direction.html": [
+   "d4b5cf18545eb48780d9a9a9bfa62ded7dc076bc",
+   "reftest"
+  ],
   "html/rendering/non-replaced-elements/tables/table-vspace-hspace-s.html": [
    "a93cbe26a0c5b9a7aeda1faf9db618f79aae8715",
    "testharness"
@@ -243744,7 +244215,7 @@
    "support"
   ],
   "navigation-timing/nav2_idlharness-expected.txt": [
-   "291fda445c50ea3938b8a381b014f1eec5f733cb",
+   "e917f77c64518a9fe633590941e455b3ae2b1437",
    "support"
   ],
   "navigation-timing/nav2_idlharness.html": [
@@ -243756,7 +244227,7 @@
    "testharness"
   ],
   "navigation-timing/nav2_test_attributes_values.html": [
-   "400c53f8d907e4eb8c9854ba73437510126d9074",
+   "e5f14ce9a725cbb3a88a73b5f137625334f1e3de",
    "testharness"
   ],
   "navigation-timing/nav2_test_document_open.html": [
@@ -250300,11 +250771,11 @@
    "testharness"
   ],
   "payment-request/allowpaymentrequest/active-document-cross-origin.https.sub.html": [
-   "7cb0cd48493781fe9a4f059365752fdc8d1ada6c",
+   "e96ac343e8533e8e90d3cbd4113a902b9e93e0bc",
    "testharness"
   ],
   "payment-request/allowpaymentrequest/active-document-same-origin.https.html": [
-   "0bcf7a59e03f70b56cc448910acab4188e225c86",
+   "8bbacc77b15f179bfc7ec4e08809e07181675030",
    "testharness"
   ],
   "payment-request/allowpaymentrequest/allowpaymentrequest-attribute-cross-origin-bc-containers.https.html": [
@@ -250316,7 +250787,7 @@
    "testharness"
   ],
   "payment-request/allowpaymentrequest/basic.https.html": [
-   "fa308475bf7e81d897f39c5df4d0cd2795df7da7",
+   "0e8a08f2e25a1cc3302d68322f504930ebd3fc8f",
    "testharness"
   ],
   "payment-request/allowpaymentrequest/common.sub.js": [
@@ -250324,7 +250795,7 @@
    "support"
   ],
   "payment-request/allowpaymentrequest/echo-PaymentRequest.html": [
-   "f330e448c1ac380bcaa23bc5bc15c3253aba18c4",
+   "1c0428fd7d3bdc4302bb061f6f99ce98d0e400b2",
    "support"
   ],
   "payment-request/allowpaymentrequest/no-attribute-cross-origin-bc-containers.https.html": [
@@ -250335,18 +250806,22 @@
    "2e42808a20c5ebe11720f01cdfab78dd2bf8221a",
    "testharness"
   ],
+  "payment-request/allowpaymentrequest/removing-allowpaymentrequest.https.sub-expected.txt": [
+   "7a03ccde67f470aa9cddc2d51c8e4a2eb9561b37",
+   "support"
+  ],
   "payment-request/allowpaymentrequest/removing-allowpaymentrequest.https.sub.html": [
    "41265b7c3d0e8d4c8f462f957627139f8aa5a3a3",
    "testharness"
   ],
-  "payment-request/allowpaymentrequest/setting-allowpaymentrequest-timing.https.sub-expected.txt": [
-   "7225ff302b23bf9fcc85e8d0af66cfdf7d65ff73",
-   "support"
-  ],
   "payment-request/allowpaymentrequest/setting-allowpaymentrequest-timing.https.sub.html": [
    "5eb37c0c6ad39187c4505a8cbe113c4b68e51c92",
    "testharness"
   ],
+  "payment-request/allowpaymentrequest/setting-allowpaymentrequest.https.sub-expected.txt": [
+   "4454910deefcb86191abb62940d4ee619b48476f",
+   "support"
+  ],
   "payment-request/allowpaymentrequest/setting-allowpaymentrequest.https.sub.html": [
    "e5be539c1b0ca1c571df1fc979fde5bf6482b43d",
    "testharness"
@@ -250356,7 +250831,7 @@
    "testharness"
   ],
   "payment-request/interfaces.https.html": [
-   "29af302db74de64e2bd1352ad92092a309d28c92",
+   "8a171d3a4fb4a384b56caa8648f04451a65007fe",
    "testharness"
   ],
   "payment-request/payment-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html": [
@@ -250368,7 +250843,7 @@
    "testharness"
   ],
   "payment-request/payment-allowed-by-feature-policy.https.sub.html": [
-   "f6c74e28d4f69c26215a81cdbd135ad448a8adce",
+   "a8d765889708b30535acb14c49ad186c1f56a4b5",
    "testharness"
   ],
   "payment-request/payment-allowed-by-feature-policy.https.sub.html.headers": [
@@ -250376,11 +250851,11 @@
    "support"
   ],
   "payment-request/payment-default-feature-policy.https.sub.html": [
-   "238606fd17c8c4a3ad90157cb5cf4b1b07b0016d",
+   "6980a706cca09eaeb2d63d6780a5e0a89ff52fa5",
    "testharness"
   ],
   "payment-request/payment-disabled-by-feature-policy.https.sub.html": [
-   "79bc619acc377b65bb16f91b2374105288f0f2b6",
+   "942104579b3710532d35bab01f9f387d5ed04fe0",
    "testharness"
   ],
   "payment-request/payment-disabled-by-feature-policy.https.sub.html.headers": [
@@ -250392,39 +250867,43 @@
    "support"
   ],
   "payment-request/payment-request-abort-method.https.html": [
-   "c9ee5af2ccd5ad364090807c8427f1d4624d3747",
+   "5c1ddf4e9e2a896036912983462f51f8ff6aa82b",
    "testharness"
   ],
+  "payment-request/payment-request-canmakepayment-method.https-expected.txt": [
+   "b03b33a8accbb265df006d5620f853ad118f0776",
+   "support"
+  ],
   "payment-request/payment-request-canmakepayment-method.https.html": [
-   "b20131bc3f2717212f9940920183d650ee111333",
+   "3b145e1e2164d5201ded51ce138aeeb4163e0261",
    "testharness"
   ],
   "payment-request/payment-request-constructor-crash.https.html": [
-   "dd2f95bd4d94a819c507e942b19a60de05a16971",
+   "9983391839ed64c41f9a7ecfb48a9b4abe6b497c",
    "testharness"
   ],
   "payment-request/payment-request-constructor.https.html": [
-   "44d2656f2990c51063254326521a02218a7fc500",
+   "786872782a815dcc32c6e93a14e691e4b9541c21",
    "testharness"
   ],
   "payment-request/payment-request-id.https.html": [
-   "3e74f97fdf39bb1ca9f2cb5596155705cd15b5b0",
+   "a90cf1e385cfee1c2090f4144e08b7cb051366d5",
    "testharness"
   ],
   "payment-request/payment-request-in-iframe.html": [
-   "26f2715d33e6d00e5ce03d7b07f35db2ac027acf",
+   "0a7cd9d70a64331eef4277db7e5ee91934b50b11",
    "testharness"
   ],
   "payment-request/payment-request-onshippingaddresschange-attribute.https.html": [
-   "9a16b563d8b5f355b73b84d01f61f910bab7eb18",
+   "c716788baf4b5e74c5bd5adbbd9f95d9ca98ce12",
    "testharness"
   ],
   "payment-request/payment-request-onshippingoptionchange-attribute.https.html": [
-   "439c524e66216aad471ecea680a36430f89d9af9",
+   "fc4772725ab7cab838d0d7bb97d0febeef8d093c",
    "testharness"
   ],
   "payment-request/payment-request-response-id.html": [
-   "88df88efdb1d44b56ac9758295f2e2920ae6c9ff",
+   "8feda1d3b4c071014d4c8c7898598c7d23086216",
    "support"
   ],
   "payment-request/payment-request-show-method.https-expected.txt": [
@@ -250432,7 +250911,7 @@
    "support"
   ],
   "payment-request/payment-request-show-method.https.html": [
-   "518136ad885f95172e578f6e2c165a559c51896b",
+   "7f58fa01b008be011158cbb834ef1ef1bec2bce5",
    "testharness"
   ],
   "payment-request/payment-request-update-event-constructor.http-expected.txt": [
@@ -250444,7 +250923,7 @@
    "testharness"
   ],
   "payment-request/payment-request-update-event-constructor.https.html": [
-   "6b546870fd384a5bf2106d25fd3159a72f8537b2",
+   "2a7df6827204f3dd03f5d4a3755f6ed96cdbbb0e",
    "testharness"
   ],
   "payment-request/payment-request-update-event-updatewith-method.https-expected.txt": [
@@ -250452,13 +250931,17 @@
    "support"
   ],
   "payment-request/payment-request-update-event-updatewith-method.https.html": [
-   "d85bca2ccb865c11c550a9d9c1d8770b2c68d6bd",
+   "08e63c4605bfa4838984aff13a82458ca2a6bdf8",
    "testharness"
   ],
   "performance-timeline/case-sensitivity.any.js": [
    "9c6b6edf19800a2730de2dfe601a7cd2503cf87d",
    "testharness"
   ],
+  "performance-timeline/idlharness-expected.txt": [
+   "f4d224a610cb977b6d28c0c9941f14e84b2e55c1",
+   "support"
+  ],
   "performance-timeline/idlharness.html": [
    "b021a9528875942d44b33c3fc3f4cd643194fad5",
    "testharness"
@@ -258743,6 +259226,10 @@
    "07abff794b79e5ec3a82ae654c00c88d63742684",
    "support"
   ],
+  "resource-timing/idlharness-expected.txt": [
+   "3a98bbc74b9ab03914d1f1bd5ee1c6acf55cfa6c",
+   "support"
+  ],
   "resource-timing/idlharness.html": [
    "60e2a57792827713ffd1176a0793da97c1ccf599",
    "testharness"
@@ -261579,6 +262066,10 @@
    "b6073e01e2544ffe2a8bb26946ac1cb5538ed195",
    "testharness"
   ],
+  "shadow-dom/slotchange-customelements.html": [
+   "8071607531cdb7c3881e08f5557ccd9a7ecf4e45",
+   "testharness"
+  ],
   "shadow-dom/slotchange-event.html": [
    "c72d9d156bf6772c3e5ea054310810b34a049b94",
    "testharness"
@@ -263543,6 +264034,10 @@
    "854e06efa9598f66705605bdef20c4a500ab2e9b",
    "testharness"
   ],
+  "url/urlsearchparams-delete-expected.txt": [
+   "68f39d2402b2f7590ae051d2d4ea4ab605b874d7",
+   "support"
+  ],
   "url/urlsearchparams-delete.html": [
    "b1fcda755e2a9e3308a222fe213abf0a255f0777",
    "testharness"
@@ -264267,6 +264762,10 @@
    "83c52be8280bba314116ff1337028ea7835ddf43",
    "testharness"
   ],
+  "web-share/idlharness.https-expected.txt": [
+   "97ec9fa4233da0c5332d43222fa25120aa188c9f",
+   "support"
+  ],
   "web-share/idlharness.https.html": [
    "17e370aefd324ffac6a6f4b0211fbaecd1781ad4",
    "testharness"
@@ -264279,6 +264778,10 @@
    "b4dcc0ac05c2a14907d55058f7d51190842fe0d3",
    "manual"
   ],
+  "web-share/share-empty.https-expected.txt": [
+   "95d08ab28f9a5139144b83005a1cd51444c23fad",
+   "support"
+  ],
   "web-share/share-empty.https.html": [
    "aa25dc3fef3c62d9b128866a2c48df204bfaa548",
    "testharness"
@@ -264343,6 +264846,10 @@
    "41dd5d1a50c21f9e77c6832b56195561ee29106f",
    "manual"
   ],
+  "web-share/share-without-user-gesture.https-expected.txt": [
+   "09e41666527d2016ca1499fd08cee5bdab25a990",
+   "support"
+  ],
   "web-share/share-without-user-gesture.https.html": [
    "d883b2469759722cf76c4624a1a81908c9aa5ae0",
    "testharness"
@@ -265031,6 +265538,10 @@
    "76e0c5f601c8ba4aefb06d1ebab8454c78fe07df",
    "testharness"
   ],
+  "webrtc/RTCConfiguration-bundlePolicy-expected.txt": [
+   "995ff82e73b802461b15b4717fabd9bbd1eac462",
+   "support"
+  ],
   "webrtc/RTCConfiguration-bundlePolicy.html": [
    "260ead036b3f4facd720dafbcaaa11040c145228",
    "testharness"
@@ -265112,7 +265623,7 @@
    "testharness"
   ],
   "webrtc/RTCPeerConnection-constructor-expected.txt": [
-   "62dfc7cd8198f45406d10afacdc13d539414801d",
+   "cf410b8c2f6faf04eb1985afa2ae5da6a3e42843",
    "support"
   ],
   "webrtc/RTCPeerConnection-constructor.html": [
@@ -265147,6 +265658,10 @@
    "27fb46922255203da0fc26a63808aeb98a60b640",
    "testharness"
   ],
+  "webrtc/RTCPeerConnection-getDefaultIceServers-expected.txt": [
+   "9d0ee7b3e0f71d1375e233516159b8216c58c324",
+   "support"
+  ],
   "webrtc/RTCPeerConnection-getDefaultIceServers.html": [
    "3b8f625e068cfd6ec3fe80bab276b2216fa8eda5",
    "testharness"
@@ -265231,6 +265746,10 @@
    "9e8eca4fa11cc72471bc48d98bec8e5936111334",
    "testharness"
   ],
+  "webrtc/RTCRtpTransceiver-setDirection-expected.txt": [
+   "ef8e1a07245c4387dcb2f15217b0fb2c3e8801a2",
+   "support"
+  ],
   "webrtc/RTCRtpTransceiver-setDirection.html": [
    "539eaba516eef7419c5e543d7218a41f850f5e7b",
    "testharness"
diff --git a/third_party/WebKit/LayoutTests/external/wpt/WebIDL/ecmascript-binding/es-exceptions/DOMException-custom-bindings.any-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/WebIDL/ecmascript-binding/es-exceptions/DOMException-custom-bindings.any-expected.txt
deleted file mode 100644
index 2a61639..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/WebIDL/ecmascript-binding/es-exceptions/DOMException-custom-bindings.any-expected.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-This is a testharness.js-based test.
-PASS Cannot construct without new 
-PASS inherits from Error: prototype-side 
-PASS does not inherit from Error: class-side 
-PASS message property descriptor 
-PASS message getter performs brand checks (i.e. is not [LenientThis] 
-PASS name property descriptor 
-PASS name getter performs brand checks (i.e. is not [LenientThis] 
-PASS code property descriptor 
-PASS code getter performs brand checks (i.e. is not [LenientThis] 
-PASS code property is not affected by shadowing the name property 
-PASS Object.prototype.toString behavior is like other interfaces 
-FAIL Inherits its toString() from Error.prototype assert_false: toString must not exist on DOMException.prototype expected false got true
-FAIL toString() behavior from Error.prototype applies as expected assert_equals: The default Error.prototype.toString() behavior must work on shadowed names and messages expected "new name: new message" but got "name: message"
-PASS DOMException.prototype.toString() applied to DOMException.prototype throws because of name/message brand checks 
-FAIL If the implementation has a stack property on normal errors, it also does on DOMExceptions assert_equals: The typeof values must match expected "string" but got "undefined"
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/external/wpt/WebIDL/ecmascript-binding/es-exceptions/DOMException-custom-bindings.any.worker-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/WebIDL/ecmascript-binding/es-exceptions/DOMException-custom-bindings.any.worker-expected.txt
deleted file mode 100644
index 2a61639..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/WebIDL/ecmascript-binding/es-exceptions/DOMException-custom-bindings.any.worker-expected.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-This is a testharness.js-based test.
-PASS Cannot construct without new 
-PASS inherits from Error: prototype-side 
-PASS does not inherit from Error: class-side 
-PASS message property descriptor 
-PASS message getter performs brand checks (i.e. is not [LenientThis] 
-PASS name property descriptor 
-PASS name getter performs brand checks (i.e. is not [LenientThis] 
-PASS code property descriptor 
-PASS code getter performs brand checks (i.e. is not [LenientThis] 
-PASS code property is not affected by shadowing the name property 
-PASS Object.prototype.toString behavior is like other interfaces 
-FAIL Inherits its toString() from Error.prototype assert_false: toString must not exist on DOMException.prototype expected false got true
-FAIL toString() behavior from Error.prototype applies as expected assert_equals: The default Error.prototype.toString() behavior must work on shadowed names and messages expected "new name: new message" but got "name: message"
-PASS DOMException.prototype.toString() applied to DOMException.prototype throws because of name/message brand checks 
-FAIL If the implementation has a stack property on normal errors, it also does on DOMExceptions assert_equals: The typeof values must match expected "string" but got "undefined"
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/external/wpt/cssom-view/scrollingElement-quirks-dynamic-001-ref.html b/third_party/WebKit/LayoutTests/external/wpt/cssom-view/scrollingElement-quirks-dynamic-001-ref.html
new file mode 100644
index 0000000..683198a
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/cssom-view/scrollingElement-quirks-dynamic-001-ref.html
@@ -0,0 +1,3 @@
+<!-- quirks mode -->
+<html style="overflow:scroll">
+<body style="overflow:scroll">The body box should have scrollbars.
diff --git a/third_party/WebKit/LayoutTests/external/wpt/cssom-view/scrollingElement-quirks-dynamic-001.html b/third_party/WebKit/LayoutTests/external/wpt/cssom-view/scrollingElement-quirks-dynamic-001.html
new file mode 100644
index 0000000..344e299
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/cssom-view/scrollingElement-quirks-dynamic-001.html
@@ -0,0 +1,17 @@
+<!-- quirks mode -->
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSSOM View Test: Dynamically changing scrollingElement to html in quirks mode</title>
+    <link rel="author" title="Rune Lillesveen" href="mailto:rune@opera.com">
+    <link rel="help" href="https://www.w3.org/TR/cssom-view-1/#dom-document-scrollingelement">
+    <link rel="match" href="scrollingElement-quirks-dynamic-001-ref.html">
+    <meta name="assert" content="Checks that setting the overflow on html to scroll will stop propagating body scrollbars to viewport.">
+  </head>
+  <body style="overflow:scroll">The body box should have scrollbars.
+    <script>
+      document.body.offsetTop; // force layout
+      document.documentElement.style.overflow = "scroll";
+    </script>
+  </body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/cssom-view/scrollingElement-quirks-dynamic-002-ref.html b/third_party/WebKit/LayoutTests/external/wpt/cssom-view/scrollingElement-quirks-dynamic-002-ref.html
new file mode 100644
index 0000000..c8a78398
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/cssom-view/scrollingElement-quirks-dynamic-002-ref.html
@@ -0,0 +1,2 @@
+<!-- quirks mode -->
+<body style="overflow:scroll">The body box should not have scrollbars.
diff --git a/third_party/WebKit/LayoutTests/external/wpt/cssom-view/scrollingElement-quirks-dynamic-002.html b/third_party/WebKit/LayoutTests/external/wpt/cssom-view/scrollingElement-quirks-dynamic-002.html
new file mode 100644
index 0000000..8495be2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/cssom-view/scrollingElement-quirks-dynamic-002.html
@@ -0,0 +1,17 @@
+<!-- quirks mode -->
+<html style="overflow:scroll">
+  <head>
+    <meta charset="utf-8">
+    <title>CSSOM View Test: Dynamically changing scrollingElement to body in quirks mode</title>
+    <link rel="author" title="Rune Lillesveen" href="mailto:rune@opera.com">
+    <link rel="help" href="https://www.w3.org/TR/cssom-view-1/#dom-document-scrollingelement">
+    <link rel="match" href="scrollingElement-quirks-dynamic-002-ref.html">
+    <meta name="assert" content="Checks that setting the overflow on html to visible will propagate body scrollbars to viewport.">
+  </head>
+  <body style="overflow:scroll">The body box should not have scrollbars.
+    <script>
+      document.body.offsetTop; // force layout
+      document.documentElement.style.overflow = "visible";
+    </script>
+  </body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/fast/events/hr-timestamp/constructed-events.html b/third_party/WebKit/LayoutTests/external/wpt/dom/events/Event-timestamp-high-resolution.html
similarity index 85%
rename from third_party/WebKit/LayoutTests/fast/events/hr-timestamp/constructed-events.html
rename to third_party/WebKit/LayoutTests/external/wpt/dom/events/Event-timestamp-high-resolution.html
index bbe5ed4..15aa014 100644
--- a/third_party/WebKit/LayoutTests/fast/events/hr-timestamp/constructed-events.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/dom/events/Event-timestamp-high-resolution.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 
-<script src="../../../resources/testharness.js"></script>
-<script src="../../../resources/testharnessreport.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
 <script type="text/javascript">
 'use strict';
 for (let eventType of [MouseEvent, KeyboardEvent, WheelEvent, GamepadEvent, FocusEvent]) {
diff --git a/third_party/WebKit/LayoutTests/fast/events/hr-timestamp/safe-resolution.html b/third_party/WebKit/LayoutTests/external/wpt/dom/events/Event-timestamp-safe-resolution.html
similarity index 82%
rename from third_party/WebKit/LayoutTests/fast/events/hr-timestamp/safe-resolution.html
rename to third_party/WebKit/LayoutTests/external/wpt/dom/events/Event-timestamp-safe-resolution.html
index bc3d519e..c19c7a0 100644
--- a/third_party/WebKit/LayoutTests/fast/events/hr-timestamp/safe-resolution.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/dom/events/Event-timestamp-safe-resolution.html
@@ -1,14 +1,14 @@
 <!DOCTYPE html>
 
-<script src="../../../resources/testharness.js"></script>
-<script src="../../../resources/testharnessreport.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
 <script type="text/javascript">
 'use strict';
 
 test(function() {
     let e1 = new MouseEvent('test1');
     let e2 = new MouseEvent('test2');
-    
+
     while (e1.timeStamp == e2.timeStamp)
         e2 = new MouseEvent('test2');
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/dom/events/EventTarget-constructible.any-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/dom/events/EventTarget-constructible.any-expected.txt
deleted file mode 100644
index a19cf28..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/dom/events/EventTarget-constructible.any-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-FAIL A constructed EventTarget can be used as expected Illegal constructor
-FAIL EventTarget can be subclassed Illegal constructor
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/external/wpt/dom/events/EventTarget-constructible.any.worker-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/dom/events/EventTarget-constructible.any.worker-expected.txt
deleted file mode 100644
index a19cf28..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/dom/events/EventTarget-constructible.any.worker-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-FAIL A constructed EventTarget can be used as expected Illegal constructor
-FAIL EventTarget can be subclassed Illegal constructor
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fonts/variations/resources/variabletest_box.ttf b/third_party/WebKit/LayoutTests/external/wpt/fonts/variations/resources/variabletest_box.ttf
new file mode 100644
index 0000000..53b5b242
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/fonts/variations/resources/variabletest_box.ttf
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fonts/variations/variable-box-font-ref.html b/third_party/WebKit/LayoutTests/external/wpt/fonts/variations/variable-box-font-ref.html
new file mode 100644
index 0000000..142b0aa
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/fonts/variations/variable-box-font-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<style>
+    @font-face {
+    font-family: variabletest_box;
+    src: url(resources/variabletest_box.ttf);
+    }
+
+    body {
+    font-family: variabletest_box, sans-serif;
+    font-size: 200px;
+    }
+</style>
+â–„ â–€
+<script>
+    document.fonts.ready.then(
+        () => { document.documentElement.classList.remove("reftest-wait"); });
+</script>
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fonts/variations/variable-box-font.html b/third_party/WebKit/LayoutTests/external/wpt/fonts/variations/variable-box-font.html
new file mode 100644
index 0000000..a9023fab
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/fonts/variations/variable-box-font.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<link rel="match" href="variable-box-font-ref.html">
+<meta charset="utf-8">
+<style>
+    @font-face {
+    font-family: variabletest_box;
+    src: url(resources/variabletest_box.ttf);
+    }
+
+    body {
+    font-family: variabletest_box,
+    sans-serif;
+    font-size: 200px;
+    }
+
+    .a_up {
+    font-variation-settings: "UPWD" 350;
+    }
+</style>
+<!-- The variabletest_box font has an A glyph that looks like a lower half box,
+  with deltas on the 'UPWD' variation axis that allow shifting the box up. At
+  350, the box is at the top. The font also has two glyphs for UPPER HALF BLOCK
+  and LOWER HALF BLOCK, which look identical to the respective variations of A.
+-->
+A <span class="a_up">A</span>
+<script>
+    document.fonts.ready.then(
+        () => { document.documentElement.classList.remove("reftest-wait"); });
+</script>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fonts/variations/variable-gpos-m2b-ref.html b/third_party/WebKit/LayoutTests/external/wpt/fonts/variations/variable-gpos-m2b-ref.html
new file mode 100644
index 0000000..c6b80b1b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/fonts/variations/variable-gpos-m2b-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<style>
+    @font-face {
+    font-family: variabletest_box;
+    src: url(resources/variabletest_box.ttf);
+    }
+
+    body {
+    font-family: variabletest_box, sans-serif;
+    sans-serif;
+    font-size: 100px;
+    }
+</style>
+M&#x033B; N&#x033B; O&#x033B;
+<script>
+    document.fonts.ready.then(
+        () => { document.documentElement.classList.remove("reftest-wait"); });
+</script>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fonts/variations/variable-gpos-m2b.html b/third_party/WebKit/LayoutTests/external/wpt/fonts/variations/variable-gpos-m2b.html
new file mode 100644
index 0000000..9b976e1
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/fonts/variations/variable-gpos-m2b.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<link rel="match" href="variable-gpos-m2b-ref.html">
+<meta charset="utf-8">
+<style>
+    @font-face {
+    font-family: variabletest_box;
+    src: url(resources/variabletest_box.ttf);
+    }
+
+    body {
+    font-family: variabletest_box, sans-serif;
+    sans-serif;
+    font-size: 100px;
+    }
+
+    .gpos_m2b_left {
+    font-variation-settings: "VM2B" 0;
+    }
+
+    .gpos_m2b_middle {
+    font-variation-settings: "VM2B" 500;
+    }
+
+    .gpos_m2b_right {
+    font-variation-settings: "VM2B" 1000;
+    }
+</style>
+<!-- The variabletest_box font has an M glyph saying "m2b pos" that combines
+     with the combining box below. And it has a glyph for combining box below
+     whose mark anchor can be shifted horizontally using the VM2B axis. The font
+     also has N and O glyphs which have fixed shifted base anchor points at the
+     middle and at the right position. In this ref test we check whether
+     applying the VM2B axis works as expected and shifts the mark anchor point
+     left so that the combining mark is placed correctly at the middle and at
+     the right position. The VM2B rendering must be identical to the
+     conventional rendering with the fixed base anchor points. -->
+<span class="gpos_m2b_left">M&#x033B;</span>
+<span class="gpos_m2b_middle">M&#x033B;</span>
+<span class="gpos_m2b_right">M&#x033B;</span>
+<script>
+    document.fonts.ready.then(
+        () => { document.documentElement.classList.remove("reftest-wait"); });
+</script>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fonts/variations/variable-gsub-ref.html b/third_party/WebKit/LayoutTests/external/wpt/fonts/variations/variable-gsub-ref.html
new file mode 100644
index 0000000..3b1f7f43
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/fonts/variations/variable-gsub-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<style>
+    @font-face {
+    font-family: variabletest_box;
+    src: url(resources/variabletest_box.ttf);
+    }
+
+    body {
+    font-family: variabletest_box, sans-serif;
+    sans-serif;
+    font-size: 100px;
+    }
+</style>
+r R
+<script>
+    document.fonts.ready.then(
+        () => { document.documentElement.classList.remove("reftest-wait"); });
+</script>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fonts/variations/variable-gsub.html b/third_party/WebKit/LayoutTests/external/wpt/fonts/variations/variable-gsub.html
new file mode 100644
index 0000000..ed432f6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/fonts/variations/variable-gsub.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<link rel="match" href="variable-gsub-ref.html">
+<meta charset="utf-8">
+<style>
+    @font-face {
+    font-family: variabletest_box;
+    src: url(resources/variabletest_box.ttf);
+    }
+
+    body {
+    font-family: variabletest_box, sans-serif;
+    sans-serif;
+    font-size: 100px;
+    }
+
+    .rvrn_replaced {
+    font-variation-settings: "FVTT" 10;
+    }
+</style>
+<!-- The variabletest_box font has an r glyph that says "rvrn base" and has
+     this as a name as well. And it has a glyph for R that says "rvrn subst"
+     where rvrn stands for the required Required Variation Alternates
+     feature. The font has an 'FVTT' axis ranging from 0 to 10, where it uses
+     a single substitution glyph lookup table for axis values starting from
+     5, which then replaces the rvrn_base glyph with the rvrn_subst
+     glyph. So in this reftest the substituted glyph for lowercase r
+     should visually match the uppercase R glyph, both show "rvrn subst". -->
+r <span class="rvrn_replaced">r</span>
+<script>
+    document.fonts.ready.then(
+        () => { document.documentElement.classList.remove("reftest-wait"); });
+</script>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/table-direction-ref.html b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/table-direction-ref.html
new file mode 100644
index 0000000..2bbd6c0
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/table-direction-ref.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Table direction</title>
+
+<style>
+  table {
+    border-collapse: collapse;
+  }
+
+  td {
+    border: 2px solid black;
+    width: 20px;
+    height: 20px;
+  }
+
+  td.special {
+    border-left: 5px solid green;
+    border-right: 5px solid blue;
+  }
+</style>
+
+Normal table:
+<table>
+  <tr>
+    <td></td>
+    <td class="special"></td>
+  </tr>
+</table>
+
+<hr>
+
+RTL table:
+<table>
+  <tr>
+    <td class="special"></td>
+    <td></td>
+  </tr>
+</table>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/table-direction.html b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/table-direction.html
new file mode 100644
index 0000000..a3de413
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/table-direction.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<link rel="match" href="table-direction-ref.html">
+<title>Table direction</title>
+
+<style>
+  table {
+    border-collapse: collapse;
+  }
+
+  td {
+    border: 2px solid black;
+    width: 20px;
+    height: 20px;
+  }
+
+  td.special {
+    border-left: 5px solid green;
+    border-right: 5px solid blue;
+  }
+</style>
+
+Normal table:
+<table>
+  <tr>
+    <td></td>
+    <td class="special"></td>
+  </tr>
+</table>
+
+<hr>
+
+RTL table:
+<table style="direction: rtl">
+  <tr>
+    <td></td>
+    <td class="special"></td>
+  </tr>
+</table>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/table-row-direction-ref.html b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/table-row-direction-ref.html
new file mode 100644
index 0000000..dab3163
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/table-row-direction-ref.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<title>Table row direction</title>
+
+<style>
+  table {
+    border-collapse: collapse;
+  }
+
+  td {
+    border: 2px solid black;
+    width: 20px;
+    height: 20px;
+  }
+
+  td.special {
+    border-left: 5px solid green;
+    border-right: 5px solid blue;
+  }
+</style>
+
+Normal table with LTR and RTL rows:
+<table>
+  <tr>
+    <td></td>
+    <td class="special"></td>
+  </tr>
+  <tr>
+    <td></td>
+    <td class="special"></td>
+  </tr>
+</table>
+
+<hr>
+
+RTL table with LTR and RTL rows:
+<table>
+  <tr>
+    <td class="special"></td>
+    <td></td>
+  </tr>
+  <tr>
+    <td class="special"></td>
+    <td></td>
+  </tr>
+</table>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/table-row-direction.html b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/table-row-direction.html
new file mode 100644
index 0000000..64ed5a6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/table-row-direction.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<link rel="match" href="table-row-direction-ref.html">
+<title>Table row direction</title>
+
+<style>
+  table {
+    border-collapse: collapse;
+  }
+
+  td {
+    border: 2px solid black;
+    width: 20px;
+    height: 20px;
+  }
+
+  td.special {
+    border-left: 5px solid green;
+    border-right: 5px solid blue;
+  }
+</style>
+
+Normal table with LTR and RTL rows:
+<table>
+  <tr style="direction: ltr">
+    <td></td>
+    <td class="special"></td>
+  </tr>
+  <tr style="direction: rtl">
+    <td></td>
+    <td class="special"></td>
+  </tr>
+</table>
+
+<hr>
+
+RTL table with LTR and RTL rows:
+<table style="direction: rtl">
+  <tr style="direction: ltr">
+    <td></td>
+    <td class="special"></td>
+  </tr>
+  <tr style="direction: rtl">
+    <td></td>
+    <td class="special"></td>
+  </tr>
+</table>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/table-row-group-direction-ref.html b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/table-row-group-direction-ref.html
new file mode 100644
index 0000000..0f3e03f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/table-row-group-direction-ref.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<title>Table row-group direction</title>
+
+<style>
+  table {
+    border-collapse: collapse;
+  }
+
+  td {
+    border: 2px solid black;
+    width: 20px;
+    height: 20px;
+  }
+
+  td.special {
+    border-left: 5px solid green;
+    border-right: 5px solid blue;
+  }
+</style>
+
+Normal table with LTR and RTL row groups:
+<table>
+  <tr>
+    <td></td>
+    <td class="special"></td>
+  </tr>
+  <tr>
+    <td></td>
+    <td class="special"></td>
+  </tr>
+</table>
+
+<hr>
+
+RTL table with LTR and RTL row groups:
+<table>
+  <tr>
+    <td class="special"></td>
+    <td></td>
+  </tr>
+  <tr>
+    <td class="special"></td>
+    <td></td>
+  </tr>
+</table>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/table-row-group-direction.html b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/table-row-group-direction.html
new file mode 100644
index 0000000..385672f1
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/table-row-group-direction.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<link rel="match" href="table-row-group-direction-ref.html">
+<title>Table row-group direction</title>
+
+<style>
+  table {
+    border-collapse: collapse;
+  }
+
+  td {
+    border: 2px solid black;
+    width: 20px;
+    height: 20px;
+  }
+
+  td.special {
+    border-left: 5px solid green;
+    border-right: 5px solid blue;
+  }
+</style>
+
+Normal table with LTR and RTL row groups:
+<table>
+  <tbody style="direction: ltr">
+    <tr>
+      <td></td>
+      <td class="special"></td>
+    </tr>
+  </tbody>
+  <tbody style="direction: rtl">
+    <tr>
+      <td></td>
+      <td class="special"></td>
+    </tr>
+  </tbody>
+</table>
+
+<hr>
+
+RTL table with LTR and RTL row groups:
+<table style="direction: rtl">
+  <tbody style="direction: ltr">
+    <tr>
+      <td></td>
+      <td class="special"></td>
+    </tr>
+  </tbody>
+  <tbody style="direction: rtl">
+    <tr>
+      <td></td>
+      <td class="special"></td>
+    </tr>
+  </tbody>
+</table>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/active-document-cross-origin.https.sub-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/active-document-cross-origin.https.sub-expected.txt
new file mode 100644
index 0000000..aaf8b3c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/active-document-cross-origin.https.sub-expected.txt
@@ -0,0 +1,6 @@
+This is a testharness.js-based test.
+FAIL PaymentRequest <iframe allowpaymentrequest> in non-active document (cross-origin) assert_throws: function "() => {
+        new grabbedPaymentRequest(...paymentArgs);
+      }" threw object "TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence." ("TypeError") expected object "[object Object]" ("SecurityError")
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/active-document-cross-origin.https.sub.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/active-document-cross-origin.https.sub.html
index fbb80e3..48f6d90 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/active-document-cross-origin.https.sub.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/active-document-cross-origin.https.sub.html
@@ -7,7 +7,7 @@
 <script>
 async_test((t) => {
   const iframe = document.getElementById('iframe');
-  const paymentArgs = [[{supportedMethods: ['foo']}], {total: {label: 'label', amount: {currency: 'USD', value: '5.00'}}}];
+  const paymentArgs = [[{supportedMethods: 'foo'}], {total: {label: 'label', amount: {currency: 'USD', value: '5.00'}}}];
 
   onload = () => {
     const win = window[0];
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/active-document-same-origin.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/active-document-same-origin.https-expected.txt
new file mode 100644
index 0000000..1a3c4d6c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/active-document-same-origin.https-expected.txt
@@ -0,0 +1,6 @@
+This is a testharness.js-based test.
+FAIL PaymentRequest <iframe allowpaymentrequest> in non-active document (same-origin) assert_throws: function "() => {
+        new grabbedPaymentRequest(...paymentArgs);
+      }" threw object "TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence." ("TypeError") expected object "[object Object]" ("SecurityError")
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/active-document-same-origin.https.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/active-document-same-origin.https.html
index 69a738a..bbcc026d5 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/active-document-same-origin.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/active-document-same-origin.https.html
@@ -6,7 +6,7 @@
 <script>
 async_test((t) => {
   const iframe = document.getElementById('iframe');
-  const paymentArgs = [[{supportedMethods: ['foo']}], {total: {label: 'label', amount: {currency: 'USD', value: '5.00'}}}];
+  const paymentArgs = [[{supportedMethods: 'foo'}], {total: {label: 'label', amount: {currency: 'USD', value: '5.00'}}}];
 
   onload = () => {
     const win = window[0];
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/allowpaymentrequest-attribute-cross-origin-bc-containers.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/allowpaymentrequest-attribute-cross-origin-bc-containers.https-expected.txt
new file mode 100644
index 0000000..9ef152b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/allowpaymentrequest-attribute-cross-origin-bc-containers.https-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+FAIL iframe assert_equals: expected "Success" but got "Exception"
+FAIL frame assert_array_equals: property 0, expected true but got false
+FAIL object assert_array_equals: property 0, expected true but got false
+FAIL embed assert_array_equals: property 0, expected true but got false
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/allowpaymentrequest-attribute-same-origin-bc-containers.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/allowpaymentrequest-attribute-same-origin-bc-containers.https-expected.txt
new file mode 100644
index 0000000..ab65a93
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/allowpaymentrequest-attribute-same-origin-bc-containers.https-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+FAIL iframe assert_equals: expected "Success" but got "Exception"
+FAIL frame assert_equals: expected "Success" but got "Exception"
+FAIL object assert_equals: expected "Success" but got "Exception"
+FAIL embed assert_equals: expected "Success" but got "Exception"
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/basic.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/basic.https-expected.txt
new file mode 100644
index 0000000..c925dae
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/basic.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL PaymentRequest <iframe allowpaymentrequest> basic Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/basic.https.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/basic.https.html
index 841880e..80a6a22 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/basic.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/basic.https.html
@@ -5,7 +5,7 @@
 <iframe id="iframe" allowpaymentrequest></iframe>
 <script>
 async_test((t) => {
-  const paymentArgs = [[{supportedMethods: ['foo']}], {total: {label: 'label', amount: {currency: 'USD', value: '5.00'}}}];
+  const paymentArgs = [[{supportedMethods: 'foo'}], {total: {label: 'label', amount: {currency: 'USD', value: '5.00'}}}];
 
   onload = t.step_func_done(() => {
     new window[0].PaymentRequest(...paymentArgs);
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/echo-PaymentRequest.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/echo-PaymentRequest.html
index 15fa1a1e..f18b16e 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/echo-PaymentRequest.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/echo-PaymentRequest.html
@@ -1,7 +1,7 @@
 <!doctype html>
 <script>
 window.onmessage = (e) => {
-  const paymentArgs = [[{supportedMethods: ['foo']}], {total: {label: 'label', amount: {currency: 'USD', value: '5.00'}}}];
+  const paymentArgs = [[{supportedMethods: 'foo'}], {total: {label: 'label', amount: {currency: 'USD', value: '5.00'}}}];
 
   if (e.data === 'What is the result of new PaymentRequest(...)?') {
     const result = {urlQuery: location.search.substring(1)}; // Used to distinguish subtests
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/no-attribute-cross-origin-bc-containers.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/no-attribute-cross-origin-bc-containers.https-expected.txt
new file mode 100644
index 0000000..d55bf82b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/no-attribute-cross-origin-bc-containers.https-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+FAIL iframe assert_array_equals: property 0, expected true but got false
+FAIL frame assert_array_equals: property 0, expected true but got false
+FAIL object assert_array_equals: property 0, expected true but got false
+FAIL embed assert_array_equals: property 0, expected true but got false
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/no-attribute-same-origin-bc-containers.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/no-attribute-same-origin-bc-containers.https-expected.txt
new file mode 100644
index 0000000..ab65a93
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/no-attribute-same-origin-bc-containers.https-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+FAIL iframe assert_equals: expected "Success" but got "Exception"
+FAIL frame assert_equals: expected "Success" but got "Exception"
+FAIL object assert_equals: expected "Success" but got "Exception"
+FAIL embed assert_equals: expected "Success" but got "Exception"
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/removing-allowpaymentrequest.https.sub-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/removing-allowpaymentrequest.https.sub-expected.txt
index 533d6d6..8754c0b 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/removing-allowpaymentrequest.https.sub-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/removing-allowpaymentrequest.https.sub-expected.txt
@@ -1,4 +1,4 @@
 This is a testharness.js-based test.
-FAIL PaymentRequest removing allowpaymentrequest after load and then navigating assert_equals: after navigation expected "Exception" but got "Success"
+FAIL PaymentRequest removing allowpaymentrequest after load and then navigating assert_equals: before navigation expected "Success" but got "Exception"
 Harness: the test ran to completion.
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/setting-allowpaymentrequest-timing.https.sub-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/setting-allowpaymentrequest-timing.https.sub-expected.txt
new file mode 100644
index 0000000..24a3a7fc
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/setting-allowpaymentrequest-timing.https.sub-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL PaymentRequest setting allowpaymentrequest after document creation, before response assert_equals: expected "Success" but got "Exception"
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/setting-allowpaymentrequest.https.sub-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/setting-allowpaymentrequest.https.sub-expected.txt
index b1372064..e6b6b53 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/setting-allowpaymentrequest.https.sub-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/allowpaymentrequest/setting-allowpaymentrequest.https.sub-expected.txt
@@ -1,4 +1,4 @@
 This is a testharness.js-based test.
-FAIL PaymentRequest setting allowpaymentrequest after load and then navigating assert_equals: after navigation expected "Success" but got "Exception"
+FAIL PaymentRequest setting allowpaymentrequest after load and then navigating assert_array_equals: before navigation property 0, expected true but got false
 Harness: the test ran to completion.
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/interfaces.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/interfaces.https-expected.txt
new file mode 100644
index 0000000..170486c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/interfaces.https-expected.txt
@@ -0,0 +1,64 @@
+This is a testharness.js-based test.
+PASS PaymentRequest interface: existence and properties of interface object 
+PASS PaymentRequest interface object length 
+PASS PaymentRequest interface object name 
+PASS PaymentRequest interface: existence and properties of interface prototype object 
+PASS PaymentRequest interface: existence and properties of interface prototype object's "constructor" property 
+PASS PaymentRequest interface: operation show() 
+PASS PaymentRequest interface: operation abort() 
+PASS PaymentRequest interface: operation canMakePayment() 
+PASS PaymentRequest interface: attribute id 
+PASS PaymentRequest interface: attribute shippingAddress 
+PASS PaymentRequest interface: attribute shippingOption 
+PASS PaymentRequest interface: attribute shippingType 
+PASS PaymentRequest interface: attribute onshippingaddresschange 
+PASS PaymentRequest interface: attribute onshippingoptionchange 
+FAIL PaymentRequest must be primary interface of new PaymentRequest([{supportedMethods: 'foo'}], {total: {label: 'bar', amount: {currency: 'BAZ', value: '0'}}}) assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence."
+FAIL Stringification of new PaymentRequest([{supportedMethods: 'foo'}], {total: {label: 'bar', amount: {currency: 'BAZ', value: '0'}}}) assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence."
+FAIL PaymentRequest interface: new PaymentRequest([{supportedMethods: 'foo'}], {total: {label: 'bar', amount: {currency: 'BAZ', value: '0'}}}) must inherit property "show" with the proper type (0) assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence."
+FAIL PaymentRequest interface: new PaymentRequest([{supportedMethods: 'foo'}], {total: {label: 'bar', amount: {currency: 'BAZ', value: '0'}}}) must inherit property "abort" with the proper type (1) assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence."
+FAIL PaymentRequest interface: new PaymentRequest([{supportedMethods: 'foo'}], {total: {label: 'bar', amount: {currency: 'BAZ', value: '0'}}}) must inherit property "canMakePayment" with the proper type (2) assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence."
+FAIL PaymentRequest interface: new PaymentRequest([{supportedMethods: 'foo'}], {total: {label: 'bar', amount: {currency: 'BAZ', value: '0'}}}) must inherit property "id" with the proper type (3) assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence."
+FAIL PaymentRequest interface: new PaymentRequest([{supportedMethods: 'foo'}], {total: {label: 'bar', amount: {currency: 'BAZ', value: '0'}}}) must inherit property "shippingAddress" with the proper type (4) assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence."
+FAIL PaymentRequest interface: new PaymentRequest([{supportedMethods: 'foo'}], {total: {label: 'bar', amount: {currency: 'BAZ', value: '0'}}}) must inherit property "shippingOption" with the proper type (5) assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence."
+FAIL PaymentRequest interface: new PaymentRequest([{supportedMethods: 'foo'}], {total: {label: 'bar', amount: {currency: 'BAZ', value: '0'}}}) must inherit property "shippingType" with the proper type (6) assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence."
+FAIL PaymentRequest interface: new PaymentRequest([{supportedMethods: 'foo'}], {total: {label: 'bar', amount: {currency: 'BAZ', value: '0'}}}) must inherit property "onshippingaddresschange" with the proper type (7) assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence."
+FAIL PaymentRequest interface: new PaymentRequest([{supportedMethods: 'foo'}], {total: {label: 'bar', amount: {currency: 'BAZ', value: '0'}}}) must inherit property "onshippingoptionchange" with the proper type (8) assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence."
+PASS PaymentAddress interface: existence and properties of interface object 
+PASS PaymentAddress interface object length 
+PASS PaymentAddress interface object name 
+PASS PaymentAddress interface: existence and properties of interface prototype object 
+PASS PaymentAddress interface: existence and properties of interface prototype object's "constructor" property 
+PASS PaymentAddress interface: attribute country 
+PASS PaymentAddress interface: attribute addressLine 
+PASS PaymentAddress interface: attribute region 
+PASS PaymentAddress interface: attribute city 
+PASS PaymentAddress interface: attribute dependentLocality 
+PASS PaymentAddress interface: attribute postalCode 
+PASS PaymentAddress interface: attribute sortingCode 
+PASS PaymentAddress interface: attribute languageCode 
+PASS PaymentAddress interface: attribute organization 
+PASS PaymentAddress interface: attribute recipient 
+PASS PaymentAddress interface: attribute phone 
+PASS PaymentResponse interface: existence and properties of interface object 
+PASS PaymentResponse interface object length 
+PASS PaymentResponse interface object name 
+PASS PaymentResponse interface: existence and properties of interface prototype object 
+PASS PaymentResponse interface: existence and properties of interface prototype object's "constructor" property 
+PASS PaymentResponse interface: attribute requestId 
+PASS PaymentResponse interface: attribute methodName 
+PASS PaymentResponse interface: attribute details 
+PASS PaymentResponse interface: attribute shippingAddress 
+PASS PaymentResponse interface: attribute shippingOption 
+PASS PaymentResponse interface: attribute payerName 
+PASS PaymentResponse interface: attribute payerEmail 
+PASS PaymentResponse interface: attribute payerPhone 
+PASS PaymentResponse interface: operation complete(PaymentComplete) 
+PASS PaymentRequestUpdateEvent interface: existence and properties of interface object 
+PASS PaymentRequestUpdateEvent interface object length 
+PASS PaymentRequestUpdateEvent interface object name 
+PASS PaymentRequestUpdateEvent interface: existence and properties of interface prototype object 
+PASS PaymentRequestUpdateEvent interface: existence and properties of interface prototype object's "constructor" property 
+PASS PaymentRequestUpdateEvent interface: operation updateWith([object Object]) 
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/interfaces.https.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/interfaces.https.html
index bca60a6..bc76247 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/interfaces.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/interfaces.https.html
@@ -29,8 +29,8 @@
              attribute EventHandler         onshippingoptionchange;
 };
 dictionary PaymentMethodData {
-    required sequence<DOMString> supportedMethods;
-             object              data;
+    required DOMString supportedMethods;
+             object    data;
 };
 dictionary PaymentCurrencyAmount {
     required DOMString currency;
@@ -51,7 +51,7 @@
     PaymentItem total;
 };
 dictionary PaymentDetailsModifier {
-    required sequence<DOMString>   supportedMethods;
+    required DOMString             supportedMethods;
              PaymentItem           total;
              sequence<PaymentItem> additionalDisplayItems;
              object                data;
@@ -134,7 +134,7 @@
   }
 });
 idlArray.add_objects({
-  PaymentRequest: ["new PaymentRequest([{supportedMethods: ['foo']}], {total: {label: 'bar', amount: {currency: 'BAZ', value: '0'}}})"]
+  PaymentRequest: ["new PaymentRequest([{supportedMethods: 'foo'}], {total: {label: 'bar', amount: {currency: 'BAZ', value: '0'}}})"]
 });
 idlArray.test();
 </script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-allowed-by-feature-policy.https.sub.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-allowed-by-feature-policy.https.sub.html
index b64cda813..68fc12d1 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-allowed-by-feature-policy.https.sub.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-allowed-by-feature-policy.https.sub.html
@@ -11,7 +11,7 @@
   var header = 'Feature-Policy header {"payment" : ["*"]}';
 
   test(() => {
-    var supportedInstruments = [ { supportedMethods: [ 'visa' ] } ];
+    var supportedInstruments = [ { supportedMethods: 'visa' } ];
     var details = {
       total: { label: 'Test', amount: { currency: 'USD', value: '5.00' } }
     };
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-default-feature-policy.https.sub-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-default-feature-policy.https.sub-expected.txt
new file mode 100644
index 0000000..b76cfa77
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-default-feature-policy.https.sub-expected.txt
@@ -0,0 +1,8 @@
+This is a testharness.js-based test.
+FAIL Default "payment" feature policy ["self"] allows the top-level document. assert_unreached: Reached unreachable code
+PASS Default "payment" feature policy ["self"] allows same-origin iframes. 
+PASS Default "payment" feature policy ["self"] disallows cross-origin iframes. 
+PASS Default "payment" feature policy ["self"] allowpaymentrequest=true allows same-origin iframes. 
+PASS Default "payment" feature policy ["self"] allowpaymentrequest=true allows cross-origin iframes. 
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-default-feature-policy.https.sub.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-default-feature-policy.https.sub.html
index ab9c8ca..ad4bbdc 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-default-feature-policy.https.sub.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-default-feature-policy.https.sub.html
@@ -11,7 +11,7 @@
   var header = 'Default "payment" feature policy ["self"]';
 
   test(() => {
-    var supportedInstruments = [ { supportedMethods: [ 'visa' ] } ];
+    var supportedInstruments = [ { supportedMethods: 'visa' } ];
     var details = {
       total: { label: 'Test', amount: { currency: 'USD', value: '5.00' } }
     };
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-disabled-by-feature-policy.https.sub.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-disabled-by-feature-policy.https.sub.html
index 7379a5cb..98e434a 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-disabled-by-feature-policy.https.sub.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-disabled-by-feature-policy.https.sub.html
@@ -11,7 +11,7 @@
   var header = 'Feature-Policy header {"payment" : []}';
 
   test(() => {
-    var supportedInstruments = [ { supportedMethods: [ 'visa' ] } ];
+    var supportedInstruments = [ { supportedMethods: 'visa' } ];
     var details = {
       total: { label: 'Test', amount: { currency: 'USD', value: '5.00' } }
     };
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-abort-method.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-abort-method.https-expected.txt
index 4fe4e85c..4ddca24 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-abort-method.https-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-abort-method.https-expected.txt
@@ -1,6 +1,6 @@
 This is a testharness.js-based test.
-PASS Throws if the promise [[state]] is not "interactive" 
-FAIL Calling abort must not change the [[state]] until after "interactive" assert_true: Unexpected promise rejection: Request failed expected true got false
-FAIL calling .abort() causes acceptPromise to reject and closes the request. assert_true: Unexpected promise rejection: Request failed expected true got false
+FAIL Throws if the promise [[state]] is not "interactive" promise_test: Unhandled rejection with value: object "TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence."
+FAIL Calling abort must not change the [[state]] until after "interactive" promise_test: Unhandled rejection with value: object "TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence."
+FAIL calling .abort() causes acceptPromise to reject and closes the request. promise_test: Unhandled rejection with value: object "TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence."
 Harness: the test ran to completion.
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-abort-method.https.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-abort-method.https.html
index f596800..8fc4baf 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-abort-method.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-abort-method.https.html
@@ -12,7 +12,7 @@
   // not being explicitly handled.
   allow_uncaught_exception: true,
 });
-const basicCard = Object.freeze({ supportedMethods: ["basic-card"] });
+const basicCard = Object.freeze({ supportedMethods: "basic-card" });
 const defaultMethods = Object.freeze([basicCard]);
 const defaultDetails = Object.freeze({
   total: {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-canmakepayment-method.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-canmakepayment-method.https-expected.txt
index 26436ee..27bb743 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-canmakepayment-method.https-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-canmakepayment-method.https-expected.txt
@@ -1,10 +1,9 @@
 This is a testharness.js-based test.
-Harness Error. harness_status.status = 1 , harness_status.message = Request failed
-FAIL If request.[[state]] is "created", then return a promise that resolves to true for known method. promise_test: Unhandled rejection with value: object "ReferenceError: assert_equal is not defined"
-FAIL If request.[[state]] is "interactive", then return a promise rejected with an "InvalidStateError" DOMException. promise_test: Unhandled rejection with value: object "InvalidStateError: Never called show(), so nothing to abort"
-FAIL If request.[[state]] is "closed", then return a promise rejected with an "InvalidStateError" DOMException. promise_test: Unhandled rejection with value: object "UnknownError: Request failed"
-FAIL If payment method identifier and serialized parts are supported, resolve promise with true. promise_test: Unhandled rejection with value: object "UnknownError: Request failed"
+FAIL If request.[[state]] is "created", then return a promise that resolves to true for known method. promise_test: Unhandled rejection with value: object "TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence."
+FAIL If request.[[state]] is "interactive", then return a promise rejected with an "InvalidStateError" DOMException. promise_test: Unhandled rejection with value: object "TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence."
+FAIL If request.[[state]] is "closed", then return a promise rejected with an "InvalidStateError" DOMException. promise_test: Unhandled rejection with value: object "TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence."
+FAIL If payment method identifier and serialized parts are supported, resolve promise with true. promise_test: Unhandled rejection with value: object "TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence."
 FAIL If payment method identifier is unknown, resolve promise with false. assert_true: Unexpected exception testing method this-is-not-supported, expected false. See error console. expected true got false
-FAIL Optionally, at the user agent's discretion, return a promise rejected with a "NotAllowedError" DOMException. promise_test: Unhandled rejection with value: object "ReferenceError: assert_equal is not defined"
+FAIL Optionally, at the user agent's discretion, return a promise rejected with a "NotAllowedError" DOMException. promise_test: Unhandled rejection with value: object "TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence."
 Harness: the test ran to completion.
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-canmakepayment-method.https.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-canmakepayment-method.https.html
index 86fae2e..891a62d2 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-canmakepayment-method.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-canmakepayment-method.https.html
@@ -6,7 +6,7 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script>
-const basicCard = Object.freeze({ supportedMethods: ["basic-card"] });
+const basicCard = Object.freeze({ supportedMethods: "basic-card" });
 const defaultMethods = Object.freeze([basicCard]);
 const defaultDetails = Object.freeze({
   total: {
@@ -101,7 +101,7 @@
   for (const method of unsupportedMethods) {
     try {
       const request = new PaymentRequest(
-        [{ supportedMethods: [method] }],
+        [{ supportedMethods: method }],
         defaultDetails
       );
       assert_false(
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-constructor-crash.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-constructor-crash.https-expected.txt
new file mode 100644
index 0000000..fea8439b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-constructor-crash.https-expected.txt
@@ -0,0 +1,53 @@
+This is a testharness.js-based test.
+FAIL Don't crash if there is an abusive number of payment methods in the methodData sequence assert_true: failed smoke test: TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+    at Test.test (https://web-platform.test:8444/payment-request/payment-request-constructor-crash.https.html:82:5)
+    at Test.step (https://web-platform.test:8444/resources/testharness.js:1413:25)
+    at test (https://web-platform.test:8444/resources/testharness.js:501:18)
+    at https://web-platform.test:8444/payment-request/payment-request-constructor-crash.https.html:78:1 expected true got false
+FAIL Don't crash if PaymentMethodData.supportedMethods is an abusive length assert_true: failed smoke test: TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+    at Test.test (https://web-platform.test:8444/payment-request/payment-request-constructor-crash.https.html:103:5)
+    at Test.step (https://web-platform.test:8444/resources/testharness.js:1413:25)
+    at test (https://web-platform.test:8444/resources/testharness.js:501:18)
+    at https://web-platform.test:8444/payment-request/payment-request-constructor-crash.https.html:99:1 expected true got false
+FAIL Don't crash if the request id has an abusive length assert_true: failed smoke test: TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+    at Test.test (https://web-platform.test:8444/payment-request/payment-request-constructor-crash.https.html:124:5)
+    at Test.step (https://web-platform.test:8444/resources/testharness.js:1413:25)
+    at test (https://web-platform.test:8444/resources/testharness.js:501:18)
+    at https://web-platform.test:8444/payment-request/payment-request-constructor-crash.https.html:120:1 expected true got false
+FAIL Don't crash if PaymentDetailsInit.total.label is an abusive length assert_true: failed smoke test: TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+    at Test.test (https://web-platform.test:8444/payment-request/payment-request-constructor-crash.https.html:148:5)
+    at Test.step (https://web-platform.test:8444/resources/testharness.js:1413:25)
+    at test (https://web-platform.test:8444/resources/testharness.js:501:18)
+    at https://web-platform.test:8444/payment-request/payment-request-constructor-crash.https.html:144:1 expected true got false
+FAIL Don't crash if total.amount.value is an abusive length assert_true: failed smoke test: TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+    at Test.test (https://web-platform.test:8444/payment-request/payment-request-constructor-crash.https.html:169:5)
+    at Test.step (https://web-platform.test:8444/resources/testharness.js:1413:25)
+    at test (https://web-platform.test:8444/resources/testharness.js:501:18)
+    at https://web-platform.test:8444/payment-request/payment-request-constructor-crash.https.html:165:1 expected true got false
+FAIL Don't crash if details.displayItems has an abusive number of items assert_true: failed smoke test: TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+    at Test.test (https://web-platform.test:8444/payment-request/payment-request-constructor-crash.https.html:192:7)
+    at Test.step (https://web-platform.test:8444/resources/testharness.js:1413:25)
+    at test (https://web-platform.test:8444/resources/testharness.js:501:18)
+    at https://web-platform.test:8444/payment-request/payment-request-constructor-crash.https.html:187:3 expected true got false
+FAIL Don't crash if details.shippingOptions has an abusive number of items assert_true: failed smoke test: TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+    at Test.test (https://web-platform.test:8444/payment-request/payment-request-constructor-crash.https.html:192:7)
+    at Test.step (https://web-platform.test:8444/resources/testharness.js:1413:25)
+    at test (https://web-platform.test:8444/resources/testharness.js:501:18)
+    at https://web-platform.test:8444/payment-request/payment-request-constructor-crash.https.html:187:3 expected true got false
+FAIL Don't crash if PaymentShippingOptions.label is an abusive length assert_true: failed smoke test: TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+    at Test.test (https://web-platform.test:8444/payment-request/payment-request-constructor-crash.https.html:215:5)
+    at Test.step (https://web-platform.test:8444/resources/testharness.js:1413:25)
+    at test (https://web-platform.test:8444/resources/testharness.js:501:18)
+    at https://web-platform.test:8444/payment-request/payment-request-constructor-crash.https.html:209:1 expected true got false
+FAIL Don't crash if the PaymentShippingOptions.amount.value is an abusive length assert_true: failed smoke test: TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+    at Test.test (https://web-platform.test:8444/payment-request/payment-request-constructor-crash.https.html:235:5)
+    at Test.step (https://web-platform.test:8444/resources/testharness.js:1413:25)
+    at test (https://web-platform.test:8444/resources/testharness.js:501:18)
+    at https://web-platform.test:8444/payment-request/payment-request-constructor-crash.https.html:229:1 expected true got false
+FAIL Don't crash if PaymentItem.label is an abusive length assert_true: failed smoke test: TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+    at Test.test (https://web-platform.test:8444/payment-request/payment-request-constructor-crash.https.html:255:5)
+    at Test.step (https://web-platform.test:8444/resources/testharness.js:1413:25)
+    at test (https://web-platform.test:8444/resources/testharness.js:501:18)
+    at https://web-platform.test:8444/payment-request/payment-request-constructor-crash.https.html:249:1 expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-constructor-crash.https.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-constructor-crash.https.html
index 499fd95..1d0b88d 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-constructor-crash.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-constructor-crash.https.html
@@ -10,7 +10,7 @@
     dictionary PaymentItem {
         required DOMString             label;
         required PaymentCurrencyAmount amount;
-                boolean               pending = false;
+                 boolean               pending = false;
     };
 
     dictionary PaymentDetailsBase {
@@ -29,7 +29,7 @@
 const ABUSIVE_AMOUNT = 100000;
 
 const basicCard = Object.freeze({
-  supportedMethods: ["basic-card"],
+  supportedMethods: "basic-card",
 });
 
 const defaultAmount = Object.freeze({
@@ -93,28 +93,28 @@
     assert_equals(err.name, "TypeError", "must be a TypeError");
   }
   assert_true(true, "Didn't crash");
-}, "Don't crash if there is a abusive number of payment methods in the methodData sequence");
+}, "Don't crash if there is an abusive number of payment methods in the methodData sequence");
 
+// PaymentMethodData.supportedMethods
 test(() => {
-  let supportedMethods = ["basic-card"];
+  const supportedMethods = "basic-card";
   // Smoke test
   try {
     new PaymentRequest([{ supportedMethods }], defaultDetails);
   } catch (err) {
     assert_true(false, "failed smoke test: " + err.stack);
   }
-  // Now, let's add an abusive number of supportedMethods to a single PaymentMethodData
-  for (let i = 0; i < ABUSIVE_AMOUNT; i++) {
-    supportedMethods.push(`https://example.com/${i}/evil_${Math.random()}`);
-  }
-  const evilMethodData = [{ supportedMethods }];
+  // Now, we make supportedMethods super large
+  const evilMethodData = [{
+    supportedMethods: supportedMethods.repeat(ABUSIVE_AMOUNT),
+  }];
   try {
     new PaymentRequest(evilMethodData, defaultDetails);
   } catch (err) {
     assert_equals(err.name, "TypeError", "must be a TypeError");
   }
   assert_true(true, "Didn't crash");
-}, "Don't crash if there is a abusive number of supported methods in one sequence");
+}, "Don't crash if PaymentMethodData.supportedMethods is an abusive length");
 
 // PaymentDetailsInit.id
 test(() => {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-constructor.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-constructor.https-expected.txt
new file mode 100644
index 0000000..6e99643
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-constructor.https-expected.txt
@@ -0,0 +1,132 @@
+This is a testharness.js-based test.
+FAIL If details.id is missing, assign a identifier Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+FAIL If details.id is missing, assign a unique identifier Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+FAIL If the same id is provided, then use it Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+FAIL Use ids even if they are strange Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+FAIL Use provided request ID Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+PASS If the length of the methodData sequence is zero, then throw a TypeError 
+FAIL Method data must be JSON-serializable object (a list in this case) assert_false: shouldn't throw when using a list expected false got true
+FAIL Method data must be JSON-serializable object (an object in this case) Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+PASS Rethrow any exceptions of JSON-serializing paymentMethod.data into a string 
+PASS If details.total.amount.value is not a valid decimal monetary value (in this case "-"), then throw a TypeError 
+PASS If details.total.amount.value is not a valid decimal monetary value (in this case "notdigits"), then throw a TypeError 
+PASS If details.total.amount.value is not a valid decimal monetary value (in this case "ALSONOTDIGITS"), then throw a TypeError 
+PASS If details.total.amount.value is not a valid decimal monetary value (in this case "10."), then throw a TypeError 
+PASS If details.total.amount.value is not a valid decimal monetary value (in this case ".99"), then throw a TypeError 
+PASS If details.total.amount.value is not a valid decimal monetary value (in this case "-10."), then throw a TypeError 
+PASS If details.total.amount.value is not a valid decimal monetary value (in this case "-.99"), then throw a TypeError 
+PASS If details.total.amount.value is not a valid decimal monetary value (in this case "10-"), then throw a TypeError 
+PASS If details.total.amount.value is not a valid decimal monetary value (in this case "1-0"), then throw a TypeError 
+PASS If details.total.amount.value is not a valid decimal monetary value (in this case "1.0.0"), then throw a TypeError 
+PASS If details.total.amount.value is not a valid decimal monetary value (in this case "1/3"), then throw a TypeError 
+PASS If details.total.amount.value is not a valid decimal monetary value (in this case ""), then throw a TypeError 
+PASS If details.total.amount.value is not a valid decimal monetary value (in this case "null"), then throw a TypeError 
+PASS If details.total.amount.value is not a valid decimal monetary value (in this case " 1.0  "), then throw a TypeError 
+PASS If details.total.amount.value is not a valid decimal monetary value (in this case " 1.0 "), then throw a TypeError 
+PASS If details.total.amount.value is not a valid decimal monetary value (in this case "1.0 "), then throw a TypeError 
+PASS If details.total.amount.value is not a valid decimal monetary value (in this case "USD$1.0"), then throw a TypeError 
+PASS If details.total.amount.value is not a valid decimal monetary value (in this case "$1.0"), then throw a TypeError 
+PASS If details.total.amount.value is not a valid decimal monetary value (in this case " 1.0"), then throw a TypeError 
+PASS If details.total.amount.value is not a valid decimal monetary value (in this case "-1"), then throw a TypeError 
+PASS If details.total.amount.value is not a valid decimal monetary value (in this case "-1.0"), then throw a TypeError 
+PASS If details.total.amount.value is not a valid decimal monetary value (in this case "-1.00"), then throw a TypeError 
+PASS If details.total.amount.value is not a valid decimal monetary value (in this case "-1000.000"), then throw a TypeError 
+FAIL PaymentDetailsBase.0 can be 0 length assert_true: 0 can be zero length expected true got false
+FAIL PaymentDetailsBase.1 can be 0 length assert_true: 1 can be zero length expected true got false
+FAIL PaymentDetailsBase.2 can be 0 length assert_true: 2 can be zero length expected true got false
+PASS If the first character of details.total.amount.value is U+002D HYPHEN-MINUS, then throw a TypeError 
+PASS For each item in details.displayItems: if item.amount.value is not a valid decimal monetary value (in this case "-"), then throw a TypeError 
+PASS For each item in details.displayItems: if item.amount.value is not a valid decimal monetary value (in this case "notdigits"), then throw a TypeError 
+PASS For each item in details.displayItems: if item.amount.value is not a valid decimal monetary value (in this case "ALSONOTDIGITS"), then throw a TypeError 
+PASS For each item in details.displayItems: if item.amount.value is not a valid decimal monetary value (in this case "10."), then throw a TypeError 
+PASS For each item in details.displayItems: if item.amount.value is not a valid decimal monetary value (in this case ".99"), then throw a TypeError 
+PASS For each item in details.displayItems: if item.amount.value is not a valid decimal monetary value (in this case "-10."), then throw a TypeError 
+PASS For each item in details.displayItems: if item.amount.value is not a valid decimal monetary value (in this case "-.99"), then throw a TypeError 
+PASS For each item in details.displayItems: if item.amount.value is not a valid decimal monetary value (in this case "10-"), then throw a TypeError 
+PASS For each item in details.displayItems: if item.amount.value is not a valid decimal monetary value (in this case "1-0"), then throw a TypeError 
+PASS For each item in details.displayItems: if item.amount.value is not a valid decimal monetary value (in this case "1.0.0"), then throw a TypeError 
+PASS For each item in details.displayItems: if item.amount.value is not a valid decimal monetary value (in this case "1/3"), then throw a TypeError 
+PASS For each item in details.displayItems: if item.amount.value is not a valid decimal monetary value (in this case ""), then throw a TypeError 
+PASS For each item in details.displayItems: if item.amount.value is not a valid decimal monetary value (in this case "null"), then throw a TypeError 
+PASS For each item in details.displayItems: if item.amount.value is not a valid decimal monetary value (in this case " 1.0  "), then throw a TypeError 
+PASS For each item in details.displayItems: if item.amount.value is not a valid decimal monetary value (in this case " 1.0 "), then throw a TypeError 
+PASS For each item in details.displayItems: if item.amount.value is not a valid decimal monetary value (in this case "1.0 "), then throw a TypeError 
+PASS For each item in details.displayItems: if item.amount.value is not a valid decimal monetary value (in this case "USD$1.0"), then throw a TypeError 
+PASS For each item in details.displayItems: if item.amount.value is not a valid decimal monetary value (in this case "$1.0"), then throw a TypeError 
+PASS For each item in details.displayItems: if item.amount.value is not a valid decimal monetary value (in this case " 1.0"), then throw a TypeError 
+FAIL Negative values are allowed for displayItems.amount.value, irrespective of total amount assert_false: shouldn't throw when given a negative value expected false got true
+FAIL it handles high precision currency values without throwing assert_false: shouldn't throw when given absurd monetary values expected false got true
+PASS For each option in details.shippingOptions: if option.amount.value is not a valid decimal monetary value (in this case "-"), then throw a TypeError 
+PASS For each option in details.shippingOptions: if option.amount.value is not a valid decimal monetary value (in this case "notdigits"), then throw a TypeError 
+PASS For each option in details.shippingOptions: if option.amount.value is not a valid decimal monetary value (in this case "ALSONOTDIGITS"), then throw a TypeError 
+PASS For each option in details.shippingOptions: if option.amount.value is not a valid decimal monetary value (in this case "10."), then throw a TypeError 
+PASS For each option in details.shippingOptions: if option.amount.value is not a valid decimal monetary value (in this case ".99"), then throw a TypeError 
+PASS For each option in details.shippingOptions: if option.amount.value is not a valid decimal monetary value (in this case "-10."), then throw a TypeError 
+PASS For each option in details.shippingOptions: if option.amount.value is not a valid decimal monetary value (in this case "-.99"), then throw a TypeError 
+PASS For each option in details.shippingOptions: if option.amount.value is not a valid decimal monetary value (in this case "10-"), then throw a TypeError 
+PASS For each option in details.shippingOptions: if option.amount.value is not a valid decimal monetary value (in this case "1-0"), then throw a TypeError 
+PASS For each option in details.shippingOptions: if option.amount.value is not a valid decimal monetary value (in this case "1.0.0"), then throw a TypeError 
+PASS For each option in details.shippingOptions: if option.amount.value is not a valid decimal monetary value (in this case "1/3"), then throw a TypeError 
+PASS For each option in details.shippingOptions: if option.amount.value is not a valid decimal monetary value (in this case ""), then throw a TypeError 
+PASS For each option in details.shippingOptions: if option.amount.value is not a valid decimal monetary value (in this case "null"), then throw a TypeError 
+PASS For each option in details.shippingOptions: if option.amount.value is not a valid decimal monetary value (in this case " 1.0  "), then throw a TypeError 
+PASS For each option in details.shippingOptions: if option.amount.value is not a valid decimal monetary value (in this case " 1.0 "), then throw a TypeError 
+PASS For each option in details.shippingOptions: if option.amount.value is not a valid decimal monetary value (in this case "1.0 "), then throw a TypeError 
+PASS For each option in details.shippingOptions: if option.amount.value is not a valid decimal monetary value (in this case "USD$1.0"), then throw a TypeError 
+PASS For each option in details.shippingOptions: if option.amount.value is not a valid decimal monetary value (in this case "$1.0"), then throw a TypeError 
+PASS For each option in details.shippingOptions: if option.amount.value is not a valid decimal monetary value (in this case " 1.0"), then throw a TypeError 
+FAIL If there is no selected shipping option, then PaymentRequest.shippingOption remains null Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+FAIL If there is a selected shipping option, then it becomes synchronously selected Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+FAIL If there is a multiple selected shipping options, only the last is selected Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+FAIL If there are any duplicate shipping option ids, then there are no shipping options Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+PASS If modifier.total.amount.value is not a valid decimal monetary value (in this case "-"), then throw a TypeError 
+PASS If modifier.total.amount.value is not a valid decimal monetary value (in this case "notdigits"), then throw a TypeError 
+PASS If modifier.total.amount.value is not a valid decimal monetary value (in this case "ALSONOTDIGITS"), then throw a TypeError 
+PASS If modifier.total.amount.value is not a valid decimal monetary value (in this case "10."), then throw a TypeError 
+PASS If modifier.total.amount.value is not a valid decimal monetary value (in this case ".99"), then throw a TypeError 
+PASS If modifier.total.amount.value is not a valid decimal monetary value (in this case "-10."), then throw a TypeError 
+PASS If modifier.total.amount.value is not a valid decimal monetary value (in this case "-.99"), then throw a TypeError 
+PASS If modifier.total.amount.value is not a valid decimal monetary value (in this case "10-"), then throw a TypeError 
+PASS If modifier.total.amount.value is not a valid decimal monetary value (in this case "1-0"), then throw a TypeError 
+PASS If modifier.total.amount.value is not a valid decimal monetary value (in this case "1.0.0"), then throw a TypeError 
+PASS If modifier.total.amount.value is not a valid decimal monetary value (in this case "1/3"), then throw a TypeError 
+PASS If modifier.total.amount.value is not a valid decimal monetary value (in this case ""), then throw a TypeError 
+PASS If modifier.total.amount.value is not a valid decimal monetary value (in this case "null"), then throw a TypeError 
+PASS If modifier.total.amount.value is not a valid decimal monetary value (in this case " 1.0  "), then throw a TypeError 
+PASS If modifier.total.amount.value is not a valid decimal monetary value (in this case " 1.0 "), then throw a TypeError 
+PASS If modifier.total.amount.value is not a valid decimal monetary value (in this case "1.0 "), then throw a TypeError 
+PASS If modifier.total.amount.value is not a valid decimal monetary value (in this case "USD$1.0"), then throw a TypeError 
+PASS If modifier.total.amount.value is not a valid decimal monetary value (in this case "$1.0"), then throw a TypeError 
+PASS If modifier.total.amount.value is not a valid decimal monetary value (in this case " 1.0"), then throw a TypeError 
+PASS If modifier.total.amount.value is not a valid decimal monetary value (in this case "-1"), then throw a TypeError 
+PASS If modifier.total.amount.value is not a valid decimal monetary value (in this case "-1.0"), then throw a TypeError 
+PASS If modifier.total.amount.value is not a valid decimal monetary value (in this case "-1.00"), then throw a TypeError 
+PASS If modifier.total.amount.value is not a valid decimal monetary value (in this case "-1000.000"), then throw a TypeError 
+PASS If amount.value of additionalDisplayItems is not a valid decimal monetary value (in this case "-"), then throw a TypeError 
+PASS If amount.value of additionalDisplayItems is not a valid decimal monetary value (in this case "notdigits"), then throw a TypeError 
+PASS If amount.value of additionalDisplayItems is not a valid decimal monetary value (in this case "ALSONOTDIGITS"), then throw a TypeError 
+PASS If amount.value of additionalDisplayItems is not a valid decimal monetary value (in this case "10."), then throw a TypeError 
+PASS If amount.value of additionalDisplayItems is not a valid decimal monetary value (in this case ".99"), then throw a TypeError 
+PASS If amount.value of additionalDisplayItems is not a valid decimal monetary value (in this case "-10."), then throw a TypeError 
+PASS If amount.value of additionalDisplayItems is not a valid decimal monetary value (in this case "-.99"), then throw a TypeError 
+PASS If amount.value of additionalDisplayItems is not a valid decimal monetary value (in this case "10-"), then throw a TypeError 
+PASS If amount.value of additionalDisplayItems is not a valid decimal monetary value (in this case "1-0"), then throw a TypeError 
+PASS If amount.value of additionalDisplayItems is not a valid decimal monetary value (in this case "1.0.0"), then throw a TypeError 
+PASS If amount.value of additionalDisplayItems is not a valid decimal monetary value (in this case "1/3"), then throw a TypeError 
+PASS If amount.value of additionalDisplayItems is not a valid decimal monetary value (in this case ""), then throw a TypeError 
+PASS If amount.value of additionalDisplayItems is not a valid decimal monetary value (in this case "null"), then throw a TypeError 
+PASS If amount.value of additionalDisplayItems is not a valid decimal monetary value (in this case " 1.0  "), then throw a TypeError 
+PASS If amount.value of additionalDisplayItems is not a valid decimal monetary value (in this case " 1.0 "), then throw a TypeError 
+PASS If amount.value of additionalDisplayItems is not a valid decimal monetary value (in this case "1.0 "), then throw a TypeError 
+PASS If amount.value of additionalDisplayItems is not a valid decimal monetary value (in this case "USD$1.0"), then throw a TypeError 
+PASS If amount.value of additionalDisplayItems is not a valid decimal monetary value (in this case "$1.0"), then throw a TypeError 
+PASS If amount.value of additionalDisplayItems is not a valid decimal monetary value (in this case " 1.0"), then throw a TypeError 
+FAIL Modifier data must be JSON-serializable object (a list in this case) assert_false: shouldn't throw when given a list expected false got true
+FAIL Modifier data must be JSON-serializable object (an object in this case) assert_false: shouldn't throw when given an object value expected false got true
+PASS Rethrow any exceptions of JSON-serializing modifier.data into a string 
+PASS Shipping type should be valid 
+FAIL PaymentRequest.shippingAddress must initially be null Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+FAIL If options.requestShipping is not set, then request.shippingType attribute is null. Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+FAIL If options.requestShipping is true, request.shippingType will be options.shippingType. Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-constructor.https.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-constructor.https.html
index 971622e8..9f80c6d3 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-constructor.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-constructor.https.html
@@ -7,7 +7,7 @@
 <script src="/resources/testharnessreport.js"></script>
 <script>
 "use strict";
-const basicCard = Object.freeze({ supportedMethods: ["basic-card"] });
+const basicCard = Object.freeze({ supportedMethods: "basic-card" });
 const defaultMethods = Object.freeze([basicCard]);
 const defaultDetails = Object.freeze({
   total: {
@@ -56,7 +56,7 @@
     new PaymentRequest(
       [
         {
-          supportedMethods: ["basic-card"],
+          supportedMethods: "basic-card",
         },
       ],
       {
@@ -94,38 +94,12 @@
 }, "If the length of the methodData sequence is zero, then throw a TypeError");
 
 test(() => {
-  assert_throws(
-    {
-      name: "TypeError",
-    },
-    () => {
-      new PaymentRequest(
-        [
-          {
-            supportedMethods: [],
-          },
-        ],
-        {
-          total: {
-            label: "",
-            amount: {
-              currency: "USD",
-              value: "1.00",
-            },
-          },
-        }
-      );
-    }
-  );
-}, "If the length of the paymentMethod.supportedMethods sequence is zero, " + "then throw a TypeError");
-
-test(() => {
   let itThrows = false;
   try {
     new PaymentRequest(
       [
         {
-          supportedMethods: ["basic-card"],
+          supportedMethods: "basic-card",
           data: ["some-data"],
         },
       ],
@@ -149,7 +123,7 @@
   new PaymentRequest(
     [
       {
-        supportedMethods: ["basic-card"],
+        supportedMethods: "basic-card",
         data: {
           some: "data",
         },
@@ -178,7 +152,7 @@
       new PaymentRequest(
         [
           {
-            supportedMethods: ["basic-card"],
+            supportedMethods: "basic-card",
             data: recursiveDictionary,
           },
         ],
@@ -202,7 +176,7 @@
       new PaymentRequest(
         [
           {
-            supportedMethods: ["basic-card"],
+            supportedMethods: "basic-card",
             data: "a string",
           },
         ],
@@ -226,7 +200,7 @@
       new PaymentRequest(
         [
           {
-            supportedMethods: ["basic-card"],
+            supportedMethods: "basic-card",
             data: null,
           },
         ],
@@ -287,7 +261,7 @@
         new PaymentRequest(
           [
             {
-              supportedMethods: ["basic-card"],
+              supportedMethods: "basic-card",
             },
           ],
           {
@@ -325,7 +299,7 @@
       new PaymentRequest(
         [
           {
-            supportedMethods: ["basic-card"],
+            supportedMethods: "basic-card",
           },
         ],
         {
@@ -352,7 +326,7 @@
         new PaymentRequest(
           [
             {
-              supportedMethods: ["basic-card"],
+              supportedMethods: "basic-card",
             },
           ],
           {
@@ -385,7 +359,7 @@
     new PaymentRequest(
       [
         {
-          supportedMethods: ["basic-card"],
+          supportedMethods: "basic-card",
           data: {
             supportedTypes: ["debit"],
           },
@@ -431,7 +405,7 @@
     new PaymentRequest(
       [
         {
-          supportedMethods: ["basic-card"],
+          supportedMethods: "basic-card",
         },
       ],
       {
@@ -566,7 +540,7 @@
       },
       () => {
         const invalidModifier = {
-          supportedMethods: ["basic-card"],
+          supportedMethods: "basic-card",
           total: {
             label: "",
             amount: {
@@ -578,7 +552,7 @@
         new PaymentRequest(
           [
             {
-              supportedMethods: ["basic-card"],
+              supportedMethods: "basic-card",
             },
           ],
           {
@@ -605,7 +579,7 @@
       },
       () => {
         const invalidModifier = {
-          supportedMethods: ["basic-card"],
+          supportedMethods: "basic-card",
           total: {
             label: "",
             amount: {
@@ -626,7 +600,7 @@
         new PaymentRequest(
           [
             {
-              supportedMethods: ["basic-card"],
+              supportedMethods: "basic-card",
             },
           ],
           {
@@ -642,7 +616,7 @@
         );
       }
     );
-  }, `If amount.value of additionalDisplayItems is is not a valid decimal monetary value (in this case "${amount}"), then throw a TypeError`);
+  }, `If amount.value of additionalDisplayItems is not a valid decimal monetary value (in this case "${amount}"), then throw a TypeError`);
 }
 
 test(() => {
@@ -651,7 +625,7 @@
     new PaymentRequest(
       [
         {
-          supportedMethods: ["basic-card"],
+          supportedMethods: "basic-card",
         },
       ],
       {
@@ -664,7 +638,7 @@
         },
         modifiers: [
           {
-            supportedMethods: ["basic-card"],
+            supportedMethods: "basic-card",
             data: ["some-data"],
           },
         ],
@@ -682,7 +656,7 @@
     new PaymentRequest(
       [
         {
-          supportedMethods: ["basic-card"],
+          supportedMethods: "basic-card",
         },
       ],
       {
@@ -695,7 +669,7 @@
         },
         modifiers: [
           {
-            supportedMethods: ["basic-card"],
+            supportedMethods: "basic-card",
             data: {
               some: "data",
             },
@@ -707,7 +681,7 @@
     itThrows = true;
   }
   assert_false(itThrows, "shouldn't throw when given an object value");
-}, "Modifier data must be JSON-serializable object (a object in this case)");
+}, "Modifier data must be JSON-serializable object (an object in this case)");
 
 test(() => {
   const recursiveDictionary = {};
@@ -720,7 +694,7 @@
       new PaymentRequest(
         [
           {
-            supportedMethods: ["basic-card"],
+            supportedMethods: "basic-card",
           },
         ],
         {
@@ -733,7 +707,7 @@
           },
           modifiers: [
             {
-              supportedMethods: ["basic-card"],
+              supportedMethods: "basic-card",
               data: recursiveDictionary,
             },
           ],
@@ -749,7 +723,7 @@
       new PaymentRequest(
         [
           {
-            supportedMethods: ["basic-card"],
+            supportedMethods: "basic-card",
           },
         ],
         {
@@ -762,7 +736,7 @@
           },
           modifiers: [
             {
-              supportedMethods: ["basic-card"],
+              supportedMethods: "basic-card",
               data: "a string",
             },
           ],
@@ -778,7 +752,7 @@
       new PaymentRequest(
         [
           {
-            supportedMethods: ["basic-card"],
+            supportedMethods: "basic-card",
           },
         ],
         {
@@ -791,7 +765,7 @@
           },
           modifiers: [
             {
-              supportedMethods: ["basic-card"],
+              supportedMethods: "basic-card",
               data: null,
             },
           ],
@@ -811,7 +785,7 @@
       new PaymentRequest(
         [
           {
-            supportedMethods: ["basic-card"],
+            supportedMethods: "basic-card",
           },
         ],
         {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-id.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-id.https-expected.txt
new file mode 100644
index 0000000..3740d2c8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-id.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Test for PaymentRequest identifier usage Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-id.https.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-id.https.html
index 8e3c3446..7a99a03 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-id.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-id.https.html
@@ -8,10 +8,10 @@
 <script>
 async_test((t) => {
   onload = t.step_func_done(() => {
-    var request = new window[0].PaymentRequest([{supportedMethods: ['foo']}], {id: 'my_payment_id', total: {label: 'label', amount: {currency: 'USD', value: '5.00'}}});
+    var request = new window[0].PaymentRequest([{supportedMethods: 'foo'}], {id: 'my_payment_id', total: {label: 'label', amount: {currency: 'USD', value: '5.00'}}});
     assert_equals(request.id, 'my_payment_id', 'Payment identifier is not reflected correctly in PaymentRequest.id');
 
-    request = new window[0].PaymentRequest([{supportedMethods: ['foo']}], {total: {label: 'label', amount: {currency: 'USD', value: '5.00'}}});
+    request = new window[0].PaymentRequest([{supportedMethods: 'foo'}], {total: {label: 'label', amount: {currency: 'USD', value: '5.00'}}});
     assert_equals(request.id.length, 36, 'Generated payment identifier is not of correct length.');
   });
 });
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-in-iframe-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-in-iframe-expected.txt
new file mode 100644
index 0000000..d4aa504
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-in-iframe-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+FAIL Test for PaymentRequest in an iframe (see also
+https://github.com/w3c/browser-payment-api/issues/2) assert_throws: If the browsing context of the script calling the constructor is not a top-level browsing context, then throw a SecurityError. function "function () {
+        new PaymentRequest([{supportedMethods: 'foo'}], {total: {label: 'label', amount: {currency: 'USD', value: '5.00'}}});
+    }" threw object "TypeError: Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence." ("TypeError") expected object "[object Object]" ("SecurityError")
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-in-iframe.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-in-iframe.html
index 9ca9f4b..8ee6928c 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-in-iframe.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-in-iframe.html
@@ -11,7 +11,7 @@
 <script>
 window.top.test(function() {
     window.top.assert_throws({name: 'SecurityError'}, function() {
-        new PaymentRequest([{supportedMethods: ['foo']}], {total: {label: 'label', amount: {currency: 'USD', value: '5.00'}}});
+        new PaymentRequest([{supportedMethods: 'foo'}], {total: {label: 'label', amount: {currency: 'USD', value: '5.00'}}});
     }, 'If the browsing context of the script calling the constructor is not a top-level browsing context, then throw a SecurityError.');
 });
 </script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-onshippingaddresschange-attribute.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-onshippingaddresschange-attribute.https-expected.txt
new file mode 100644
index 0000000..ac7a844c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-onshippingaddresschange-attribute.https-expected.txt
@@ -0,0 +1,6 @@
+This is a testharness.js-based test.
+FAIL onshippingaddresschange attribute is a generic handler for "shippingaddresschange" Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+FAIL onshippingaddresschange attribute is a handler for PaymentRequestUpdateEvent Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+FAIL onshippingaddresschange attribute and listeners both work Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-onshippingaddresschange-attribute.https.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-onshippingaddresschange-attribute.https.html
index 7d5530a..6b68783 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-onshippingaddresschange-attribute.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-onshippingaddresschange-attribute.https.html
@@ -7,7 +7,7 @@
 <script src="/resources/testharnessreport.js"></script>
 <script>
 "use strict";
-const basicCard = Object.freeze({ supportedMethods: ["basic-card"] });
+const basicCard = Object.freeze({ supportedMethods: "basic-card" });
 const defaultMethods = Object.freeze([basicCard]);
 const defaultDetails = Object.freeze({
   total: {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-onshippingoptionchange-attribute.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-onshippingoptionchange-attribute.https-expected.txt
new file mode 100644
index 0000000..1458d6d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-onshippingoptionchange-attribute.https-expected.txt
@@ -0,0 +1,6 @@
+This is a testharness.js-based test.
+FAIL onshippingoptionchange attribute is a generic handler for "shippingoptionchange" Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+FAIL onshippingoptionchange attribute is a handler for PaymentRequestUpdateEvent Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+FAIL onshippingoptionchange attribute and listeners both work Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-onshippingoptionchange-attribute.https.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-onshippingoptionchange-attribute.https.html
index 05c3dbf..29c1189 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-onshippingoptionchange-attribute.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-onshippingoptionchange-attribute.https.html
@@ -7,7 +7,7 @@
 <script src="/resources/testharnessreport.js"></script>
 <script>
 "use strict";
-const basicCard = Object.freeze({ supportedMethods: ["basic-card"] });
+const basicCard = Object.freeze({ supportedMethods: "basic-card" });
 const defaultMethods = Object.freeze([basicCard]);
 const defaultDetails = Object.freeze({
   total: {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-response-id.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-response-id.html
index a28f3b24..90a2cfbb 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-response-id.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-response-id.html
@@ -32,7 +32,7 @@
        }
 
        const supportedInstruments = [{
-         supportedMethods: ['https://android.com/pay'],
+         supportedMethods: 'https://android.com/pay',
          data: {
            merchantName: 'Rouslan Solomakhin',
            merchantId: '00184145120947117657',
@@ -47,7 +47,7 @@
            },
          },
        }, {
-         supportedMethods: ['basic-card'],
+         supportedMethods: 'basic-card',
          data: {
            supportedNetworks: ['unionpay', 'visa', 'mastercard', 'amex', 'discover',
              'diners', 'jcb', 'mir',
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-show-method.https.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-show-method.https.html
index 1bdbba4..01859c1b 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-show-method.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-show-method.https.html
@@ -7,7 +7,7 @@
 <script src="/resources/testharnessreport.js"></script>
 <script>
 'use strict';
-const basicCard = Object.freeze({ supportedMethods: ["basic-card"] });
+const basicCard = Object.freeze({ supportedMethods: "basic-card" });
 const defaultMethods = Object.freeze([basicCard]);
 const defaultDetails = Object.freeze({
   total: {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-update-event-constructor.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-update-event-constructor.https-expected.txt
new file mode 100644
index 0000000..720ad614
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-update-event-constructor.https-expected.txt
@@ -0,0 +1,6 @@
+This is a testharness.js-based test.
+PASS PaymentRequestUpdateEvent can be constructed in secure-context 
+PASS PaymentRequestUpdateEvent can be constructed with an EventInitDict, even if not trusted 
+FAIL PaymentRequestUpdateEvent can be dispatched, even if not trusted Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-update-event-constructor.https.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-update-event-constructor.https.html
index bb83e5d..e158a14 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-update-event-constructor.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-update-event-constructor.https.html
@@ -6,7 +6,7 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script>
-const basicCard = Object.freeze({ supportedMethods: ["basic-card"] });
+const basicCard = Object.freeze({ supportedMethods: "basic-card" });
 const defaultMethods = Object.freeze([basicCard]);
 const defaultDetails = Object.freeze({
   total: {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-update-event-updatewith-method.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-update-event-updatewith-method.https-expected.txt
index 4ffc7a1..383d733 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-update-event-updatewith-method.https-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-update-event-updatewith-method.https-expected.txt
@@ -1,10 +1,8 @@
 This is a testharness.js-based test.
-PASS Let target be the request which is dispatching the event. 
+FAIL Let target be the request which is dispatching the event. Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
 FAIL Calling .updateWith() with an undispatched untrusted event throws "InvalidStateError" assert_throws: untrusted event of type "just a test" must throw "InvalidStateError" function "() => {
         ev.updateWith(Promise.resolve());
       }" did not throw
-FAIL Calling .updateWith() with a dispatched, untrusted event, throws "InvalidStateError" assert_throws: untrusted event of type "just a test" must throw "InvalidStateError" function "() => {
-        ev.updateWith(Promise.resolve())
-      }" did not throw
+FAIL Calling .updateWith() with a dispatched, untrusted event, throws "InvalidStateError" Failed to construct 'PaymentRequest': The provided value cannot be converted to a sequence.
 Harness: the test ran to completion.
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-update-event-updatewith-method.https.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-update-event-updatewith-method.https.html
index 98a418c..adacdf3 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-update-event-updatewith-method.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-update-event-updatewith-method.https.html
@@ -6,7 +6,7 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script>
-const basicCard = Object.freeze({ supportedMethods: ["basic-card"] });
+const basicCard = Object.freeze({ supportedMethods: "basic-card" });
 const defaultMethods = Object.freeze([basicCard]);
 const defaultDetails = Object.freeze({
   total: {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/shadow-dom/slotchange-customelements.html b/third_party/WebKit/LayoutTests/external/wpt/shadow-dom/slotchange-customelements.html
new file mode 100644
index 0000000..b0cf932
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/shadow-dom/slotchange-customelements.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Shadow DOM: slotchange customelements</title>
+<meta name="author" title="Surma" href="mailto:surma@google.com">
+<link rel="help" href="https://dom.spec.whatwg.org/#signaling-slot-change">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<slots-in-constructor id="constructor-upgrade"><div></div></slots-in-constructor>
+<slots-in-callback id="callback-upgrade"><div></div></slots-in-callback>
+<script>
+var calls = [];
+class SlotsInConstructor extends HTMLElement {
+    constructor() {
+        super();
+        this.attachShadow({mode: 'open'});
+        this.shadowRoot.innerHTML = '<slot></slot>';
+        var slot = this.shadowRoot.children[0];
+        slot.addEventListener('slotchange', function() {
+            calls.push(this.id);
+        }.bind(this));
+    }
+}
+customElements.define('slots-in-constructor', SlotsInConstructor);
+class SlotsInCallback extends HTMLElement {
+    constructor() {
+        super();
+    }
+
+    connectedCallback() {
+        this.attachShadow({mode: 'open'});
+        this.shadowRoot.innerHTML = '<slot></slot>';
+        var slot = this.shadowRoot.children[0];
+        slot.addEventListener('slotchange', function() {
+            calls.push(this.id);
+        }.bind(this));
+    }
+}
+customElements.define('slots-in-callback', SlotsInCallback);
+</script>
+<slots-in-constructor id="constructor-parser"><div></div></slots-in-constructor>
+<slots-in-callback id="callback-parser"><div></div></slots-in-callback>
+<script>
+test(function () {
+    assert_true(calls.includes("constructor-parser"));
+    assert_true(calls.includes("callback-parser"));
+    assert_true(calls.includes("constructor-upgrade"));
+    assert_true(calls.includes("callback-upgrade"));
+}, 'slotchange must fire on initialization of custom elements with slotted children');
+done();
+</script>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/fast/canvas/getPutImageDataPairTest-expected.txt b/third_party/WebKit/LayoutTests/fast/canvas/getPutImageDataPairTest-expected.txt
deleted file mode 100644
index beacd89..0000000
--- a/third_party/WebKit/LayoutTests/fast/canvas/getPutImageDataPairTest-expected.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-This is a testharness.js-based test.
-Harness Error. harness_status.status = 1 , harness_status.message = 1 duplicate test name: "GetPutImageDataTestCase "
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/fast/canvas/getPutImageDataPairTest.html b/third_party/WebKit/LayoutTests/fast/canvas/getPutImageDataPairTest.html
index ab075af..630d6853 100644
--- a/third_party/WebKit/LayoutTests/fast/canvas/getPutImageDataPairTest.html
+++ b/third_party/WebKit/LayoutTests/fast/canvas/getPutImageDataPairTest.html
@@ -3,6 +3,8 @@
 
 <script>
 var canvas = document.createElement('canvas');
+canvas.width = 5;
+canvas.height = 5;
 var ctx = canvas.getContext('2d');
 
 function getPutImageData(numIters, ctx, rgba) {
@@ -39,15 +41,15 @@
 }
 
 var testScenarios = [
-    ['GetPutImageDataTestCase ', 50, ctx, '0, 0, 0, 0.0'],
-    ['GetPutImageDataTestCase ', 50, ctx, '0, 0, 0, 0.5'],
-    ['GetPutImageDataTestCase ', 50, ctx, '0, 0, 0, 1.0'],
-    ['GetPutImageDataTestCase ', 50, ctx, '127, 128, 129, 0.49'],
-    ['GetPutImageDataTestCase ', 50, ctx, '127, 128, 129, 0.51'],
-    ['GetPutImageDataTestCase ', 50, ctx, '127, 128, 129, 0.5'],
-    ['GetPutImageDataTestCase ', 50, ctx, '128, 128, 128, 0.0'],
-    ['GetPutImageDataTestCase ', 50, ctx, '128, 128, 128, 0.5'],
-    ['GetPutImageDataTestCase ', 50, ctx, '128, 128, 128, 1.0'],
+    ['GetPutImageDataTestCase0 ', 50, ctx, '0, 0, 0, 0.0'],
+    ['GetPutImageDataTestCase1 ', 50, ctx, '0, 0, 0, 0.5'],
+    ['GetPutImageDataTestCase2 ', 50, ctx, '0, 0, 0, 1.0'],
+    ['GetPutImageDataTestCase3 ', 50, ctx, '127, 128, 129, 0.49'],
+    ['GetPutImageDataTestCase4 ', 50, ctx, '127, 128, 129, 0.51'],
+    ['GetPutImageDataTestCase5 ', 50, ctx, '127, 128, 129, 0.5'],
+    ['GetPutImageDataTestCase6 ', 50, ctx, '128, 128, 128, 0.0'],
+    ['GetPutImageDataTestCase7 ', 50, ctx, '128, 128, 128, 0.5'],
+    ['GetPutImageDataTestCase8 ', 50, ctx, '128, 128, 128, 1.0'],
 ];
 
 generate_tests(getPutImageData, testScenarios);
diff --git a/third_party/WebKit/LayoutTests/fast/css/getComputedStyle/computed-style-listing-expected.txt b/third_party/WebKit/LayoutTests/fast/css/getComputedStyle/computed-style-listing-expected.txt
index 182d8a9..d651bbc 100644
--- a/third_party/WebKit/LayoutTests/fast/css/getComputedStyle/computed-style-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/fast/css/getComputedStyle/computed-style-listing-expected.txt
@@ -185,6 +185,7 @@
 left: auto
 letter-spacing: normal
 lighting-color: rgb(255, 255, 255)
+line-break: auto
 line-height: normal
 line-height-step: 0px
 list-style-image: none
diff --git a/third_party/WebKit/LayoutTests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt b/third_party/WebKit/LayoutTests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt
index 9cd914c..e5a7c17 100644
--- a/third_party/WebKit/LayoutTests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt
@@ -185,6 +185,7 @@
 left: auto
 letter-spacing: normal
 lighting-color: rgb(255, 255, 255)
+line-break: auto
 line-height: normal
 line-height-step: 0px
 list-style-image: none
diff --git a/third_party/WebKit/LayoutTests/http/tests/plugins/cross-frame-object-access-expected.txt b/third_party/WebKit/LayoutTests/http/tests/plugins/cross-frame-object-access-expected.txt
index 6fef89e5..a4c1bbe 100644
--- a/third_party/WebKit/LayoutTests/http/tests/plugins/cross-frame-object-access-expected.txt
+++ b/third_party/WebKit/LayoutTests/http/tests/plugins/cross-frame-object-access-expected.txt
@@ -8,7 +8,7 @@
 
 This tests that plugins can access objects in other frames as allowed by the security model enforced in WebCore.
 Error: Error: Failed conversion between PP_Var and V8 value
-Error: Uncaught [object DOMException]
+Error: Uncaught
 Error: Error: Failed conversion between PP_Var and V8 value
-Error: Uncaught [object DOMException]
+Error: Uncaught
 SUCCESS
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/WebKit/LayoutTests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index 3007e262..14fde4d 100644
--- a/third_party/WebKit/LayoutTests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -179,7 +179,6 @@
     getter message
     getter name
     method constructor
-    method toString
 interface DOMMatrix : DOMMatrixReadOnly
     attribute @@toStringTag
     getter a
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/gpu/fast/canvas/getPutImageDataPairTest-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/virtual/gpu/fast/canvas/getPutImageDataPairTest-expected.txt
deleted file mode 100644
index beacd89..0000000
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/gpu/fast/canvas/getPutImageDataPairTest-expected.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-This is a testharness.js-based test.
-Harness Error. harness_status.status = 1 , harness_status.message = 1 duplicate test name: "GetPutImageDataTestCase "
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/virtual/gpu/fast/canvas/getPutImageDataPairTest-expected.txt b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/virtual/gpu/fast/canvas/getPutImageDataPairTest-expected.txt
deleted file mode 100644
index beacd89..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/virtual/gpu/fast/canvas/getPutImageDataPairTest-expected.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-This is a testharness.js-based test.
-Harness Error. harness_status.status = 1 , harness_status.message = 1 duplicate test name: "GetPutImageDataTestCase "
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index 798e4e8..08b3a3bc 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -112,7 +112,6 @@
     getter message
     getter name
     method constructor
-    method toString
 interface DOMMatrix : DOMMatrixReadOnly
     attribute @@toStringTag
     getter a
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
index 4e696b1..95759b9 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -103,7 +103,6 @@
 [Worker]     getter message
 [Worker]     getter name
 [Worker]     method constructor
-[Worker]     method toString
 [Worker] interface DOMMatrix : DOMMatrixReadOnly
 [Worker]     attribute @@toStringTag
 [Worker]     getter a
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/stable/webexposed/global-interface-listing-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/virtual/stable/webexposed/global-interface-listing-expected.txt
index 715eb4fc..3bc1c24 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/stable/webexposed/global-interface-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/stable/webexposed/global-interface-listing-expected.txt
@@ -711,7 +711,6 @@
     getter message
     getter name
     method constructor
-    method toString
 interface DOMImplementation
     attribute @@toStringTag
     method constructor
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
index 38903fe..b6e08c9 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -103,7 +103,6 @@
 [Worker]     getter message
 [Worker]     getter name
 [Worker]     method constructor
-[Worker]     method toString
 [Worker] interface DOMMatrix : DOMMatrixReadOnly
 [Worker]     attribute @@toStringTag
 [Worker]     getter a
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/stable/webexposed/global-interface-listing-expected.txt b/third_party/WebKit/LayoutTests/platform/win/virtual/stable/webexposed/global-interface-listing-expected.txt
index 1a42282..da23fd5 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/stable/webexposed/global-interface-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/stable/webexposed/global-interface-listing-expected.txt
@@ -640,7 +640,6 @@
     getter message
     getter name
     method constructor
-    method toString
 interface DOMImplementation
     attribute @@toStringTag
     method constructor
diff --git a/third_party/WebKit/LayoutTests/platform/win7/virtual/gpu/fast/canvas/getPutImageDataPairTest-expected.txt b/third_party/WebKit/LayoutTests/platform/win7/virtual/gpu/fast/canvas/getPutImageDataPairTest-expected.txt
deleted file mode 100644
index beacd89..0000000
--- a/third_party/WebKit/LayoutTests/platform/win7/virtual/gpu/fast/canvas/getPutImageDataPairTest-expected.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-This is a testharness.js-based test.
-Harness Error. harness_status.status = 1 , harness_status.message = 1 duplicate test name: "GetPutImageDataTestCase "
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-PASS GetPutImageDataTestCase  
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/svg/css/getComputedStyle-listing-expected.txt b/third_party/WebKit/LayoutTests/svg/css/getComputedStyle-listing-expected.txt
index b4ae6eb..49c9a86 100644
--- a/third_party/WebKit/LayoutTests/svg/css/getComputedStyle-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/svg/css/getComputedStyle-listing-expected.txt
@@ -185,6 +185,7 @@
 left: auto
 letter-spacing: normal
 lighting-color: rgb(255, 255, 255)
+line-break: auto
 line-height: normal
 line-height-step: 0px
 list-style-image: none
diff --git a/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index 10cef54..3d3cb4c 100644
--- a/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -179,7 +179,6 @@
     getter message
     getter name
     method constructor
-    method toString
 interface DOMMatrix : DOMMatrixReadOnly
     attribute @@toStringTag
     getter a
diff --git a/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-dedicated-worker-expected.txt
index 59e2341..7b49c67 100644
--- a/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -141,7 +141,6 @@
 [Worker]     getter message
 [Worker]     getter name
 [Worker]     method constructor
-[Worker]     method toString
 [Worker] interface DOMMatrix : DOMMatrixReadOnly
 [Worker]     attribute @@toStringTag
 [Worker]     getter a
diff --git a/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-expected.txt b/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-expected.txt
index 44e6215..ee312d3 100644
--- a/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-expected.txt
@@ -346,6 +346,17 @@
     method setValueAtTime
     method setValueCurveAtTime
     setter value
+interface AudioParamMap
+    attribute @@toStringTag
+    getter size
+    method @@iterator
+    method constructor
+    method entries
+    method forEach
+    method get
+    method has
+    method keys
+    method values
 interface AudioProcessingEvent : Event
     attribute @@toStringTag
     getter inputBuffer
@@ -1130,7 +1141,6 @@
     getter message
     getter name
     method constructor
-    method toString
 interface DOMImplementation
     attribute @@toStringTag
     method constructor
diff --git a/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-shared-worker-expected.txt
index d7d9625..bb7e134 100644
--- a/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-shared-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -141,7 +141,6 @@
 [Worker]     getter message
 [Worker]     getter name
 [Worker]     method constructor
-[Worker]     method toString
 [Worker] interface DOMMatrix : DOMMatrixReadOnly
 [Worker]     attribute @@toStringTag
 [Worker]     getter a
diff --git a/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index 36c2af57..30f11f67 100644
--- a/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -152,7 +152,6 @@
     getter message
     getter name
     method constructor
-    method toString
 interface DOMMatrix : DOMMatrixReadOnly
     getter a
     getter b
diff --git a/third_party/WebKit/LayoutTests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/WebKit/LayoutTests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index 09aa1e3..0055abc 100644
--- a/third_party/WebKit/LayoutTests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -112,7 +112,6 @@
     getter message
     getter name
     method constructor
-    method toString
 interface DOMMatrix : DOMMatrixReadOnly
     attribute @@toStringTag
     getter a
diff --git a/third_party/WebKit/LayoutTests/virtual/stable/webexposed/css-properties-as-js-properties-expected.txt b/third_party/WebKit/LayoutTests/virtual/stable/webexposed/css-properties-as-js-properties-expected.txt
index 1ae6a93..ae272a94 100644
--- a/third_party/WebKit/LayoutTests/virtual/stable/webexposed/css-properties-as-js-properties-expected.txt
+++ b/third_party/WebKit/LayoutTests/virtual/stable/webexposed/css-properties-as-js-properties-expected.txt
@@ -165,6 +165,7 @@
 length
 letterSpacing
 lightingColor
+lineBreak
 lineHeight
 listStyle
 listStyleImage
diff --git a/third_party/WebKit/LayoutTests/virtual/stable/webexposed/css-property-listing-expected.txt b/third_party/WebKit/LayoutTests/virtual/stable/webexposed/css-property-listing-expected.txt
index 1dcb8b0..81b5942 100644
--- a/third_party/WebKit/LayoutTests/virtual/stable/webexposed/css-property-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/virtual/stable/webexposed/css-property-listing-expected.txt
@@ -223,6 +223,7 @@
     left
     letter-spacing
     lighting-color
+    line-break
     line-height
     list-style-image
     list-style-position
@@ -725,6 +726,4 @@
         transition-timing-function
     -webkit-user-select
         user-select
-    line-break
-        -webkit-line-break
 
diff --git a/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
index 8247901..59343c3 100644
--- a/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -103,7 +103,6 @@
 [Worker]     getter message
 [Worker]     getter name
 [Worker]     method constructor
-[Worker]     method toString
 [Worker] interface DOMMatrix : DOMMatrixReadOnly
 [Worker]     attribute @@toStringTag
 [Worker]     getter a
diff --git a/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
index d4f6a25f..580d086 100644
--- a/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -103,7 +103,6 @@
 [Worker]     getter message
 [Worker]     getter name
 [Worker]     method constructor
-[Worker]     method toString
 [Worker] interface DOMMatrix : DOMMatrixReadOnly
 [Worker]     attribute @@toStringTag
 [Worker]     getter a
diff --git a/third_party/WebKit/LayoutTests/webexposed/css-properties-as-js-properties-expected.txt b/third_party/WebKit/LayoutTests/webexposed/css-properties-as-js-properties-expected.txt
index 8f7aadd..14e521b2 100644
--- a/third_party/WebKit/LayoutTests/webexposed/css-properties-as-js-properties-expected.txt
+++ b/third_party/WebKit/LayoutTests/webexposed/css-properties-as-js-properties-expected.txt
@@ -169,6 +169,7 @@
 length
 letterSpacing
 lightingColor
+lineBreak
 lineHeight
 lineHeightStep
 listStyle
diff --git a/third_party/WebKit/LayoutTests/webexposed/css-property-listing-expected.txt b/third_party/WebKit/LayoutTests/webexposed/css-property-listing-expected.txt
index 65a3817..b7c4d50b7 100644
--- a/third_party/WebKit/LayoutTests/webexposed/css-property-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/webexposed/css-property-listing-expected.txt
@@ -227,6 +227,7 @@
     left
     letter-spacing
     lighting-color
+    line-break
     line-height
     line-height-step
     list-style-image
@@ -781,6 +782,4 @@
         transition-timing-function
     -webkit-user-select
         user-select
-    line-break
-        -webkit-line-break
 
diff --git a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-dedicated-worker-expected.txt
index 7c83dff..7f223fac 100644
--- a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -141,7 +141,6 @@
 [Worker]     getter message
 [Worker]     getter name
 [Worker]     method constructor
-[Worker]     method toString
 [Worker] interface DOMMatrix : DOMMatrixReadOnly
 [Worker]     attribute @@toStringTag
 [Worker]     getter a
diff --git a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
index d530b63..1f08975 100644
--- a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
@@ -346,6 +346,17 @@
     method setValueAtTime
     method setValueCurveAtTime
     setter value
+interface AudioParamMap
+    attribute @@toStringTag
+    getter size
+    method @@iterator
+    method constructor
+    method entries
+    method forEach
+    method get
+    method has
+    method keys
+    method values
 interface AudioProcessingEvent : Event
     attribute @@toStringTag
     getter inputBuffer
@@ -1130,7 +1141,6 @@
     getter message
     getter name
     method constructor
-    method toString
 interface DOMImplementation
     attribute @@toStringTag
     method constructor
diff --git a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-shared-worker-expected.txt
index 8c28435..361fdf5 100644
--- a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-shared-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -141,7 +141,6 @@
 [Worker]     getter message
 [Worker]     getter name
 [Worker]     method constructor
-[Worker]     method toString
 [Worker] interface DOMMatrix : DOMMatrixReadOnly
 [Worker]     attribute @@toStringTag
 [Worker]     getter a
diff --git a/third_party/WebKit/Source/bindings/core/v8/serialization/V8ScriptValueSerializerTest.cpp b/third_party/WebKit/Source/bindings/core/v8/serialization/V8ScriptValueSerializerTest.cpp
index 52848148..a335321 100644
--- a/third_party/WebKit/Source/bindings/core/v8/serialization/V8ScriptValueSerializerTest.cpp
+++ b/third_party/WebKit/Source/bindings/core/v8/serialization/V8ScriptValueSerializerTest.cpp
@@ -168,7 +168,7 @@
   ASSERT_TRUE(HadDOMException("DataCloneError", script_state, exception_state));
   DOMException* dom_exception =
       V8DOMException::toImpl(exception_state.GetException().As<v8::Object>());
-  EXPECT_TRUE(dom_exception->toString().Contains("postMessage"));
+  EXPECT_TRUE(dom_exception->message().Contains("postMessage"));
 }
 
 TEST(V8ScriptValueSerializerTest, RethrowsScriptError) {
diff --git a/third_party/WebKit/Source/bindings/templates/interface_base.cpp.tmpl b/third_party/WebKit/Source/bindings/templates/interface_base.cpp.tmpl
index ea50c0a..86d20601 100644
--- a/third_party/WebKit/Source/bindings/templates/interface_base.cpp.tmpl
+++ b/third_party/WebKit/Source/bindings/templates/interface_base.cpp.tmpl
@@ -602,6 +602,30 @@
   interfaceTemplate->Inherit(intrinsicIteratorPrototypeInterfaceTemplate);
   {% endif %}
 
+  {% if interface_name == 'DOMException' %}
+  // The WebIDL spec states that DOMException objects have a few peculiarities.
+  // One of them is similar to what it mandates for Iterator objects when it
+  // comes to the inheritance chain. Instead of
+  //     DOMException -> prototype -> %ObjectPrototype%
+  // we have
+  //     DOMException -> prototype -> %ErrorPrototype% -> %ObjectPrototype%
+  // so that DOMException objects "inherit" toString() and a few properties
+  // from %ErrorPrototype%.
+  // See https://heycam.github.io/webidl/#es-DOMException-specialness.
+  //
+  // We achieve this with the same hack we use for Iterators: create a new
+  // function template with no prototype, set its "prototype" property to
+  // %ErrorPrototype% and make |interfaceTemplate| inherit from it. When
+  // |interfaceTemplate| is instantiated, its prototype.__proto__ will point to
+  // |intrinsicErrorPrototypeInterfaceTemplate|'s "prototype" property.
+  v8::Local<v8::FunctionTemplate> intrinsicErrorPrototypeInterfaceTemplate =
+      v8::FunctionTemplate::New(isolate);
+  intrinsicErrorPrototypeInterfaceTemplate->RemovePrototype();
+  intrinsicErrorPrototypeInterfaceTemplate->SetIntrinsicDataProperty(
+      V8AtomicString(isolate, "prototype"), v8::kErrorPrototype);
+  interfaceTemplate->Inherit(intrinsicErrorPrototypeInterfaceTemplate);
+  {% endif %}
+
   {% if interface_name == 'Location' %}
   // Symbol.toPrimitive
   // Prevent author scripts to inject Symbol.toPrimitive property into location
diff --git a/third_party/WebKit/Source/build/scripts/make_names.py b/third_party/WebKit/Source/build/scripts/make_names.py
index 19488fb0..95746bf 100755
--- a/third_party/WebKit/Source/build/scripts/make_names.py
+++ b/third_party/WebKit/Source/build/scripts/make_names.py
@@ -40,7 +40,7 @@
     # FIXME: Remove this special case for the ugly x-webkit-foo attributes.
     if entry['name'].startswith('-webkit-'):
         return entry['name'].replace('-', '_')[1:]
-    return name_utilities.cpp_name(entry).replace('-', '_')
+    return name_utilities.cpp_name(entry).replace('-', '_').replace(' ', '_')
 
 
 class MakeNamesWriter(json5_generator.Writer):
diff --git a/third_party/WebKit/Source/core/css/CSSComputedStyleDeclaration.cpp b/third_party/WebKit/Source/core/css/CSSComputedStyleDeclaration.cpp
index b3d9c8a5..89709fa 100644
--- a/third_party/WebKit/Source/core/css/CSSComputedStyleDeclaration.cpp
+++ b/third_party/WebKit/Source/core/css/CSSComputedStyleDeclaration.cpp
@@ -174,7 +174,7 @@
     CSSPropertyVectorEffect, CSSPropertyPaintOrder, CSSPropertyD, CSSPropertyCx,
     CSSPropertyCy, CSSPropertyX, CSSPropertyY, CSSPropertyR, CSSPropertyRx,
     CSSPropertyRy, CSSPropertyTranslate, CSSPropertyRotate, CSSPropertyScale,
-    CSSPropertyCaretColor};
+    CSSPropertyCaretColor, CSSPropertyLineBreak};
 
 CSSValueID CssIdentifierForFontSizeKeyword(int keyword_size) {
   DCHECK_NE(keyword_size, 0);
diff --git a/third_party/WebKit/Source/core/css/CSSProperties.json5 b/third_party/WebKit/Source/core/css/CSSProperties.json5
index a6edc158..968d9a4 100644
--- a/third_party/WebKit/Source/core/css/CSSProperties.json5
+++ b/third_party/WebKit/Source/core/css/CSSProperties.json5
@@ -3043,6 +3043,11 @@
       default_value: "auto",
       field_group: "rare-inherited",
     },
+    {
+      name: "line-break",
+      inherited: true,
+      type_name: "LineBreak",
+    },
     // An Apple extension.
     {
       name: "-webkit-line-clamp",
@@ -4151,10 +4156,6 @@
       alias_for: "justify-content",
     },
     {
-      name: "line-break",
-      alias_for: "-webkit-line-break",
-    },
-    {
       name: "-webkit-opacity",
       alias_for: "opacity",
     },
diff --git a/third_party/WebKit/Source/core/css/ComputedStyleCSSValueMapping.cpp b/third_party/WebKit/Source/core/css/ComputedStyleCSSValueMapping.cpp
index 7aaf7c4..b7f4e48 100644
--- a/third_party/WebKit/Source/core/css/ComputedStyleCSSValueMapping.cpp
+++ b/third_party/WebKit/Source/core/css/ComputedStyleCSSValueMapping.cpp
@@ -3002,6 +3002,7 @@
       return ZoomAdjustedPixelValue(style.WordSpacing(), style);
     case CSSPropertyWordWrap:
       return CSSIdentifierValue::Create(style.OverflowWrap());
+    case CSSPropertyLineBreak:
     case CSSPropertyWebkitLineBreak:
       return CSSIdentifierValue::Create(style.GetLineBreak());
     case CSSPropertyResize:
diff --git a/third_party/WebKit/Source/core/css/parser/CSSParserFastPaths.cpp b/third_party/WebKit/Source/core/css/parser/CSSParserFastPaths.cpp
index d4d57bb..1a37b6e 100644
--- a/third_party/WebKit/Source/core/css/parser/CSSParserFastPaths.cpp
+++ b/third_party/WebKit/Source/core/css/parser/CSSParserFastPaths.cpp
@@ -806,6 +806,9 @@
       return value_id == CSSValueAuto || value_id == CSSValueNone ||
              value_id == CSSValueAntialiased ||
              value_id == CSSValueSubpixelAntialiased;
+    case CSSPropertyLineBreak:
+      return value_id == CSSValueAuto || value_id == CSSValueLoose ||
+             value_id == CSSValueNormal || value_id == CSSValueStrict;
     case CSSPropertyWebkitLineBreak:
       return value_id == CSSValueAuto || value_id == CSSValueLoose ||
              value_id == CSSValueNormal || value_id == CSSValueStrict ||
@@ -952,6 +955,7 @@
     case CSSPropertyFlexWrap:
     case CSSPropertyFontKerning:
     case CSSPropertyWebkitFontSmoothing:
+    case CSSPropertyLineBreak:
     case CSSPropertyWebkitLineBreak:
     case CSSPropertyWebkitMarginAfterCollapse:
     case CSSPropertyWebkitMarginBeforeCollapse:
diff --git a/third_party/WebKit/Source/core/dom/DOMException.cpp b/third_party/WebKit/Source/core/dom/DOMException.cpp
index 739f80b..1f3ae0a 100644
--- a/third_party/WebKit/Source/core/dom/DOMException.cpp
+++ b/third_party/WebKit/Source/core/dom/DOMException.cpp
@@ -195,10 +195,6 @@
   return new DOMException(GetErrorCode(name), name, message, message);
 }
 
-String DOMException::toString() const {
-  return name() + ": " + message();
-}
-
 String DOMException::ToStringForConsole() const {
   return name() + ": " + MessageForConsole();
 }
diff --git a/third_party/WebKit/Source/core/dom/DOMException.h b/third_party/WebKit/Source/core/dom/DOMException.h
index c0c503c..74b61e5 100644
--- a/third_party/WebKit/Source/core/dom/DOMException.h
+++ b/third_party/WebKit/Source/core/dom/DOMException.h
@@ -57,7 +57,6 @@
   // This is the message that's exposed to JavaScript: never return unsanitized
   // data.
   String message() const { return sanitized_message_; }
-  String toString() const;
 
   // This is the message that's exposed to the console: if an unsanitized
   // message is present, we prefer it.
diff --git a/third_party/WebKit/Source/core/dom/DOMException.idl b/third_party/WebKit/Source/core/dom/DOMException.idl
index 5056da9..bbf3701 100644
--- a/third_party/WebKit/Source/core/dom/DOMException.idl
+++ b/third_party/WebKit/Source/core/dom/DOMException.idl
@@ -40,9 +40,6 @@
     readonly attribute DOMString        name;
     readonly attribute DOMString        message;
 
-    // Override in a Mozilla compatible format
-    [NotEnumerable] DOMString toString();
-
     // ExceptionCode
     const unsigned short      INDEX_SIZE_ERR                 = 1;
     const unsigned short      DOMSTRING_SIZE_ERR             = 2;
diff --git a/third_party/WebKit/Source/core/editing/markers/SpellingMarkerTest.cpp b/third_party/WebKit/Source/core/editing/markers/SpellingMarkerTest.cpp
index bffeadfd..32d076c44 100644
--- a/third_party/WebKit/Source/core/editing/markers/SpellingMarkerTest.cpp
+++ b/third_party/WebKit/Source/core/editing/markers/SpellingMarkerTest.cpp
@@ -8,23 +8,23 @@
 
 namespace blink {
 
-const char* const kDescription = "Test description";
+const char* const kTestDescription = "Test description";
 
 class SpellingMarkerTest : public ::testing::Test {};
 
 TEST_F(SpellingMarkerTest, MarkerType) {
-  DocumentMarker* marker = new SpellingMarker(0, 1, kDescription);
+  DocumentMarker* marker = new SpellingMarker(0, 1, kTestDescription);
   EXPECT_EQ(DocumentMarker::kSpelling, marker->GetType());
 }
 
 TEST_F(SpellingMarkerTest, IsSpellCheckMarker) {
-  DocumentMarker* marker = new SpellingMarker(0, 1, kDescription);
+  DocumentMarker* marker = new SpellingMarker(0, 1, kTestDescription);
   EXPECT_TRUE(IsSpellCheckMarker(*marker));
 }
 
 TEST_F(SpellingMarkerTest, ConstructorAndGetters) {
-  SpellingMarker* marker = new SpellingMarker(0, 1, kDescription);
-  EXPECT_EQ(kDescription, marker->Description());
+  SpellingMarker* marker = new SpellingMarker(0, 1, kTestDescription);
+  EXPECT_EQ(kTestDescription, marker->Description());
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/frame/UseCounter.cpp b/third_party/WebKit/Source/core/frame/UseCounter.cpp
index 18456c9..f4d1b9b 100644
--- a/third_party/WebKit/Source/core/frame/UseCounter.cpp
+++ b/third_party/WebKit/Source/core/frame/UseCounter.cpp
@@ -1070,7 +1070,7 @@
       return 554;
     case CSSPropertyMaxBlockSize:
       return 555;
-    case CSSPropertyAliasLineBreak:
+    case CSSPropertyLineBreak:
       return 556;
     case CSSPropertyPlaceContent:
       return 557;
diff --git a/third_party/WebKit/Source/core/html/parser/HTMLPreloadScannerTest.cpp b/third_party/WebKit/Source/core/html/parser/HTMLPreloadScannerTest.cpp
index 1180cfb0..f624e63 100644
--- a/third_party/WebKit/Source/core/html/parser/HTMLPreloadScannerTest.cpp
+++ b/third_party/WebKit/Source/core/html/parser/HTMLPreloadScannerTest.cpp
@@ -17,6 +17,7 @@
 #include "platform/loader/fetch/ClientHintsPreferences.h"
 #include "platform/weborigin/SecurityOrigin.h"
 #include "public/platform/Platform.h"
+#include "public/platform/WebClientHintsType.h"
 #include "public/platform/WebURLLoaderMockFactory.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -84,12 +85,15 @@
       EXPECT_STREQ(base_url,
                    preload_request_->BaseURL().GetString().Ascii().data());
       EXPECT_EQ(width, preload_request_->ResourceWidth());
-      EXPECT_EQ(preferences.ShouldSendDPR(),
-                preload_request_->Preferences().ShouldSendDPR());
-      EXPECT_EQ(preferences.ShouldSendResourceWidth(),
-                preload_request_->Preferences().ShouldSendResourceWidth());
-      EXPECT_EQ(preferences.ShouldSendViewportWidth(),
-                preload_request_->Preferences().ShouldSendViewportWidth());
+      EXPECT_EQ(
+          preferences.ShouldSend(kWebClientHintsTypeDpr),
+          preload_request_->Preferences().ShouldSend(kWebClientHintsTypeDpr));
+      EXPECT_EQ(preferences.ShouldSend(kWebClientHintsTypeResourceWidth),
+                preload_request_->Preferences().ShouldSend(
+                    kWebClientHintsTypeResourceWidth));
+      EXPECT_EQ(preferences.ShouldSend(kWebClientHintsTypeViewportWidth),
+                preload_request_->Preferences().ShouldSend(
+                    kWebClientHintsTypeViewportWidth));
     }
   }
 
@@ -503,12 +507,12 @@
   ClientHintsPreferences resource_width;
   ClientHintsPreferences all;
   ClientHintsPreferences viewport_width;
-  dpr.SetShouldSendDPR(true);
-  all.SetShouldSendDPR(true);
-  resource_width.SetShouldSendResourceWidth(true);
-  all.SetShouldSendResourceWidth(true);
-  viewport_width.SetShouldSendViewportWidth(true);
-  all.SetShouldSendViewportWidth(true);
+  dpr.SetShouldSendForTesting(kWebClientHintsTypeDpr);
+  all.SetShouldSendForTesting(kWebClientHintsTypeDpr);
+  resource_width.SetShouldSendForTesting(kWebClientHintsTypeResourceWidth);
+  all.SetShouldSendForTesting(kWebClientHintsTypeResourceWidth);
+  viewport_width.SetShouldSendForTesting(kWebClientHintsTypeViewportWidth);
+  all.SetShouldSendForTesting(kWebClientHintsTypeViewportWidth);
   TestCase test_cases[] = {
       {"http://example.test",
        "<meta http-equiv='accept-ch' content='bla'><img srcset='bla.gif 320w, "
diff --git a/third_party/WebKit/Source/core/inspector/AddStringToDigestor.cpp b/third_party/WebKit/Source/core/inspector/AddStringToDigestor.cpp
new file mode 100644
index 0000000..2c59eba
--- /dev/null
+++ b/third_party/WebKit/Source/core/inspector/AddStringToDigestor.cpp
@@ -0,0 +1,18 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "core/inspector/AddStringToDigestor.h"
+
+#include "platform/wtf/text/WTFString.h"
+#include "public/platform/WebCrypto.h"
+
+namespace blink {
+
+void AddStringToDigestor(WebCryptoDigestor* digestor, const String& string) {
+  const CString c_string = string.Utf8();
+  digestor->Consume(reinterpret_cast<const unsigned char*>(c_string.data()),
+                    c_string.length());
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/core/inspector/AddStringToDigestor.h b/third_party/WebKit/Source/core/inspector/AddStringToDigestor.h
new file mode 100644
index 0000000..2981c38
--- /dev/null
+++ b/third_party/WebKit/Source/core/inspector/AddStringToDigestor.h
@@ -0,0 +1,17 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef AddStringToDigestor_h
+#define AddStringToDigestor_h
+
+namespace WTF {
+class String;
+}
+
+namespace blink {
+class WebCryptoDigestor;
+void AddStringToDigestor(WebCryptoDigestor*, const WTF::String&);
+}  // namespace blink
+
+#endif  // AddStringToDigestor_h
diff --git a/third_party/WebKit/Source/core/inspector/BUILD.gn b/third_party/WebKit/Source/core/inspector/BUILD.gn
index ca9d502..d824be26 100644
--- a/third_party/WebKit/Source/core/inspector/BUILD.gn
+++ b/third_party/WebKit/Source/core/inspector/BUILD.gn
@@ -11,6 +11,8 @@
 
 blink_core_sources("inspector") {
   sources = [
+    "AddStringToDigestor.cpp",
+    "AddStringToDigestor.h",
     "ConsoleMessage.cpp",
     "ConsoleMessage.h",
     "ConsoleMessageStorage.cpp",
@@ -95,18 +97,12 @@
   ]
 
   jumbo_excluded_sources = [
-    # Collides with DOMPatchSupport.cpp (patch incoming)
-    "InspectorAnimationAgent.cpp",
-
     # Collides with InspectorPageAgent.cpp and
     # NetworkResourcesData.cpp (patch incoming)
     "InspectorNetworkAgent.cpp",
 
     # Collides with InspectorPageAgent.cpp (patch incoming)
     "MainThreadDebugger.cpp",
-
-    "InspectorOverlayAgent.cpp",  # Way too many different "Response"
-    "InspectorEmulationAgent.cpp",  # Way too many different "Response"
   ]
 
   configs += [
diff --git a/third_party/WebKit/Source/core/inspector/DOMPatchSupport.cpp b/third_party/WebKit/Source/core/inspector/DOMPatchSupport.cpp
index c406897..7de5ea5 100644
--- a/third_party/WebKit/Source/core/inspector/DOMPatchSupport.cpp
+++ b/third_party/WebKit/Source/core/inspector/DOMPatchSupport.cpp
@@ -43,6 +43,7 @@
 #include "core/html/HTMLDocument.h"
 #include "core/html/HTMLHeadElement.h"
 #include "core/html/parser/HTMLDocumentParser.h"
+#include "core/inspector/AddStringToDigestor.h"
 #include "core/inspector/DOMEditor.h"
 #include "core/inspector/InspectorHistory.h"
 #include "core/xml/parser/XMLDocumentParser.h"
@@ -427,13 +428,6 @@
   return true;
 }
 
-static void AddStringToDigestor(WebCryptoDigestor* digestor,
-                                const String& string) {
-  digestor->Consume(
-      reinterpret_cast<const unsigned char*>(string.Utf8().data()),
-      string.length());
-}
-
 DOMPatchSupport::Digest* DOMPatchSupport::CreateDigest(
     Node* node,
     UnusedNodesMap* unused_nodes_map) {
diff --git a/third_party/WebKit/Source/core/inspector/InspectorAnimationAgent.cpp b/third_party/WebKit/Source/core/inspector/InspectorAnimationAgent.cpp
index 8d2ea53..a4e464a 100644
--- a/third_party/WebKit/Source/core/inspector/InspectorAnimationAgent.cpp
+++ b/third_party/WebKit/Source/core/inspector/InspectorAnimationAgent.cpp
@@ -22,6 +22,7 @@
 #include "core/css/resolver/StyleResolver.h"
 #include "core/dom/DOMNodeIds.h"
 #include "core/frame/LocalFrame.h"
+#include "core/inspector/AddStringToDigestor.h"
 #include "core/inspector/InspectedFrames.h"
 #include "core/inspector/InspectorCSSAgent.h"
 #include "core/inspector/InspectorStyleSheet.h"
@@ -454,13 +455,6 @@
     CSSPropertyTransitionProperty, CSSPropertyTransitionTimingFunction,
 };
 
-static void AddStringToDigestor(WebCryptoDigestor* digestor,
-                                const String& string) {
-  digestor->Consume(
-      reinterpret_cast<const unsigned char*>(string.Ascii().data()),
-      string.length());
-}
-
 String InspectorAnimationAgent::CreateCSSId(blink::Animation& animation) {
   String type =
       id_to_animation_type_.at(String::Number(animation.SequenceNumber()));
diff --git a/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp b/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp
index 811342a..8674241 100644
--- a/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp
+++ b/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp
@@ -786,19 +786,7 @@
   if (!RuntimeEnabledFeatures::ClientHintsEnabled())
     return;
 
-  bool should_send_device_ram =
-      GetClientHintsPreferences().ShouldSendDeviceRAM() ||
-      hints_preferences.ShouldSendDeviceRAM();
-  bool should_send_dpr = GetClientHintsPreferences().ShouldSendDPR() ||
-                         hints_preferences.ShouldSendDPR();
-  bool should_send_resource_width =
-      GetClientHintsPreferences().ShouldSendResourceWidth() ||
-      hints_preferences.ShouldSendResourceWidth();
-  bool should_send_viewport_width =
-      GetClientHintsPreferences().ShouldSendViewportWidth() ||
-      hints_preferences.ShouldSendViewportWidth();
-
-  if (should_send_device_ram) {
+  if (ShouldSendClientHint(kWebClientHintsTypeDeviceRam, hints_preferences)) {
     int64_t physical_memory = MemoryCoordinator::GetPhysicalMemoryMB();
     request.AddHTTPHeaderField(
         "device-ram",
@@ -806,11 +794,11 @@
   }
 
   float dpr = GetDevicePixelRatio();
-  if (should_send_dpr) {
+  if (ShouldSendClientHint(kWebClientHintsTypeDpr, hints_preferences))
     request.AddHTTPHeaderField("DPR", AtomicString(String::Number(dpr)));
-  }
 
-  if (should_send_resource_width) {
+  if (ShouldSendClientHint(kWebClientHintsTypeResourceWidth,
+                           hints_preferences)) {
     if (resource_width.is_set) {
       float physical_width = resource_width.width * dpr;
       request.AddHTTPHeaderField(
@@ -818,7 +806,9 @@
     }
   }
 
-  if (should_send_viewport_width && !IsDetached() && GetFrame()->View()) {
+  if (ShouldSendClientHint(kWebClientHintsTypeViewportWidth,
+                           hints_preferences) &&
+      !IsDetached() && GetFrame()->View()) {
     request.AddHTTPHeaderField(
         "Viewport-Width",
         AtomicString(String::Number(GetFrame()->View()->ViewportWidth())));
@@ -1095,6 +1085,13 @@
   return document_->DevicePixelRatio();
 }
 
+bool FrameFetchContext::ShouldSendClientHint(
+    WebClientHintsType type,
+    const ClientHintsPreferences& hints_preferences) const {
+  return GetClientHintsPreferences().ShouldSend(type) ||
+         hints_preferences.ShouldSend(type);
+}
+
 std::unique_ptr<WebURLLoader> FrameFetchContext::CreateURLLoader(
     const ResourceRequest& request) {
   DCHECK(!IsDetached());
diff --git a/third_party/WebKit/Source/core/loader/FrameFetchContext.h b/third_party/WebKit/Source/core/loader/FrameFetchContext.h
index a9d3315..9699931 100644
--- a/third_party/WebKit/Source/core/loader/FrameFetchContext.h
+++ b/third_party/WebKit/Source/core/loader/FrameFetchContext.h
@@ -217,6 +217,8 @@
   RefPtr<SecurityOrigin> GetRequestorOriginForFrameLoading();
   ClientHintsPreferences GetClientHintsPreferences() const;
   float GetDevicePixelRatio() const;
+  bool ShouldSendClientHint(WebClientHintsType,
+                            const ClientHintsPreferences&) const;
 
   Member<DocumentLoader> document_loader_;
   Member<Document> document_;
diff --git a/third_party/WebKit/Source/core/loader/FrameFetchContextTest.cpp b/third_party/WebKit/Source/core/loader/FrameFetchContextTest.cpp
index 06bcc30..538c083 100644
--- a/third_party/WebKit/Source/core/loader/FrameFetchContextTest.cpp
+++ b/third_party/WebKit/Source/core/loader/FrameFetchContextTest.cpp
@@ -53,6 +53,7 @@
 #include "platform/weborigin/SecurityViolationReportingPolicy.h"
 #include "public/platform/WebAddressSpace.h"
 #include "public/platform/WebCachePolicy.h"
+#include "public/platform/WebClientHintsType.h"
 #include "public/platform/WebDocumentSubresourceFilter.h"
 #include "public/platform/WebInsecureRequestPolicy.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -541,7 +542,7 @@
 TEST_F(FrameFetchContextHintsTest, MonitorDeviceRAMHints) {
   ExpectHeader("http://www.example.com/1.gif", "device-ram", false, "");
   ClientHintsPreferences preferences;
-  preferences.SetShouldSendDeviceRAM(true);
+  preferences.SetShouldSendForTesting(kWebClientHintsTypeDeviceRam);
   document->GetClientHintsPreferences().UpdateFrom(preferences);
   MemoryCoordinator::SetPhysicalMemoryMBForTesting(4096);
   ExpectHeader("http://www.example.com/1.gif", "device-ram", true, "4");
@@ -559,7 +560,7 @@
 TEST_F(FrameFetchContextHintsTest, MonitorDPRHints) {
   ExpectHeader("http://www.example.com/1.gif", "DPR", false, "");
   ClientHintsPreferences preferences;
-  preferences.SetShouldSendDPR(true);
+  preferences.SetShouldSendForTesting(kWebClientHintsTypeDpr);
   document->GetClientHintsPreferences().UpdateFrom(preferences);
   ExpectHeader("http://www.example.com/1.gif", "DPR", true, "1");
   dummy_page_holder->GetPage().SetDeviceScaleFactorDeprecated(2.5);
@@ -571,7 +572,7 @@
 TEST_F(FrameFetchContextHintsTest, MonitorResourceWidthHints) {
   ExpectHeader("http://www.example.com/1.gif", "Width", false, "");
   ClientHintsPreferences preferences;
-  preferences.SetShouldSendResourceWidth(true);
+  preferences.SetShouldSendForTesting(kWebClientHintsTypeResourceWidth);
   document->GetClientHintsPreferences().UpdateFrom(preferences);
   ExpectHeader("http://www.example.com/1.gif", "Width", true, "500", 500);
   ExpectHeader("http://www.example.com/1.gif", "Width", true, "667", 666.6666);
@@ -584,7 +585,7 @@
 TEST_F(FrameFetchContextHintsTest, MonitorViewportWidthHints) {
   ExpectHeader("http://www.example.com/1.gif", "Viewport-Width", false, "");
   ClientHintsPreferences preferences;
-  preferences.SetShouldSendViewportWidth(true);
+  preferences.SetShouldSendForTesting(kWebClientHintsTypeViewportWidth);
   document->GetClientHintsPreferences().UpdateFrom(preferences);
   ExpectHeader("http://www.example.com/1.gif", "Viewport-Width", true, "500");
   dummy_page_holder->GetFrameView().SetLayoutSizeFixedToFrameSize(false);
@@ -602,10 +603,10 @@
   ExpectHeader("http://www.example.com/1.gif", "Width", false, "");
 
   ClientHintsPreferences preferences;
-  preferences.SetShouldSendDeviceRAM(true);
-  preferences.SetShouldSendDPR(true);
-  preferences.SetShouldSendResourceWidth(true);
-  preferences.SetShouldSendViewportWidth(true);
+  preferences.SetShouldSendForTesting(kWebClientHintsTypeDeviceRam);
+  preferences.SetShouldSendForTesting(kWebClientHintsTypeDpr);
+  preferences.SetShouldSendForTesting(kWebClientHintsTypeResourceWidth);
+  preferences.SetShouldSendForTesting(kWebClientHintsTypeViewportWidth);
   MemoryCoordinator::SetPhysicalMemoryMBForTesting(4096);
   document->GetClientHintsPreferences().UpdateFrom(preferences);
   ExpectHeader("http://www.example.com/1.gif", "device-ram", true, "4");
@@ -1241,18 +1242,25 @@
   request.SetFetchCredentialsMode(WebURLRequest::kFetchCredentialsModeOmit);
 
   ClientHintsPreferences client_hints_preferences;
-  client_hints_preferences.SetShouldSendDeviceRAM(true);
-  client_hints_preferences.SetShouldSendDPR(true);
-  client_hints_preferences.SetShouldSendResourceWidth(true);
-  client_hints_preferences.SetShouldSendViewportWidth(true);
+  client_hints_preferences.SetShouldSendForTesting(
+      kWebClientHintsTypeDeviceRam);
+  client_hints_preferences.SetShouldSendForTesting(kWebClientHintsTypeDpr);
+  client_hints_preferences.SetShouldSendForTesting(
+      kWebClientHintsTypeResourceWidth);
+  client_hints_preferences.SetShouldSendForTesting(
+      kWebClientHintsTypeViewportWidth);
 
   FetchParameters::ResourceWidth resource_width;
   ResourceLoaderOptions options;
 
-  document->GetClientHintsPreferences().SetShouldSendDeviceRAM(true);
-  document->GetClientHintsPreferences().SetShouldSendDPR(true);
-  document->GetClientHintsPreferences().SetShouldSendResourceWidth(true);
-  document->GetClientHintsPreferences().SetShouldSendViewportWidth(true);
+  document->GetClientHintsPreferences().SetShouldSendForTesting(
+      kWebClientHintsTypeDeviceRam);
+  document->GetClientHintsPreferences().SetShouldSendForTesting(
+      kWebClientHintsTypeDpr);
+  document->GetClientHintsPreferences().SetShouldSendForTesting(
+      kWebClientHintsTypeResourceWidth);
+  document->GetClientHintsPreferences().SetShouldSendForTesting(
+      kWebClientHintsTypeViewportWidth);
 
   dummy_page_holder = nullptr;
 
diff --git a/third_party/WebKit/Source/core/loader/ImageLoader.cpp b/third_party/WebKit/Source/core/loader/ImageLoader.cpp
index 4c7a48e..5fccbd9 100644
--- a/third_party/WebKit/Source/core/loader/ImageLoader.cpp
+++ b/third_party/WebKit/Source/core/loader/ImageLoader.cpp
@@ -51,6 +51,7 @@
 #include "platform/weborigin/SecurityPolicy.h"
 #include "platform/wtf/PtrUtil.h"
 #include "public/platform/WebCachePolicy.h"
+#include "public/platform/WebClientHintsType.h"
 #include "public/platform/WebURLRequest.h"
 
 namespace blink {
@@ -230,7 +231,7 @@
         element.GetDocument().GetSecurityOrigin(), cross_origin);
   }
 
-  if (client_hints_preferences.ShouldSendResourceWidth() &&
+  if (client_hints_preferences.ShouldSend(kWebClientHintsTypeResourceWidth) &&
       isHTMLImageElement(element))
     params.SetResourceWidth(toHTMLImageElement(element).GetResourceWidth());
 }
diff --git a/third_party/WebKit/Source/core/loader/private/FrameClientHintsPreferencesContext.cpp b/third_party/WebKit/Source/core/loader/private/FrameClientHintsPreferencesContext.cpp
index 1dbc12ce..c5538b10 100644
--- a/third_party/WebKit/Source/core/loader/private/FrameClientHintsPreferencesContext.cpp
+++ b/third_party/WebKit/Source/core/loader/private/FrameClientHintsPreferencesContext.cpp
@@ -8,24 +8,28 @@
 
 namespace blink {
 
+namespace {
+
+// Mapping from WebClientHintsType to WebFeature. The ordering should match the
+// ordering of enums in WebClientHintsType.
+static constexpr WebFeature kWebFeatureMapping[] = {
+    WebFeature::kClientHintsDeviceRAM, WebFeature::kClientHintsDPR,
+    WebFeature::kClientHintsResourceWidth,
+    WebFeature::kClientHintsViewportWidth,
+};
+
+static_assert(kWebClientHintsTypeLast + 1 == arraysize(kWebFeatureMapping),
+              "unhandled client hint type");
+
+}  // namespace
+
 FrameClientHintsPreferencesContext::FrameClientHintsPreferencesContext(
     LocalFrame* frame)
     : frame_(frame) {}
 
-void FrameClientHintsPreferencesContext::CountClientHintsDeviceRAM() {
-  UseCounter::Count(frame_, WebFeature::kClientHintsDeviceRAM);
-}
-
-void FrameClientHintsPreferencesContext::CountClientHintsDPR() {
-  UseCounter::Count(frame_, WebFeature::kClientHintsDPR);
-}
-
-void FrameClientHintsPreferencesContext::CountClientHintsResourceWidth() {
-  UseCounter::Count(frame_, WebFeature::kClientHintsResourceWidth);
-}
-
-void FrameClientHintsPreferencesContext::CountClientHintsViewportWidth() {
-  UseCounter::Count(frame_, WebFeature::kClientHintsViewportWidth);
+void FrameClientHintsPreferencesContext::CountClientHints(
+    WebClientHintsType type) {
+  UseCounter::Count(frame_, kWebFeatureMapping[static_cast<int32_t>(type)]);
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/loader/private/FrameClientHintsPreferencesContext.h b/third_party/WebKit/Source/core/loader/private/FrameClientHintsPreferencesContext.h
index 9949e034..af04a00 100644
--- a/third_party/WebKit/Source/core/loader/private/FrameClientHintsPreferencesContext.h
+++ b/third_party/WebKit/Source/core/loader/private/FrameClientHintsPreferencesContext.h
@@ -19,10 +19,7 @@
  public:
   explicit FrameClientHintsPreferencesContext(LocalFrame*);
 
-  void CountClientHintsDeviceRAM() override;
-  void CountClientHintsDPR() override;
-  void CountClientHintsResourceWidth() override;
-  void CountClientHintsViewportWidth() override;
+  void CountClientHints(WebClientHintsType) override;
 
  private:
   Member<LocalFrame> frame_;
diff --git a/third_party/WebKit/Source/core/scheduler/ThrottlingTest.cpp b/third_party/WebKit/Source/core/scheduler/ThrottlingTest.cpp
index 32679ca..cae8d1a 100644
--- a/third_party/WebKit/Source/core/scheduler/ThrottlingTest.cpp
+++ b/third_party/WebKit/Source/core/scheduler/ThrottlingTest.cpp
@@ -68,8 +68,7 @@
 
 class BackgroundRendererThrottlingTest : public SimTest {};
 
-TEST_F(BackgroundRendererThrottlingTest,
-       DISABLED_BackgroundRenderersAreThrottled) {
+TEST_F(BackgroundRendererThrottlingTest, BackgroundRenderersAreThrottled) {
   SimRequest main_resource("https://example.com/", "text/html");
 
   LoadURL("https://example.com/");
diff --git a/third_party/WebKit/Source/core/svg/SVGAngleTearOff.cpp b/third_party/WebKit/Source/core/svg/SVGAngleTearOff.cpp
index 928139d..a1b7b25 100644
--- a/third_party/WebKit/Source/core/svg/SVGAngleTearOff.cpp
+++ b/third_party/WebKit/Source/core/svg/SVGAngleTearOff.cpp
@@ -135,7 +135,8 @@
 }
 
 DEFINE_TRACE_WRAPPERS(SVGAngleTearOff) {
-  visitor->TraceWrappers(contextElement());
+  SVGPropertyTearOff<SVGAngle>::TraceWrappers(visitor);
+  ScriptWrappable::TraceWrappers(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/svg/SVGAnimatedBoolean.h b/third_party/WebKit/Source/core/svg/SVGAnimatedBoolean.h
index 4eccd57..328ea30 100644
--- a/third_party/WebKit/Source/core/svg/SVGAnimatedBoolean.h
+++ b/third_party/WebKit/Source/core/svg/SVGAnimatedBoolean.h
@@ -48,7 +48,8 @@
   }
 
   DEFINE_INLINE_VIRTUAL_TRACE_WRAPPERS() {
-    visitor->TraceWrappers(contextElement());
+    SVGAnimatedProperty<SVGBoolean>::TraceWrappers(visitor);
+    ScriptWrappable::TraceWrappers(visitor);
   }
 
  protected:
diff --git a/third_party/WebKit/Source/core/svg/SVGAnimatedEnumeration.h b/third_party/WebKit/Source/core/svg/SVGAnimatedEnumeration.h
index c2d20e8f..d9927d5 100644
--- a/third_party/WebKit/Source/core/svg/SVGAnimatedEnumeration.h
+++ b/third_party/WebKit/Source/core/svg/SVGAnimatedEnumeration.h
@@ -71,7 +71,7 @@
   }
 
   DEFINE_INLINE_VIRTUAL_TRACE_WRAPPERS() {
-    visitor->TraceWrappers(contextElement());
+    SVGAnimatedEnumerationBase::TraceWrappers(visitor);
   }
 
  protected:
diff --git a/third_party/WebKit/Source/core/svg/SVGAnimatedEnumerationBase.h b/third_party/WebKit/Source/core/svg/SVGAnimatedEnumerationBase.h
index 3f44dc8d..aff9408c 100644
--- a/third_party/WebKit/Source/core/svg/SVGAnimatedEnumerationBase.h
+++ b/third_party/WebKit/Source/core/svg/SVGAnimatedEnumerationBase.h
@@ -47,6 +47,11 @@
 
   void setBaseVal(unsigned short, ExceptionState&);
 
+  DEFINE_INLINE_VIRTUAL_TRACE_WRAPPERS() {
+    SVGAnimatedProperty<SVGEnumerationBase>::TraceWrappers(visitor);
+    ScriptWrappable::TraceWrappers(visitor);
+  }
+
  protected:
   SVGAnimatedEnumerationBase(SVGElement* context_element,
                              const QualifiedName& attribute_name,
diff --git a/third_party/WebKit/Source/core/svg/SVGAnimatedInteger.cpp b/third_party/WebKit/Source/core/svg/SVGAnimatedInteger.cpp
index f6454ce..14b9f642 100644
--- a/third_party/WebKit/Source/core/svg/SVGAnimatedInteger.cpp
+++ b/third_party/WebKit/Source/core/svg/SVGAnimatedInteger.cpp
@@ -50,7 +50,8 @@
 }
 
 DEFINE_TRACE_WRAPPERS(SVGAnimatedInteger) {
-  visitor->TraceWrappers(contextElement());
+  SVGAnimatedProperty<SVGInteger>::TraceWrappers(visitor);
+  ScriptWrappable::TraceWrappers(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/svg/SVGAnimatedLength.cpp b/third_party/WebKit/Source/core/svg/SVGAnimatedLength.cpp
index 9039789..23b4478 100644
--- a/third_party/WebKit/Source/core/svg/SVGAnimatedLength.cpp
+++ b/third_party/WebKit/Source/core/svg/SVGAnimatedLength.cpp
@@ -53,7 +53,8 @@
 }
 
 DEFINE_TRACE_WRAPPERS(SVGAnimatedLength) {
-  visitor->TraceWrappers(contextElement());
+  SVGAnimatedProperty<SVGLength>::TraceWrappers(visitor);
+  ScriptWrappable::TraceWrappers(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/svg/SVGAnimatedLengthList.h b/third_party/WebKit/Source/core/svg/SVGAnimatedLengthList.h
index 49e6c918..9acf97d 100644
--- a/third_party/WebKit/Source/core/svg/SVGAnimatedLengthList.h
+++ b/third_party/WebKit/Source/core/svg/SVGAnimatedLengthList.h
@@ -52,7 +52,8 @@
   }
 
   DEFINE_INLINE_VIRTUAL_TRACE_WRAPPERS() {
-    visitor->TraceWrappers(contextElement());
+    SVGAnimatedProperty<SVGLengthList>::TraceWrappers(visitor);
+    ScriptWrappable::TraceWrappers(visitor);
   }
 
  protected:
diff --git a/third_party/WebKit/Source/core/svg/SVGAnimatedNumber.cpp b/third_party/WebKit/Source/core/svg/SVGAnimatedNumber.cpp
index e8fe2c1..316471cc 100644
--- a/third_party/WebKit/Source/core/svg/SVGAnimatedNumber.cpp
+++ b/third_party/WebKit/Source/core/svg/SVGAnimatedNumber.cpp
@@ -50,7 +50,8 @@
 }
 
 DEFINE_TRACE_WRAPPERS(SVGAnimatedNumber) {
-  visitor->TraceWrappers(contextElement());
+  SVGAnimatedProperty<SVGNumber>::TraceWrappers(visitor);
+  ScriptWrappable::TraceWrappers(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/svg/SVGAnimatedNumberList.h b/third_party/WebKit/Source/core/svg/SVGAnimatedNumberList.h
index 61b99c6..b8838a0 100644
--- a/third_party/WebKit/Source/core/svg/SVGAnimatedNumberList.h
+++ b/third_party/WebKit/Source/core/svg/SVGAnimatedNumberList.h
@@ -50,7 +50,8 @@
   }
 
   DEFINE_INLINE_VIRTUAL_TRACE_WRAPPERS() {
-    visitor->TraceWrappers(contextElement());
+    SVGAnimatedProperty<SVGNumberList>::TraceWrappers(visitor);
+    ScriptWrappable::TraceWrappers(visitor);
   }
 
  protected:
diff --git a/third_party/WebKit/Source/core/svg/SVGAnimatedPreserveAspectRatio.h b/third_party/WebKit/Source/core/svg/SVGAnimatedPreserveAspectRatio.h
index cf681e9a..955183a 100644
--- a/third_party/WebKit/Source/core/svg/SVGAnimatedPreserveAspectRatio.h
+++ b/third_party/WebKit/Source/core/svg/SVGAnimatedPreserveAspectRatio.h
@@ -50,7 +50,8 @@
   }
 
   DEFINE_INLINE_VIRTUAL_TRACE_WRAPPERS() {
-    visitor->TraceWrappers(contextElement());
+    SVGAnimatedProperty<SVGPreserveAspectRatio>::TraceWrappers(visitor);
+    ScriptWrappable::TraceWrappers(visitor);
   }
 
  protected:
diff --git a/third_party/WebKit/Source/core/svg/SVGAnimatedRect.h b/third_party/WebKit/Source/core/svg/SVGAnimatedRect.h
index 62c97f6..2b7baf8f 100644
--- a/third_party/WebKit/Source/core/svg/SVGAnimatedRect.h
+++ b/third_party/WebKit/Source/core/svg/SVGAnimatedRect.h
@@ -48,7 +48,8 @@
   }
 
   DEFINE_INLINE_VIRTUAL_TRACE_WRAPPERS() {
-    visitor->TraceWrappers(contextElement());
+    SVGAnimatedProperty<SVGRect>::TraceWrappers(visitor);
+    ScriptWrappable::TraceWrappers(visitor);
   }
 
  protected:
diff --git a/third_party/WebKit/Source/core/svg/SVGAnimatedString.cpp b/third_party/WebKit/Source/core/svg/SVGAnimatedString.cpp
index dfe0edb..5f10195c 100644
--- a/third_party/WebKit/Source/core/svg/SVGAnimatedString.cpp
+++ b/third_party/WebKit/Source/core/svg/SVGAnimatedString.cpp
@@ -20,7 +20,8 @@
 }
 
 DEFINE_TRACE_WRAPPERS(SVGAnimatedString) {
-  visitor->TraceWrappers(contextElement());
+  SVGAnimatedProperty<SVGString>::TraceWrappers(visitor);
+  ScriptWrappable::TraceWrappers(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/svg/SVGAnimatedTransformList.h b/third_party/WebKit/Source/core/svg/SVGAnimatedTransformList.h
index f45df33..b348bad9 100644
--- a/third_party/WebKit/Source/core/svg/SVGAnimatedTransformList.h
+++ b/third_party/WebKit/Source/core/svg/SVGAnimatedTransformList.h
@@ -54,7 +54,8 @@
   }
 
   DEFINE_INLINE_VIRTUAL_TRACE_WRAPPERS() {
-    visitor->TraceWrappers(contextElement());
+    SVGAnimatedProperty<SVGTransformList>::TraceWrappers(visitor);
+    ScriptWrappable::TraceWrappers(visitor);
   }
 
  protected:
diff --git a/third_party/WebKit/Source/core/svg/SVGLengthListTearOff.h b/third_party/WebKit/Source/core/svg/SVGLengthListTearOff.h
index 514a99a..bf47f36 100644
--- a/third_party/WebKit/Source/core/svg/SVGLengthListTearOff.h
+++ b/third_party/WebKit/Source/core/svg/SVGLengthListTearOff.h
@@ -52,7 +52,9 @@
   }
 
   DEFINE_INLINE_VIRTUAL_TRACE_WRAPPERS() {
-    visitor->TraceWrappers(contextElement());
+    SVGListPropertyTearOffHelper<SVGLengthListTearOff,
+                                 SVGLengthList>::TraceWrappers(visitor);
+    ScriptWrappable::TraceWrappers(visitor);
   }
 
  private:
diff --git a/third_party/WebKit/Source/core/svg/SVGLengthTearOff.cpp b/third_party/WebKit/Source/core/svg/SVGLengthTearOff.cpp
index 9145a353..02afe89 100644
--- a/third_party/WebKit/Source/core/svg/SVGLengthTearOff.cpp
+++ b/third_party/WebKit/Source/core/svg/SVGLengthTearOff.cpp
@@ -250,7 +250,8 @@
 }
 
 DEFINE_TRACE_WRAPPERS(SVGLengthTearOff) {
-  visitor->TraceWrappers(contextElement());
+  SVGPropertyTearOff<SVGLength>::TraceWrappers(visitor);
+  ScriptWrappable::TraceWrappers(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/svg/SVGNumberListTearOff.h b/third_party/WebKit/Source/core/svg/SVGNumberListTearOff.h
index ba394ef..d56b6be5 100644
--- a/third_party/WebKit/Source/core/svg/SVGNumberListTearOff.h
+++ b/third_party/WebKit/Source/core/svg/SVGNumberListTearOff.h
@@ -52,7 +52,9 @@
   }
 
   DEFINE_INLINE_VIRTUAL_TRACE_WRAPPERS() {
-    visitor->TraceWrappers(contextElement());
+    SVGListPropertyTearOffHelper<SVGNumberListTearOff,
+                                 SVGNumberList>::TraceWrappers(visitor);
+    ScriptWrappable::TraceWrappers(visitor);
   }
 
  private:
diff --git a/third_party/WebKit/Source/core/svg/SVGNumberTearOff.cpp b/third_party/WebKit/Source/core/svg/SVGNumberTearOff.cpp
index c274ff7e..abea236 100644
--- a/third_party/WebKit/Source/core/svg/SVGNumberTearOff.cpp
+++ b/third_party/WebKit/Source/core/svg/SVGNumberTearOff.cpp
@@ -58,7 +58,8 @@
 }
 
 DEFINE_TRACE_WRAPPERS(SVGNumberTearOff) {
-  visitor->TraceWrappers(contextElement());
+  SVGPropertyTearOff<SVGNumber>::TraceWrappers(visitor);
+  ScriptWrappable::TraceWrappers(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/svg/SVGPointListTearOff.h b/third_party/WebKit/Source/core/svg/SVGPointListTearOff.h
index db3e349..d16a2ae 100644
--- a/third_party/WebKit/Source/core/svg/SVGPointListTearOff.h
+++ b/third_party/WebKit/Source/core/svg/SVGPointListTearOff.h
@@ -51,7 +51,9 @@
   }
 
   DEFINE_INLINE_VIRTUAL_TRACE_WRAPPERS() {
-    visitor->TraceWrappers(contextElement());
+    SVGListPropertyTearOffHelper<SVGPointListTearOff,
+                                 SVGPointList>::TraceWrappers(visitor);
+    ScriptWrappable::TraceWrappers(visitor);
   }
 
  private:
diff --git a/third_party/WebKit/Source/core/svg/SVGPointTearOff.cpp b/third_party/WebKit/Source/core/svg/SVGPointTearOff.cpp
index bc69419..efad53a 100644
--- a/third_party/WebKit/Source/core/svg/SVGPointTearOff.cpp
+++ b/third_party/WebKit/Source/core/svg/SVGPointTearOff.cpp
@@ -73,7 +73,8 @@
 }
 
 DEFINE_TRACE_WRAPPERS(SVGPointTearOff) {
-  visitor->TraceWrappers(contextElement());
+  SVGPropertyTearOff<SVGPoint>::TraceWrappers(visitor);
+  ScriptWrappable::TraceWrappers(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/svg/SVGPreserveAspectRatioTearOff.cpp b/third_party/WebKit/Source/core/svg/SVGPreserveAspectRatioTearOff.cpp
index fe06339..8831f56d 100644
--- a/third_party/WebKit/Source/core/svg/SVGPreserveAspectRatioTearOff.cpp
+++ b/third_party/WebKit/Source/core/svg/SVGPreserveAspectRatioTearOff.cpp
@@ -82,7 +82,8 @@
                                                  attribute_name) {}
 
 DEFINE_TRACE_WRAPPERS(SVGPreserveAspectRatioTearOff) {
-  visitor->TraceWrappers(contextElement());
+  SVGPropertyTearOff<SVGPreserveAspectRatio>::TraceWrappers(visitor);
+  ScriptWrappable::TraceWrappers(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/svg/SVGRectTearOff.cpp b/third_party/WebKit/Source/core/svg/SVGRectTearOff.cpp
index 14820d6d..9a27b1eb 100644
--- a/third_party/WebKit/Source/core/svg/SVGRectTearOff.cpp
+++ b/third_party/WebKit/Source/core/svg/SVGRectTearOff.cpp
@@ -85,7 +85,8 @@
 }
 
 DEFINE_TRACE_WRAPPERS(SVGRectTearOff) {
-  visitor->TraceWrappers(contextElement());
+  SVGPropertyTearOff<SVGRect>::TraceWrappers(visitor);
+  ScriptWrappable::TraceWrappers(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/svg/SVGStringListTearOff.cpp b/third_party/WebKit/Source/core/svg/SVGStringListTearOff.cpp
index 96bff941..5b2d2dc7a 100644
--- a/third_party/WebKit/Source/core/svg/SVGStringListTearOff.cpp
+++ b/third_party/WebKit/Source/core/svg/SVGStringListTearOff.cpp
@@ -45,6 +45,7 @@
                                         attribute_name) {}
 
 DEFINE_TRACE_WRAPPERS(SVGStringListTearOff) {
-  visitor->TraceWrappers(contextElement());
+  SVGPropertyTearOff<SVGStringList>::TraceWrappers(visitor);
+  ScriptWrappable::TraceWrappers(visitor);
 }
 }
diff --git a/third_party/WebKit/Source/core/svg/SVGTransformListTearOff.cpp b/third_party/WebKit/Source/core/svg/SVGTransformListTearOff.cpp
index 8619d137..2d507019 100644
--- a/third_party/WebKit/Source/core/svg/SVGTransformListTearOff.cpp
+++ b/third_party/WebKit/Source/core/svg/SVGTransformListTearOff.cpp
@@ -62,7 +62,9 @@
 }
 
 DEFINE_TRACE_WRAPPERS(SVGTransformListTearOff) {
-  visitor->TraceWrappers(contextElement());
+  SVGListPropertyTearOffHelper<SVGTransformListTearOff,
+                               SVGTransformList>::TraceWrappers(visitor);
+  ScriptWrappable::TraceWrappers(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/svg/SVGTransformTearOff.cpp b/third_party/WebKit/Source/core/svg/SVGTransformTearOff.cpp
index 4f2b54ca..417209b 100644
--- a/third_party/WebKit/Source/core/svg/SVGTransformTearOff.cpp
+++ b/third_party/WebKit/Source/core/svg/SVGTransformTearOff.cpp
@@ -52,6 +52,11 @@
   SVGPropertyTearOff<SVGTransform>::Trace(visitor);
 }
 
+DEFINE_TRACE_WRAPPERS(SVGTransformTearOff) {
+  SVGPropertyTearOff<SVGTransform>::TraceWrappers(visitor);
+  ScriptWrappable::TraceWrappers(visitor);
+}
+
 SVGTransformTearOff* SVGTransformTearOff::CreateDetached() {
   return Create(SVGTransform::Create(blink::kSvgTransformMatrix), nullptr,
                 kPropertyIsNotAnimVal, QualifiedName::Null());
@@ -130,8 +135,4 @@
   CommitChange();
 }
 
-DEFINE_TRACE_WRAPPERS(SVGTransformTearOff) {
-  visitor->TraceWrappers(contextElement());
-}
-
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/svg/properties/SVGAnimatedProperty.h b/third_party/WebKit/Source/core/svg/properties/SVGAnimatedProperty.h
index 41138df..ce958184 100644
--- a/third_party/WebKit/Source/core/svg/properties/SVGAnimatedProperty.h
+++ b/third_party/WebKit/Source/core/svg/properties/SVGAnimatedProperty.h
@@ -81,6 +81,10 @@
 
   DEFINE_INLINE_VIRTUAL_TRACE() {}
 
+  DEFINE_INLINE_VIRTUAL_TRACE_WRAPPERS() {
+    visitor->TraceWrappersWithManualWriteBarrier(context_element_.Get());
+  }
+
  protected:
   SVGAnimatedPropertyBase(AnimatedPropertyType,
                           SVGElement*,
diff --git a/third_party/WebKit/Source/core/svg/properties/SVGListPropertyTearOffHelper.h b/third_party/WebKit/Source/core/svg/properties/SVGListPropertyTearOffHelper.h
index db00a87c..4117242 100644
--- a/third_party/WebKit/Source/core/svg/properties/SVGListPropertyTearOffHelper.h
+++ b/third_party/WebKit/Source/core/svg/properties/SVGListPropertyTearOffHelper.h
@@ -182,6 +182,10 @@
     return CreateItemTearOff(value);
   }
 
+  DEFINE_INLINE_VIRTUAL_TRACE_WRAPPERS() {
+    SVGPropertyTearOff<ListProperty>::TraceWrappers(visitor);
+  }
+
  protected:
   SVGListPropertyTearOffHelper(
       ListPropertyType* target,
diff --git a/third_party/WebKit/Source/core/svg/properties/SVGPropertyTearOff.h b/third_party/WebKit/Source/core/svg/properties/SVGPropertyTearOff.h
index 70edbe586..62140ec 100644
--- a/third_party/WebKit/Source/core/svg/properties/SVGPropertyTearOff.h
+++ b/third_party/WebKit/Source/core/svg/properties/SVGPropertyTearOff.h
@@ -66,11 +66,18 @@
     DCHECK(context_element);
     DCHECK(attribute_name != QualifiedName::Null());
     context_element_ = context_element;
+    // Requires SVGPropertyTearOffBase to be the left-most class in the
+    // inheritance hierarchy.
+    ScriptWrappableVisitor::WriteBarrier(this, context_element_.Get());
     attribute_name_ = attribute_name;
   }
 
   DEFINE_INLINE_VIRTUAL_TRACE() {}
 
+  DEFINE_INLINE_VIRTUAL_TRACE_WRAPPERS() {
+    visitor->TraceWrappersWithManualWriteBarrier(context_element_.Get());
+  }
+
   static void ThrowReadOnly(ExceptionState&);
 
  protected:
@@ -108,6 +115,10 @@
     SVGPropertyTearOffBase::Trace(visitor);
   }
 
+  DEFINE_INLINE_VIRTUAL_TRACE_WRAPPERS() {
+    SVGPropertyTearOffBase::TraceWrappers(visitor);
+  }
+
  protected:
   SVGPropertyTearOff(Property* target,
                      SVGElement* context_element,
diff --git a/third_party/WebKit/Source/modules/cachestorage/InspectorCacheStorageAgent.cpp b/third_party/WebKit/Source/modules/cachestorage/InspectorCacheStorageAgent.cpp
index e56047d9..d2c2860 100644
--- a/third_party/WebKit/Source/modules/cachestorage/InspectorCacheStorageAgent.cpp
+++ b/third_party/WebKit/Source/modules/cachestorage/InspectorCacheStorageAgent.cpp
@@ -30,9 +30,11 @@
 #include "public/platform/modules/serviceworker/WebServiceWorkerResponse.h"
 
 using blink::protocol::Array;
-using blink::protocol::CacheStorage::Cache;
+// Renaming Cache since there is another blink::Cache.
+using ProtocolCache = blink::protocol::CacheStorage::Cache;
 using blink::protocol::CacheStorage::DataEntry;
-using blink::protocol::Response;
+// Renaming Response since there is another blink::Response.
+using ProtocolResponse = blink::protocol::Response;
 
 typedef blink::protocol::CacheStorage::Backend::DeleteCacheCallback
     DeleteCacheCallback;
@@ -55,41 +57,44 @@
   return id;
 }
 
-Response ParseCacheId(const String& id,
-                      String* security_origin,
-                      String* cache_name) {
+ProtocolResponse ParseCacheId(const String& id,
+                              String* security_origin,
+                              String* cache_name) {
   size_t pipe = id.find('|');
   if (pipe == WTF::kNotFound)
-    return Response::Error("Invalid cache id.");
+    return ProtocolResponse::Error("Invalid cache id.");
   *security_origin = id.Substring(0, pipe);
   *cache_name = id.Substring(pipe + 1);
-  return Response::OK();
+  return ProtocolResponse::OK();
 }
 
-Response AssertCacheStorage(
+ProtocolResponse AssertCacheStorage(
     const String& security_origin,
     std::unique_ptr<WebServiceWorkerCacheStorage>& result) {
   RefPtr<SecurityOrigin> sec_origin =
       SecurityOrigin::CreateFromString(security_origin);
 
   // Cache Storage API is restricted to trustworthy origins.
-  if (!sec_origin->IsPotentiallyTrustworthy())
-    return Response::Error(sec_origin->IsPotentiallyTrustworthyErrorMessage());
+  if (!sec_origin->IsPotentiallyTrustworthy()) {
+    return ProtocolResponse::Error(
+        sec_origin->IsPotentiallyTrustworthyErrorMessage());
+  }
 
   std::unique_ptr<WebServiceWorkerCacheStorage> cache =
       Platform::Current()->CreateCacheStorage(WebSecurityOrigin(sec_origin));
   if (!cache)
-    return Response::Error("Could not find cache storage.");
+    return ProtocolResponse::Error("Could not find cache storage.");
   result = std::move(cache);
-  return Response::OK();
+  return ProtocolResponse::OK();
 }
 
-Response AssertCacheStorageAndNameForId(
+ProtocolResponse AssertCacheStorageAndNameForId(
     const String& cache_id,
     String* cache_name,
     std::unique_ptr<WebServiceWorkerCacheStorage>& result) {
   String security_origin;
-  Response response = ParseCacheId(cache_id, &security_origin, cache_name);
+  ProtocolResponse response =
+      ParseCacheId(cache_id, &security_origin, cache_name);
   if (!response.isSuccess())
     return response;
   return AssertCacheStorage(security_origin, result);
@@ -129,11 +134,12 @@
   ~RequestCacheNames() override {}
 
   void OnSuccess(const WebVector<WebString>& caches) override {
-    std::unique_ptr<Array<Cache>> array = Array<Cache>::create();
+    std::unique_ptr<Array<ProtocolCache>> array =
+        Array<ProtocolCache>::create();
     for (size_t i = 0; i < caches.size(); i++) {
       String name = String(caches[i]);
-      std::unique_ptr<Cache> entry =
-          Cache::create()
+      std::unique_ptr<ProtocolCache> entry =
+          ProtocolCache::create()
               .setSecurityOrigin(security_origin_)
               .setCacheName(name)
               .setCacheId(BuildCacheId(security_origin_, name))
@@ -144,7 +150,7 @@
   }
 
   void OnError(WebServiceWorkerCacheError error) override {
-    callback_->sendFailure(Response::Error(
+    callback_->sendFailure(ProtocolResponse::Error(
         String::Format("Error requesting cache names: %s",
                        ServiceWorkerCacheErrorString(error).data())));
   }
@@ -218,7 +224,9 @@
     callback_->sendSuccess(std::move(array), has_more);
   }
 
-  void SendFailure(const Response& error) { callback_->sendFailure(error); }
+  void SendFailure(const ProtocolResponse& error) {
+    callback_->sendFailure(error);
+  }
 
  private:
   DataRequestParams params_;
@@ -243,7 +251,7 @@
   }
 
   void OnError(WebServiceWorkerCacheError error) override {
-    accumulator_->SendFailure(Response::Error(
+    accumulator_->SendFailure(ProtocolResponse::Error(
         String::Format("Error requesting responses for cache  %s: %s",
                        params_.cache_name.Utf8().data(),
                        ServiceWorkerCacheErrorString(error).data())));
@@ -289,7 +297,7 @@
   }
 
   void OnError(WebServiceWorkerCacheError error) override {
-    callback_->sendFailure(Response::Error(
+    callback_->sendFailure(ProtocolResponse::Error(
         String::Format("Error requesting requests for cache %s: %s",
                        params_.cache_name.Utf8().data(),
                        ServiceWorkerCacheErrorString(error).data())));
@@ -320,7 +328,7 @@
   }
 
   void OnError(WebServiceWorkerCacheError error) override {
-    callback_->sendFailure(Response::Error(String::Format(
+    callback_->sendFailure(ProtocolResponse::Error(String::Format(
         "Error requesting cache %s: %s", params_.cache_name.Utf8().data(),
         ServiceWorkerCacheErrorString(error).data())));
   }
@@ -341,7 +349,7 @@
   void OnSuccess() override { callback_->sendSuccess(); }
 
   void OnError(WebServiceWorkerCacheError error) override {
-    callback_->sendFailure(Response::Error(
+    callback_->sendFailure(ProtocolResponse::Error(
         String::Format("Error requesting cache names: %s",
                        ServiceWorkerCacheErrorString(error).data())));
   }
@@ -361,7 +369,7 @@
   void OnSuccess() override { callback_->sendSuccess(); }
 
   void OnError(WebServiceWorkerCacheError error) override {
-    callback_->sendFailure(Response::Error(
+    callback_->sendFailure(ProtocolResponse::Error(
         String::Format("Error requesting cache names: %s",
                        ServiceWorkerCacheErrorString(error).data())));
   }
@@ -397,7 +405,7 @@
   }
 
   void OnError(WebServiceWorkerCacheError error) override {
-    callback_->sendFailure(Response::Error(String::Format(
+    callback_->sendFailure(ProtocolResponse::Error(String::Format(
         "Error requesting cache %s: %s", cache_name_.Utf8().data(),
         ServiceWorkerCacheErrorString(error).data())));
   }
@@ -428,12 +436,12 @@
   if (!sec_origin->IsPotentiallyTrustworthy()) {
     // Don't treat this as an error, just don't attempt to open and enumerate
     // the caches.
-    callback->sendSuccess(Array<protocol::CacheStorage::Cache>::create());
+    callback->sendSuccess(Array<ProtocolCache>::create());
     return;
   }
 
   std::unique_ptr<WebServiceWorkerCacheStorage> cache;
-  Response response = AssertCacheStorage(security_origin, cache);
+  ProtocolResponse response = AssertCacheStorage(security_origin, cache);
   if (!response.isSuccess()) {
     callback->sendFailure(response);
     return;
@@ -449,7 +457,7 @@
     std::unique_ptr<RequestEntriesCallback> callback) {
   String cache_name;
   std::unique_ptr<WebServiceWorkerCacheStorage> cache;
-  Response response =
+  ProtocolResponse response =
       AssertCacheStorageAndNameForId(cache_id, &cache_name, cache);
   if (!response.isSuccess()) {
     callback->sendFailure(response);
@@ -469,7 +477,7 @@
     std::unique_ptr<DeleteCacheCallback> callback) {
   String cache_name;
   std::unique_ptr<WebServiceWorkerCacheStorage> cache;
-  Response response =
+  ProtocolResponse response =
       AssertCacheStorageAndNameForId(cache_id, &cache_name, cache);
   if (!response.isSuccess()) {
     callback->sendFailure(response);
@@ -485,7 +493,7 @@
     std::unique_ptr<DeleteEntryCallback> callback) {
   String cache_name;
   std::unique_ptr<WebServiceWorkerCacheStorage> cache;
-  Response response =
+  ProtocolResponse response =
       AssertCacheStorageAndNameForId(cache_id, &cache_name, cache);
   if (!response.isSuccess()) {
     callback->sendFailure(response);
diff --git a/third_party/WebKit/Source/modules/modules_idl_files.gni b/third_party/WebKit/Source/modules/modules_idl_files.gni
index a5c52308..0e456bb 100644
--- a/third_party/WebKit/Source/modules/modules_idl_files.gni
+++ b/third_party/WebKit/Source/modules/modules_idl_files.gni
@@ -290,6 +290,7 @@
                     "webaudio/AudioListener.idl",
                     "webaudio/AudioNode.idl",
                     "webaudio/AudioParam.idl",
+                    "webaudio/AudioParamMap.idl",
                     "webaudio/AudioProcessingEvent.idl",
                     "webaudio/AudioScheduledSourceNode.idl",
                     "webaudio/AudioWorkletGlobalScope.idl",
diff --git a/third_party/WebKit/Source/modules/webaudio/AudioParamMap.cpp b/third_party/WebKit/Source/modules/webaudio/AudioParamMap.cpp
new file mode 100644
index 0000000..5b5ba6f2
--- /dev/null
+++ b/third_party/WebKit/Source/modules/webaudio/AudioParamMap.cpp
@@ -0,0 +1,65 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "modules/webaudio/AudioParamMap.h"
+
+namespace blink {
+
+class AudioParamMapIterationSource final
+    : public PairIterable<String, AudioParam*>::IterationSource {
+ public:
+  AudioParamMapIterationSource(
+      const HeapHashMap<String, Member<AudioParam>>& map) {
+    for (const auto name : map.Keys()) {
+      parameter_names_.push_back(name);
+      parameter_objects_.push_back(map.at(name));
+    }
+  }
+
+  bool Next(ScriptState* scrip_state,
+            String& key,
+            AudioParam*& audio_param,
+            ExceptionState&) override {
+    if (current_index_ == parameter_names_.size())
+      return false;
+    key = parameter_names_[current_index_];
+    audio_param = parameter_objects_[current_index_];
+    ++current_index_;
+    return true;
+  }
+
+  DEFINE_INLINE_VIRTUAL_TRACE() {
+    visitor->Trace(parameter_objects_);
+    PairIterable<String, AudioParam*>::IterationSource::Trace(visitor);
+  }
+
+ private:
+  // For sequential iteration (e.g. Next()).
+  Vector<String> parameter_names_;
+  HeapVector<Member<AudioParam>> parameter_objects_;
+  unsigned current_index_;
+};
+
+AudioParamMap::AudioParamMap(
+    const HeapHashMap<String, Member<AudioParam>>& parameter_map)
+    : parameter_map_(parameter_map) {}
+
+PairIterable<String, AudioParam*>::IterationSource*
+    AudioParamMap::StartIteration(ScriptState*, ExceptionState&) {
+  return new AudioParamMapIterationSource(parameter_map_);
+}
+
+bool AudioParamMap::GetMapEntry(ScriptState*,
+                                const String& key,
+                                AudioParam*& audio_param,
+                                ExceptionState&) {
+  if (parameter_map_.Contains(key)) {
+    audio_param = parameter_map_.at(key);
+    return true;
+  }
+
+  return false;
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/modules/webaudio/AudioParamMap.h b/third_party/WebKit/Source/modules/webaudio/AudioParamMap.h
new file mode 100644
index 0000000..d263e7b
--- /dev/null
+++ b/third_party/WebKit/Source/modules/webaudio/AudioParamMap.h
@@ -0,0 +1,51 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef AudioParamMap_h
+#define AudioParamMap_h
+
+#include "bindings/core/v8/ExceptionState.h"
+#include "bindings/core/v8/Maplike.h"
+#include "bindings/core/v8/V8BindingForCore.h"
+#include "modules/webaudio/AudioParam.h"
+#include "platform/bindings/ScriptWrappable.h"
+#include "platform/heap/Handle.h"
+#include "platform/wtf/text/WTFString.h"
+
+namespace blink {
+
+class AudioParam;
+
+class AudioParamMap final : public GarbageCollectedFinalized<AudioParamMap>,
+                            public ScriptWrappable,
+                            public Maplike<String, AudioParam*> {
+  DEFINE_WRAPPERTYPEINFO();
+
+ public:
+  explicit AudioParamMap(
+      const HeapHashMap<String, Member<AudioParam>>& parameter_map);
+
+  // IDL attributes / methods
+  size_t size() const { return parameter_map_.size(); }
+
+  AudioParam* At(String name) { return parameter_map_.at(name); }
+  bool Contains(String name) { return parameter_map_.Contains(name); }
+
+  DEFINE_INLINE_VIRTUAL_TRACE() { visitor->Trace(parameter_map_); }
+
+ private:
+  PairIterable<String, AudioParam*>::IterationSource* StartIteration(
+      ScriptState*,
+      ExceptionState&) override;
+  bool GetMapEntry(ScriptState*,
+                   const String& key,
+                   AudioParam*&,
+                   ExceptionState&) override;
+
+  const HeapHashMap<String, Member<AudioParam>> parameter_map_;
+};
+
+}  // namespace blink
+
+#endif
diff --git a/third_party/WebKit/Source/modules/webaudio/AudioParamMap.idl b/third_party/WebKit/Source/modules/webaudio/AudioParamMap.idl
new file mode 100644
index 0000000..740c70f
--- /dev/null
+++ b/third_party/WebKit/Source/modules/webaudio/AudioParamMap.idl
@@ -0,0 +1,10 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// https://webaudio.github.io/web-audio-api/#idl-def-AudioParamMap
+[
+  RuntimeEnabled=AudioWorklet
+] interface AudioParamMap {
+    readonly maplike<DOMString, AudioParam>;
+};
diff --git a/third_party/WebKit/Source/modules/webaudio/BUILD.gn b/third_party/WebKit/Source/modules/webaudio/BUILD.gn
index 8cfcd64..e9ecd2a 100644
--- a/third_party/WebKit/Source/modules/webaudio/BUILD.gn
+++ b/third_party/WebKit/Source/modules/webaudio/BUILD.gn
@@ -32,6 +32,8 @@
     "AudioNodeOutput.h",
     "AudioParam.cpp",
     "AudioParam.h",
+    "AudioParamMap.cpp",
+    "AudioParamMap.h",
     "AudioParamTimeline.cpp",
     "AudioParamTimeline.h",
     "AudioProcessingEvent.cpp",
diff --git a/third_party/WebKit/Source/platform/bindings/V8PerContextData.cpp b/third_party/WebKit/Source/platform/bindings/V8PerContextData.cpp
index 3a955b8..5aadd390 100644
--- a/third_party/WebKit/Source/platform/bindings/V8PerContextData.cpp
+++ b/third_party/WebKit/Source/platform/bindings/V8PerContextData.cpp
@@ -51,18 +51,6 @@
       activity_logger_(nullptr) {
   context_holder_->SetContext(context);
 
-  v8::Context::Scope context_scope(context);
-  DCHECK(error_prototype_.IsEmpty());
-  v8::Local<v8::Value> object_value =
-      context->Global()
-          ->Get(context, V8AtomicString(isolate_, "Error"))
-          .ToLocalChecked();
-  v8::Local<v8::Value> prototype_value =
-      object_value.As<v8::Object>()
-          ->Get(context, V8AtomicString(isolate_, "prototype"))
-          .ToLocalChecked();
-  error_prototype_.Set(isolate_, prototype_value);
-
   if (IsMainThread()) {
     InstanceCounters::IncrementCounter(
         InstanceCounters::kV8PerContextDataCounter);
@@ -87,8 +75,6 @@
 
 v8::Local<v8::Object> V8PerContextData::CreateWrapperFromCacheSlowCase(
     const WrapperTypeInfo* type) {
-  DCHECK(!error_prototype_.IsEmpty());
-
   v8::Context::Scope scope(GetContext());
   v8::Local<v8::Function> interface_object = ConstructorForType(type);
   CHECK(!interface_object.IsEmpty());
@@ -101,8 +87,6 @@
 
 v8::Local<v8::Function> V8PerContextData::ConstructorForTypeSlowCase(
     const WrapperTypeInfo* type) {
-  DCHECK(!error_prototype_.IsEmpty());
-
   v8::Local<v8::Context> current_context = GetContext();
   v8::Context::Scope scope(current_context);
   const DOMWrapperWorld& world = DOMWrapperWorld::World(current_context);
@@ -147,13 +131,6 @@
     type->PreparePrototypeAndInterfaceObject(current_context, world,
                                              prototype_object, interface_object,
                                              interface_template);
-    if (type->wrapper_type_prototype ==
-        WrapperTypeInfo::kWrapperTypeExceptionPrototype) {
-      if (!V8CallBoolean(prototype_object->SetPrototype(
-              current_context, error_prototype_.NewLocal(isolate_)))) {
-        return v8::Local<v8::Function>();
-      }
-    }
   }
 
   // Origin Trials
diff --git a/third_party/WebKit/Source/platform/bindings/V8PerContextData.h b/third_party/WebKit/Source/platform/bindings/V8PerContextData.h
index 8e016e2..787153b 100644
--- a/third_party/WebKit/Source/platform/bindings/V8PerContextData.h
+++ b/third_party/WebKit/Source/platform/bindings/V8PerContextData.h
@@ -150,7 +150,6 @@
   std::unique_ptr<gin::ContextHolder> context_holder_;
 
   ScopedPersistent<v8::Context> context_;
-  ScopedPersistent<v8::Value> error_prototype_;
 
   ScopedPersistent<v8::Private> private_custom_element_definition_id_;
 
diff --git a/third_party/WebKit/Source/platform/fonts/AlternateFontFamily.h b/third_party/WebKit/Source/platform/fonts/AlternateFontFamily.h
index 6b7f0976b..133680c 100644
--- a/third_party/WebKit/Source/platform/fonts/AlternateFontFamily.h
+++ b/third_party/WebKit/Source/platform/fonts/AlternateFontFamily.h
@@ -32,6 +32,7 @@
 #define AlternateFontFamily_h
 
 #include "build/build_config.h"
+#include "platform/FontFamilyNames.h"
 #include "platform/fonts/FontDescription.h"
 #include "platform/wtf/text/AtomicString.h"
 
@@ -47,27 +48,20 @@
   // 'Courier' is a bitmap font. On Mac on the other hand 'Courier' is
   // a truetype font. Thus pages asking for Courier are better of
   // using 'Courier New' on windows.
-  DEFINE_STATIC_LOCAL(AtomicString, courier, ("Courier"));
-  DEFINE_STATIC_LOCAL(AtomicString, courier_new, ("Courier New"));
-  if (DeprecatedEqualIgnoringCase(family_name, courier))
-    return courier_new;
+  if (EqualIgnoringASCIICase(family_name, FontFamilyNames::Courier))
+    return FontFamilyNames::Courier_New;
 
   // Alias 'MS Sans Serif' (bitmap font) -> 'Microsoft Sans Serif'
   // (truetype font).
-  DEFINE_STATIC_LOCAL(AtomicString, ms_sans, ("MS Sans Serif"));
-  DEFINE_STATIC_LOCAL(AtomicString, microsoft_sans, ("Microsoft Sans Serif"));
-  if (DeprecatedEqualIgnoringCase(family_name, ms_sans))
-    return microsoft_sans;
+  if (EqualIgnoringASCIICase(family_name, FontFamilyNames::MS_Sans_Serif))
+    return FontFamilyNames::Microsoft_Sans_Serif;
 
   // Alias 'MS Serif' (bitmap) -> 'Times New Roman' (truetype font).
   // Alias 'Times' -> 'Times New Roman' (truetype font).
   // There's no 'Microsoft Sans Serif-equivalent' for Serif.
-  DEFINE_STATIC_LOCAL(AtomicString, ms_serif, ("MS Serif"));
-  DEFINE_STATIC_LOCAL(AtomicString, times, ("Times"));
-  DEFINE_STATIC_LOCAL(AtomicString, times_new_roman, ("Times New Roman"));
-  if (DeprecatedEqualIgnoringCase(family_name, ms_serif) ||
-      DeprecatedEqualIgnoringCase(family_name, times))
-    return times_new_roman;
+  if (EqualIgnoringASCIICase(family_name, FontFamilyNames::MS_Serif) ||
+      EqualIgnoringASCIICase(family_name, FontFamilyNames::Times))
+    return FontFamilyNames::Times_New_Roman;
 #endif
 
   return family_name;
@@ -76,56 +70,44 @@
 inline const AtomicString& AlternateFamilyName(
     const AtomicString& family_name) {
   // Alias Courier <-> Courier New
-  DEFINE_STATIC_LOCAL(AtomicString, courier, ("Courier"));
-  DEFINE_STATIC_LOCAL(AtomicString, courier_new, ("Courier New"));
-  if (DeprecatedEqualIgnoringCase(family_name, courier))
-    return courier_new;
+  if (EqualIgnoringASCIICase(family_name, FontFamilyNames::Courier))
+    return FontFamilyNames::Courier_New;
 #if !defined(OS_WIN)
   // On Windows, Courier New (truetype font) is always present and
   // Courier is a bitmap font. So, we don't want to map Courier New to
   // Courier.
-  if (DeprecatedEqualIgnoringCase(family_name, courier_new))
-    return courier;
+  if (EqualIgnoringASCIICase(family_name, FontFamilyNames::Courier_New))
+    return FontFamilyNames::Courier;
 #endif
 
   // Alias Times and Times New Roman.
-  DEFINE_STATIC_LOCAL(AtomicString, times, ("Times"));
-  DEFINE_STATIC_LOCAL(AtomicString, times_new_roman, ("Times New Roman"));
-  if (DeprecatedEqualIgnoringCase(family_name, times))
-    return times_new_roman;
-  if (DeprecatedEqualIgnoringCase(family_name, times_new_roman))
-    return times;
+  if (EqualIgnoringASCIICase(family_name, FontFamilyNames::Times))
+    return FontFamilyNames::Times_New_Roman;
+  if (EqualIgnoringASCIICase(family_name, FontFamilyNames::Times_New_Roman))
+    return FontFamilyNames::Times;
 
   // Alias Arial and Helvetica
-  DEFINE_STATIC_LOCAL(AtomicString, arial, ("Arial"));
-  DEFINE_STATIC_LOCAL(AtomicString, helvetica, ("Helvetica"));
-  if (DeprecatedEqualIgnoringCase(family_name, arial))
-    return helvetica;
-  if (DeprecatedEqualIgnoringCase(family_name, helvetica))
-    return arial;
+  if (EqualIgnoringASCIICase(family_name, FontFamilyNames::Arial))
+    return FontFamilyNames::Helvetica;
+  if (EqualIgnoringASCIICase(family_name, FontFamilyNames::Helvetica))
+    return FontFamilyNames::Arial;
 
   return g_empty_atom;
 }
 
-inline const AtomicString GetFallbackFontFamily(
+inline const AtomicString& GetFallbackFontFamily(
     const FontDescription& description) {
-  DEFINE_STATIC_LOCAL(const AtomicString, sans_str, ("sans-serif"));
-  DEFINE_STATIC_LOCAL(const AtomicString, serif_str, ("serif"));
-  DEFINE_STATIC_LOCAL(const AtomicString, monospace_str, ("monospace"));
-  DEFINE_STATIC_LOCAL(const AtomicString, cursive_str, ("cursive"));
-  DEFINE_STATIC_LOCAL(const AtomicString, fantasy_str, ("fantasy"));
-
   switch (description.GenericFamily()) {
     case FontDescription::kSansSerifFamily:
-      return sans_str;
+      return FontFamilyNames::sans_serif;
     case FontDescription::kSerifFamily:
-      return serif_str;
+      return FontFamilyNames::serif;
     case FontDescription::kMonospaceFamily:
-      return monospace_str;
+      return FontFamilyNames::monospace;
     case FontDescription::kCursiveFamily:
-      return cursive_str;
+      return FontFamilyNames::cursive;
     case FontDescription::kFantasyFamily:
-      return fantasy_str;
+      return FontFamilyNames::fantasy;
     default:
       // Let the caller use the system default font.
       return g_empty_atom;
diff --git a/third_party/WebKit/Source/platform/fonts/FontFamilyNames.json5 b/third_party/WebKit/Source/platform/fonts/FontFamilyNames.json5
index fd34749..4d86dc7 100644
--- a/third_party/WebKit/Source/platform/fonts/FontFamilyNames.json5
+++ b/third_party/WebKit/Source/platform/fonts/FontFamilyNames.json5
@@ -13,5 +13,25 @@
     "-webkit-pictograph",
     "-webkit-standard",
     "system-ui",
+
+    "Arial",
+    "Calibri",
+    "Courier New",
+    "Courier",
+    "Helvetica",
+    "Microsoft Sans Serif",
+    "MS Sans Serif",
+    "MS Serif",
+    "MS UI Gothic",
+    "Sans",
+    "Segoe UI",
+    "Times New Roman",
+    "Times",
+
+    "cursive",
+    "fantasy",
+    "monospace",
+    "sans-serif",
+    "serif",
   ],
 }
diff --git a/third_party/WebKit/Source/platform/fonts/skia/FontCacheSkia.cpp b/third_party/WebKit/Source/platform/fonts/skia/FontCacheSkia.cpp
index 3a4e9fd..bf47c7b 100644
--- a/third_party/WebKit/Source/platform/fonts/skia/FontCacheSkia.cpp
+++ b/third_party/WebKit/Source/platform/fonts/skia/FontCacheSkia.cpp
@@ -38,6 +38,7 @@
 #include "SkTypeface.h"
 
 #include "build/build_config.h"
+#include "platform/FontFamilyNames.h"
 #include "platform/Language.h"
 #include "platform/fonts/AlternateFontFamily.h"
 #include "platform/fonts/FontCache.h"
@@ -156,59 +157,63 @@
   // We should at least have Sans or Arial which is the last resort fallback of
   // SkFontHost ports.
   if (!font_platform_data) {
-    DEFINE_STATIC_LOCAL(const FontFaceCreationParams, sans_creation_params,
-                        (AtomicString("Sans")));
+    DEFINE_THREAD_SAFE_STATIC_LOCAL(const FontFaceCreationParams,
+                                    sans_creation_params,
+                                    (FontFamilyNames::Sans));
     font_platform_data = GetFontPlatformData(description, sans_creation_params,
                                              AlternateFontName::kLastResort);
   }
   if (!font_platform_data) {
-    DEFINE_STATIC_LOCAL(const FontFaceCreationParams, arial_creation_params,
-                        (AtomicString("Arial")));
+    DEFINE_THREAD_SAFE_STATIC_LOCAL(const FontFaceCreationParams,
+                                    arial_creation_params,
+                                    (FontFamilyNames::Arial));
     font_platform_data = GetFontPlatformData(description, arial_creation_params,
                                              AlternateFontName::kLastResort);
   }
 #if defined(OS_WIN)
   // Try some more Windows-specific fallbacks.
   if (!font_platform_data) {
-    DEFINE_STATIC_LOCAL(const FontFaceCreationParams,
-                        msuigothic_creation_params,
-                        (AtomicString("MS UI Gothic")));
+    DEFINE_THREAD_SAFE_STATIC_LOCAL(const FontFaceCreationParams,
+                                    msuigothic_creation_params,
+                                    (FontFamilyNames::MS_UI_Gothic));
     font_platform_data =
         GetFontPlatformData(description, msuigothic_creation_params,
                             AlternateFontName::kLastResort);
   }
   if (!font_platform_data) {
-    DEFINE_STATIC_LOCAL(const FontFaceCreationParams,
-                        mssansserif_creation_params,
-                        (AtomicString("Microsoft Sans Serif")));
+    DEFINE_THREAD_SAFE_STATIC_LOCAL(const FontFaceCreationParams,
+                                    mssansserif_creation_params,
+                                    (FontFamilyNames::Microsoft_Sans_Serif));
     font_platform_data =
         GetFontPlatformData(description, mssansserif_creation_params,
                             AlternateFontName::kLastResort);
   }
   if (!font_platform_data) {
-    DEFINE_STATIC_LOCAL(const FontFaceCreationParams, segoeui_creation_params,
-                        (AtomicString("Segoe UI")));
+    DEFINE_THREAD_SAFE_STATIC_LOCAL(const FontFaceCreationParams,
+                                    segoeui_creation_params,
+                                    (FontFamilyNames::Segoe_UI));
     font_platform_data = GetFontPlatformData(
         description, segoeui_creation_params, AlternateFontName::kLastResort);
   }
   if (!font_platform_data) {
-    DEFINE_STATIC_LOCAL(const FontFaceCreationParams, calibri_creation_params,
-                        (AtomicString("Calibri")));
+    DEFINE_THREAD_SAFE_STATIC_LOCAL(const FontFaceCreationParams,
+                                    calibri_creation_params,
+                                    (FontFamilyNames::Calibri));
     font_platform_data = GetFontPlatformData(
         description, calibri_creation_params, AlternateFontName::kLastResort);
   }
   if (!font_platform_data) {
-    DEFINE_STATIC_LOCAL(const FontFaceCreationParams,
-                        timesnewroman_creation_params,
-                        (AtomicString("Times New Roman")));
+    DEFINE_THREAD_SAFE_STATIC_LOCAL(const FontFaceCreationParams,
+                                    timesnewroman_creation_params,
+                                    (FontFamilyNames::Times_New_Roman));
     font_platform_data =
         GetFontPlatformData(description, timesnewroman_creation_params,
                             AlternateFontName::kLastResort);
   }
   if (!font_platform_data) {
-    DEFINE_STATIC_LOCAL(const FontFaceCreationParams,
-                        couriernew_creation_params,
-                        (AtomicString("Courier New")));
+    DEFINE_THREAD_SAFE_STATIC_LOCAL(const FontFaceCreationParams,
+                                    couriernew_creation_params,
+                                    (FontFamilyNames::Courier_New));
     font_platform_data =
         GetFontPlatformData(description, couriernew_creation_params,
                             AlternateFontName::kLastResort);
diff --git a/third_party/WebKit/Source/platform/loader/fetch/ClientHintsPreferences.cpp b/third_party/WebKit/Source/platform/loader/fetch/ClientHintsPreferences.cpp
index 5498b64db..fda1729cb 100644
--- a/third_party/WebKit/Source/platform/loader/fetch/ClientHintsPreferences.cpp
+++ b/third_party/WebKit/Source/platform/loader/fetch/ClientHintsPreferences.cpp
@@ -9,18 +9,38 @@
 
 namespace blink {
 
-ClientHintsPreferences::ClientHintsPreferences()
-    : should_send_device_ram_(false),
-      should_send_dpr_(false),
-      should_send_resource_width_(false),
-      should_send_viewport_width_(false) {}
+namespace {
+
+// Mapping from WebClientHintsType to the header value for enabling the
+// corresponding client hint. The ordering should match the ordering of enums in
+// WebClientHintsType.
+static constexpr const char* kHeaderMapping[] = {"device-ram", "dpr", "width",
+                                                 "viewport-width"};
+
+static_assert(kWebClientHintsTypeLast + 1 == arraysize(kHeaderMapping),
+              "unhandled client hint type");
+
+void ParseAcceptChHeader(const String& header_value,
+                         bool enabled_types[kWebClientHintsTypeLast + 1]) {
+  CommaDelimitedHeaderSet accept_client_hints_header;
+  ParseCommaDelimitedHeader(header_value, accept_client_hints_header);
+
+  for (size_t i = 0; i < kWebClientHintsTypeLast + 1; ++i)
+    enabled_types[i] = accept_client_hints_header.Contains(kHeaderMapping[i]);
+
+  enabled_types[kWebClientHintsTypeDeviceRam] =
+      enabled_types[kWebClientHintsTypeDeviceRam] &&
+      RuntimeEnabledFeatures::DeviceRAMHeaderEnabled();
+}
+
+}  // namespace
+
+ClientHintsPreferences::ClientHintsPreferences() {}
 
 void ClientHintsPreferences::UpdateFrom(
     const ClientHintsPreferences& preferences) {
-  should_send_device_ram_ = preferences.should_send_device_ram_;
-  should_send_dpr_ = preferences.should_send_dpr_;
-  should_send_resource_width_ = preferences.should_send_resource_width_;
-  should_send_viewport_width_ = preferences.should_send_viewport_width_;
+  for (size_t i = 0; i < kWebClientHintsTypeLast + 1; ++i)
+    enabled_types_[i] = preferences.enabled_types_[i];
 }
 
 void ClientHintsPreferences::UpdateFromAcceptClientHintsHeader(
@@ -29,31 +49,18 @@
   if (!RuntimeEnabledFeatures::ClientHintsEnabled() || header_value.IsEmpty())
     return;
 
-  CommaDelimitedHeaderSet accept_client_hints_header;
-  ParseCommaDelimitedHeader(header_value, accept_client_hints_header);
-  if (RuntimeEnabledFeatures::DeviceRAMHeaderEnabled() &&
-      accept_client_hints_header.Contains("device-ram")) {
-    if (context)
-      context->CountClientHintsDeviceRAM();
-    should_send_device_ram_ = true;
-  }
+  bool new_enabled_types[kWebClientHintsTypeLast + 1] = {};
 
-  if (accept_client_hints_header.Contains("dpr")) {
-    if (context)
-      context->CountClientHintsDPR();
-    should_send_dpr_ = true;
-  }
+  ParseAcceptChHeader(header_value, new_enabled_types);
 
-  if (accept_client_hints_header.Contains("width")) {
-    if (context)
-      context->CountClientHintsResourceWidth();
-    should_send_resource_width_ = true;
-  }
+  for (size_t i = 0; i < kWebClientHintsTypeLast + 1; ++i)
+    enabled_types_[i] = enabled_types_[i] || new_enabled_types[i];
 
-  if (accept_client_hints_header.Contains("viewport-width")) {
-    if (context)
-      context->CountClientHintsViewportWidth();
-    should_send_viewport_width_ = true;
+  if (context) {
+    for (size_t i = 0; i < kWebClientHintsTypeLast + 1; ++i) {
+      if (enabled_types_[i])
+        context->CountClientHints(static_cast<WebClientHintsType>(i));
+    }
   }
 }
 
diff --git a/third_party/WebKit/Source/platform/loader/fetch/ClientHintsPreferences.h b/third_party/WebKit/Source/platform/loader/fetch/ClientHintsPreferences.h
index 3de29e0f..12e1f4b 100644
--- a/third_party/WebKit/Source/platform/loader/fetch/ClientHintsPreferences.h
+++ b/third_party/WebKit/Source/platform/loader/fetch/ClientHintsPreferences.h
@@ -8,6 +8,7 @@
 #include "platform/PlatformExport.h"
 #include "platform/wtf/Allocator.h"
 #include "platform/wtf/text/WTFString.h"
+#include "public/platform/WebClientHintsType.h"
 
 namespace blink {
 
@@ -17,10 +18,7 @@
  public:
   class Context {
    public:
-    virtual void CountClientHintsDeviceRAM() = 0;
-    virtual void CountClientHintsDPR() = 0;
-    virtual void CountClientHintsResourceWidth() = 0;
-    virtual void CountClientHintsViewportWidth() = 0;
+    virtual void CountClientHints(WebClientHintsType) = 0;
 
    protected:
     virtual ~Context() {}
@@ -31,27 +29,15 @@
   void UpdateFrom(const ClientHintsPreferences&);
   void UpdateFromAcceptClientHintsHeader(const String& header_value, Context*);
 
-  bool ShouldSendDeviceRAM() const { return should_send_device_ram_; }
-  void SetShouldSendDeviceRAM(bool should) { should_send_device_ram_ = should; }
-
-  bool ShouldSendDPR() const { return should_send_dpr_; }
-  void SetShouldSendDPR(bool should) { should_send_dpr_ = should; }
-
-  bool ShouldSendResourceWidth() const { return should_send_resource_width_; }
-  void SetShouldSendResourceWidth(bool should) {
-    should_send_resource_width_ = should;
+  bool ShouldSend(WebClientHintsType type) const {
+    return enabled_types_[type];
   }
-
-  bool ShouldSendViewportWidth() const { return should_send_viewport_width_; }
-  void SetShouldSendViewportWidth(bool should) {
-    should_send_viewport_width_ = should;
+  void SetShouldSendForTesting(WebClientHintsType type) {
+    enabled_types_[type] = true;
   }
 
  private:
-  bool should_send_device_ram_;
-  bool should_send_dpr_;
-  bool should_send_resource_width_;
-  bool should_send_viewport_width_;
+  bool enabled_types_[kWebClientHintsTypeLast + 1] = {};
 };
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/platform/loader/fetch/ClientHintsPreferencesTest.cpp b/third_party/WebKit/Source/platform/loader/fetch/ClientHintsPreferencesTest.cpp
index 11e3c63..c6e8d3a 100644
--- a/third_party/WebKit/Source/platform/loader/fetch/ClientHintsPreferencesTest.cpp
+++ b/third_party/WebKit/Source/platform/loader/fetch/ClientHintsPreferencesTest.cpp
@@ -27,14 +27,34 @@
 
   for (const auto& test_case : cases) {
     ClientHintsPreferences preferences;
-    const char* value = test_case.header_value;
-
-    preferences.UpdateFromAcceptClientHintsHeader(value, nullptr);
+    preferences.UpdateFromAcceptClientHintsHeader(test_case.header_value,
+                                                  nullptr);
     EXPECT_EQ(test_case.expectation_resource_width,
-              preferences.ShouldSendResourceWidth());
-    EXPECT_EQ(test_case.expectation_dpr, preferences.ShouldSendDPR());
+              preferences.ShouldSend(kWebClientHintsTypeResourceWidth));
+    EXPECT_EQ(test_case.expectation_dpr,
+              preferences.ShouldSend(kWebClientHintsTypeDpr));
     EXPECT_EQ(test_case.expectation_viewport_width,
-              preferences.ShouldSendViewportWidth());
+              preferences.ShouldSend(kWebClientHintsTypeViewportWidth));
+
+    // Calling UpdateFromAcceptClientHintsHeader with empty header should have
+    // no impact on client hint preferences.
+    preferences.UpdateFromAcceptClientHintsHeader("", nullptr);
+    EXPECT_EQ(test_case.expectation_resource_width,
+              preferences.ShouldSend(kWebClientHintsTypeResourceWidth));
+    EXPECT_EQ(test_case.expectation_dpr,
+              preferences.ShouldSend(kWebClientHintsTypeDpr));
+    EXPECT_EQ(test_case.expectation_viewport_width,
+              preferences.ShouldSend(kWebClientHintsTypeViewportWidth));
+
+    // Calling UpdateFromAcceptClientHintsHeader with an invalid header should
+    // have no impact on client hint preferences.
+    preferences.UpdateFromAcceptClientHintsHeader("foobar", nullptr);
+    EXPECT_EQ(test_case.expectation_resource_width,
+              preferences.ShouldSend(kWebClientHintsTypeResourceWidth));
+    EXPECT_EQ(test_case.expectation_dpr,
+              preferences.ShouldSend(kWebClientHintsTypeDpr));
+    EXPECT_EQ(test_case.expectation_viewport_width,
+              preferences.ShouldSend(kWebClientHintsTypeViewportWidth));
   }
 }
 
diff --git a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.cc b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.cc
index ca57b27a..489ea65 100644
--- a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.cc
+++ b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.cc
@@ -1215,6 +1215,11 @@
     new_policy.timer_queue_policy().is_suspended = true;
   }
 
+  if (GetMainThreadOnly().renderer_backgrounded &&
+      RuntimeEnabledFeatures::TimerThrottlingForBackgroundTabsEnabled()) {
+    new_policy.timer_queue_policy().is_throttled = true;
+  }
+
   if (GetMainThreadOnly().use_virtual_time) {
     new_policy.compositor_queue_policy().use_virtual_time = true;
     new_policy.default_queue_policy().use_virtual_time = true;
diff --git a/third_party/WebKit/Source/platform/testing/TestingPlatformSupport.cpp b/third_party/WebKit/Source/platform/testing/TestingPlatformSupport.cpp
index bb4f751..11ba85c 100644
--- a/third_party/WebKit/Source/platform/testing/TestingPlatformSupport.cpp
+++ b/third_party/WebKit/Source/platform/testing/TestingPlatformSupport.cpp
@@ -41,6 +41,7 @@
 #include "cc/blink/web_compositor_support_impl.h"
 #include "cc/test/ordered_simple_task_runner.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
+#include "platform/FontFamilyNames.h"
 #include "platform/HTTPNames.h"
 #include "platform/Language.h"
 #include "platform/heap/Heap.h"
@@ -358,6 +359,7 @@
   FetchInitiatorTypeNames::init();
 
   InitializePlatformLanguage();
+  FontFamilyNames::init();
 }
 
 ScopedUnittestsEnvironmentSetup::~ScopedUnittestsEnvironmentSetup() {}
diff --git a/third_party/WebKit/public/BUILD.gn b/third_party/WebKit/public/BUILD.gn
index e46c9516..4a105f3 100644
--- a/third_party/WebKit/public/BUILD.gn
+++ b/third_party/WebKit/public/BUILD.gn
@@ -139,6 +139,7 @@
     "platform/WebCallbacks.h",
     "platform/WebCanvas.h",
     "platform/WebCanvasCaptureHandler.h",
+    "platform/WebClientHintsType.h",
     "platform/WebClipboard.h",
     "platform/WebCoalescedInputEvent.h",
     "platform/WebColor.h",
diff --git a/third_party/WebKit/public/platform/WebClientHintsType.h b/third_party/WebKit/public/platform/WebClientHintsType.h
new file mode 100644
index 0000000..8d2ccab2
--- /dev/null
+++ b/third_party/WebKit/public/platform/WebClientHintsType.h
@@ -0,0 +1,25 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WebClientHintsType_h
+#define WebClientHintsType_h
+
+namespace blink {
+
+enum WebClientHintsType {
+  // The order of the enums or the values must not be changed. New values should
+  // only be added after the last value, and kWebClientHintsTypeLast should be
+  // updated accordingly.
+  kWebClientHintsTypeDeviceRam,
+  kWebClientHintsTypeDpr,
+  kWebClientHintsTypeResourceWidth,
+  kWebClientHintsTypeViewportWidth,
+
+  // Last client hint type.
+  kWebClientHintsTypeLast = kWebClientHintsTypeViewportWidth
+};
+
+}  // namespace blink
+
+#endif  // WebClientHintsType_h
diff --git a/tools/clang/scripts/update.py b/tools/clang/scripts/update.py
index fb597db..1dcd239 100755
--- a/tools/clang/scripts/update.py
+++ b/tools/clang/scripts/update.py
@@ -27,14 +27,14 @@
 # Do NOT CHANGE this if you don't know what you're doing -- see
 # https://chromium.googlesource.com/chromium/src/+/master/docs/updating_clang.md
 # Reverting problematic clang rolls is safe, though.
-CLANG_REVISION = '305735'
+CLANG_REVISION = '307486'
 
 use_head_revision = 'LLVM_FORCE_HEAD_REVISION' in os.environ
 if use_head_revision:
   CLANG_REVISION = 'HEAD'
 
 # This is incremented when pushing a new build of Clang at the same revision.
-CLANG_SUB_REVISION=3
+CLANG_SUB_REVISION=1
 
 PACKAGE_VERSION = "%s-%s" % (CLANG_REVISION, CLANG_SUB_REVISION)
 
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index f36a0b4..23c96520 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -35605,6 +35605,20 @@
   <int value="3" label="Iframe with different signon realm"/>
 </enum>
 
+<enum name="SubprocessType">
+  <int value="1" label="Unknown"/>
+  <int value="2" label="Browser"/>
+  <int value="3" label="Renderer"/>
+  <int value="4" label="Plugin (deprecated)"/>
+  <int value="5" label="Worker (deprecated)"/>
+  <int value="6" label="Utility"/>
+  <int value="7" label="Zygote"/>
+  <int value="8" label="Sandbox Helper"/>
+  <int value="9" label="GPU"/>
+  <int value="10" label="PPAPI Plugin"/>
+  <int value="11" label="PPAPI Broker"/>
+</enum>
+
 <enum name="SubresourceFilterActions">
   <int value="0" label="New Navigation"/>
   <int value="1" label="UI Shown"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index e8f9de4e..d50fc40 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -48253,8 +48253,11 @@
   <owner>dimich@chromium.org</owner>
   <summary>
     Enum-based buckets reflect the number of days the Chrome was used in each
-    specific way. This data is accumulated locally and reported when connection
-    is likely to succeed (usually after successful online navigation).
+    specific way: - Not used at all during whole day. - Started, but failed to
+    successfully navigate. - Only navigated to offline pages locally saved on
+    the device. - Only navigated online. - Navigated to both online and offline
+    pages. This data is accumulated locally and reported when connection is
+    likely to succeed (usually after successful online navigation).
   </summary>
 </histogram>
 
@@ -57712,6 +57715,9 @@
 </histogram>
 
 <histogram name="Precache.BatteryPercentage.Start" units="%">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>bengr@chromium.org</owner>
   <owner>rajendrant@chromium.org</owner>
   <summary>
@@ -57720,6 +57726,9 @@
 </histogram>
 
 <histogram name="Precache.BatteryPercentageDiff.End" units="%">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>bengr@chromium.org</owner>
   <owner>rajendrant@chromium.org</owner>
   <summary>
@@ -57732,6 +57741,9 @@
 </histogram>
 
 <histogram name="Precache.CacheSize.AllEntries" units="KB">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>jamartin@chromium.org</owner>
   <owner>bengr@chromium.org</owner>
   <summary>
@@ -57741,6 +57753,9 @@
 </histogram>
 
 <histogram name="Precache.CacheStatus.NonPrefetch" enum="HttpCachePattern">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>jamartin@chromium.org</owner>
   <owner>bengr@chromium.org</owner>
   <summary>
@@ -57751,6 +57766,9 @@
 
 <histogram name="Precache.CacheStatus.NonPrefetch.FromPrecache"
     enum="HttpCachePattern">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>jamartin@chromium.org</owner>
   <owner>bengr@chromium.org</owner>
   <summary>
@@ -57762,6 +57780,9 @@
 
 <histogram name="Precache.CacheStatus.NonPrefetch.NonTopHosts"
     enum="HttpCachePattern">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>twifkak@chromium.org</owner>
   <owner>jamartin@chromium.org</owner>
   <summary>
@@ -57773,6 +57794,9 @@
 
 <histogram name="Precache.CacheStatus.NonPrefetch.TopHosts"
     enum="HttpCachePattern">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>twifkak@chromium.org</owner>
   <owner>jamartin@chromium.org</owner>
   <summary>
@@ -57783,6 +57807,9 @@
 </histogram>
 
 <histogram name="Precache.CacheStatus.Prefetch" enum="HttpCachePattern">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>jamartin@chromium.org</owner>
   <owner>twifkak@chromium.org</owner>
   <summary>
@@ -57792,6 +57819,9 @@
 </histogram>
 
 <histogram name="Precache.DownloadedNonPrecache" units="bytes">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>bengr@chromium.org</owner>
   <summary>
     The number of bytes that were downloaded over the network for HTTP/HTTPS
@@ -57800,6 +57830,9 @@
 </histogram>
 
 <histogram name="Precache.DownloadedPrecacheMotivated" units="bytes">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>bengr@chromium.org</owner>
   <summary>
     The number of bytes that were downloaded because of precaching. Logged
@@ -57808,6 +57841,9 @@
 </histogram>
 
 <histogram name="Precache.Events" enum="PrecacheEvents">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>rajendrant@chromium.org</owner>
   <owner>bengr@chromium.org</owner>
   <summary>
@@ -57818,6 +57854,9 @@
 </histogram>
 
 <histogram name="Precache.Fetch.FailureReasons">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>twifkak@chromium.org</owner>
   <owner>bengr@chromium.org</owner>
   <summary>
@@ -57827,6 +57866,9 @@
 </histogram>
 
 <histogram name="Precache.Fetch.MinWeight" units="thousandths">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>twifkak@chromium.org</owner>
   <summary>
     The minimum resource weight that is fetched in a given precache run.
@@ -57837,6 +57879,9 @@
 </histogram>
 
 <histogram name="Precache.Fetch.PercentCompleted" units="%">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>twifkak@chromium.org</owner>
   <owner>bengr@chromium.org</owner>
   <summary>
@@ -57858,6 +57903,9 @@
 </histogram>
 
 <histogram name="Precache.Fetch.ResponseBytes.Daily" units="bytes">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>twifkak@chromium.org</owner>
   <summary>
     The total number of response bytes in 24 hours received over the network
@@ -57867,6 +57915,9 @@
 </histogram>
 
 <histogram name="Precache.Fetch.ResponseBytes.Network" units="bytes">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>twifkak@chromium.org</owner>
   <owner>bengr@chromium.org</owner>
   <summary>
@@ -57877,6 +57928,9 @@
 </histogram>
 
 <histogram name="Precache.Fetch.ResponseBytes.NetworkWasted" units="bytes">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>rajendrant@chromium.org</owner>
   <owner>bengr@chromium.org</owner>
   <summary>
@@ -57888,6 +57942,9 @@
 </histogram>
 
 <histogram name="Precache.Fetch.ResponseBytes.Total" units="bytes">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>twifkak@chromium.org</owner>
   <owner>bengr@chromium.org</owner>
   <summary>
@@ -57897,6 +57954,9 @@
 </histogram>
 
 <histogram name="Precache.Fetch.TimeToComplete" units="ms">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>twifkak@chromium.org</owner>
   <owner>bengr@chromium.org</owner>
   <summary>
@@ -57906,6 +57966,9 @@
 </histogram>
 
 <histogram name="Precache.Freshness.Prefetch" units="seconds">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>jamartin@chromium.org</owner>
   <owner>bengr@chromium.org</owner>
   <summary>
@@ -57966,6 +58029,9 @@
 </histogram>
 
 <histogram name="Precache.PeriodicTaskInterval" units="minutes">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>rajendrant@chromium.org</owner>
   <owner>bengr@chromium.org</owner>
   <summary>
@@ -57976,6 +58042,9 @@
 </histogram>
 
 <histogram name="Precache.Saved" units="bytes">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>bengr@chromium.org</owner>
   <summary>
     The number of bytes during user browsing that were served from the cache,
@@ -57985,6 +58054,9 @@
 </histogram>
 
 <histogram name="Precache.Saved.Freshness" units="seconds">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>jamartin@chromium.org</owner>
   <owner>bengr@chromium.org</owner>
   <summary>
@@ -57997,6 +58069,9 @@
 </histogram>
 
 <histogram name="Precache.TimeSinceLastPrecache" units="seconds">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <owner>jamartin@chromium.org</owner>
   <owner>bengr@chromium.org</owner>
   <summary>
@@ -65560,6 +65635,15 @@
   </summary>
 </histogram>
 
+<histogram name="SBClientDownload.DownloadFileHasDmgSignature" enum="Boolean">
+  <owner>jialiul@chromium.org</owner>
+  <summary>
+    A Mac-only metric that records whether a given download file is a
+    cryptographically signed DMG archive. This metric is logged before Chrome
+    sends SafeBrowsing download pings.
+  </summary>
+</histogram>
+
 <histogram
     name="SBClientDownload.DownloadFileWithoutDiskImageExtensionHasKolySignature"
     enum="Boolean">
@@ -81119,13 +81203,14 @@
 </histogram>
 
 <histogram name="UMA.SubprocessMetricsProvider.UntrackedProcesses"
-    units="subprocesses">
+    enum="SubprocessType">
   <owner>asvitkine@chromium.org</owner>
   <owner>bcwhite@chromium.org</owner>
   <summary>
     The number of subprocesses, by type, from which persistent metrics are NOT
     collected because there is no information about this (likely new) process
-    type.
+    type. Process numbers 1000 or greater are &quot;custom&quot; processes used
+    by embedders.
   </summary>
 </histogram>
 
@@ -95507,6 +95592,9 @@
 </histogram_suffixes>
 
 <histogram_suffixes name="PrecacheCellular" separator=".">
+  <obsolete>
+    Deprecated July 11 2017.
+  </obsolete>
   <suffix name="Cellular"
       label="covers fetches when connected to cellular networks"/>
   <affected-histogram name="Precache.DownloadedNonPrecache"/>
@@ -97284,7 +97372,11 @@
   <suffix name="History" label="History"/>
   <suffix name="OfflinePageMetadata" label="OfflinePageMetadata"/>
   <suffix name="Passwords" label="Passwords"/>
-  <suffix name="Precache" label="Precache"/>
+  <suffix name="Precache" label="Precache">
+    <obsolete>
+      Deprecated July 11 2017.
+    </obsolete>
+  </suffix>
   <suffix name="PrefetchStore" label="PrefetchStore"/>
   <suffix name="Predictor" label="Predictor"/>
   <suffix name="PreviewsOptOut" label="PreviewsOptOut"/>
diff --git a/tools/perf/fetch_benchmark_deps.py b/tools/perf/fetch_benchmark_deps.py
index 0a07d06..3a21c3d08 100755
--- a/tools/perf/fetch_benchmark_deps.py
+++ b/tools/perf/fetch_benchmark_deps.py
@@ -6,6 +6,7 @@
 """This module fetches and prints the dependencies given a benchmark."""
 
 import argparse
+import optparse
 import os
 import sys
 
@@ -23,6 +24,9 @@
 
 def _FetchDependenciesIfNeeded(story_set):
   """ Download files needed by a user story set. """
+  if not story_set.wpr_archive_info:
+    return
+
   # Download files in serving_dirs.
   serving_dirs = story_set.serving_dirs
   for directory in serving_dirs:
@@ -60,9 +64,14 @@
 
 
 def FetchDepsForBenchmark(benchmark, output):
-  # Download files according to specified benchmark.
-  story_set = benchmark().CreateStorySet(None)
+  # Create a dummy options object which hold default values that are expected
+  # by Benchmark.CreateStorySet(options) method.
+  parser = optparse.OptionParser()
+  benchmark.AddBenchmarkCommandLineArgs(parser)
+  options, _ = parser.parse_args([])
+  story_set = benchmark().CreateStorySet(options)
 
+  # Download files according to specified benchmark.
   _FetchDependenciesIfNeeded(story_set)
 
   # Print files downloaded.
@@ -96,9 +105,8 @@
       raw_input(
           'No benchmark name is specified. Fetching all benchmark deps. '
           'Press enter to continue...')
-    for b in benchmark_finders.GetAllBenchmarks():
-      print >> output, ('Fetch dependencies for benchmark %s:'
-                        % benchmark.Name())
+    for b in benchmark_finders.GetAllPerfBenchmarks():
+      print >> output, ('Fetch dependencies for benchmark %s' % b.Name())
       FetchDepsForBenchmark(b, output)
 
 if __name__ == '__main__':
diff --git a/tools/perf/page_sets/data/chrome_signin_credentials.json b/tools/perf/page_sets/data/chrome_signin_credentials.json
new file mode 100644
index 0000000..1f180cc
--- /dev/null
+++ b/tools/perf/page_sets/data/chrome_signin_credentials.json
@@ -0,0 +1,6 @@
+{
+  "chrome": {
+    "username": "chrometelemetry@gmail.com",
+    "password": "signinfortelemetry"
+  }
+}
diff --git a/tools/perf/page_sets/tough_video_cases.py b/tools/perf/page_sets/tough_video_cases.py
index a66af42..2a280680 100644
--- a/tools/perf/page_sets/tough_video_cases.py
+++ b/tools/perf/page_sets/tough_video_cases.py
@@ -22,9 +22,10 @@
     # Other filter tags:
     'is_50fps',
     'is_4k',
-    # Play action
+    # Play action:
     'seek',
     'normal_play',
+    'background',
 ]
 
 
@@ -63,6 +64,31 @@
     if self.page_set.measure_memory:
       action_runner.MeasureMemory()
 
+  def PlayInBackgroundTab(self, action_runner, background_time=10):
+    # Steps:
+    # 1. Play a video
+    # 2. Open new tab overtop to obscure the video
+    # 3. Close the tab to go back to the tab that is playing the video.
+    # This test case will work differently depending on whether the platform is
+    # desktop or Android and whether the video has sound or not. For example,
+    # the current Chrome video implementation (as of July 2017) pauses video on
+    # Android when the tab is backgrounded, but on desktop the video is not
+    # paused.
+    # TODO(crouleau): Use --disable-media-suspend flag to enable Android to
+    # play video in the background.
+    # The motivation for this test case is crbug.com/678663.
+    action_runner.PlayMedia(
+        playing_event_timeout_in_seconds=60)
+    action_runner.Wait(.5)
+    new_tab = action_runner.tab.browser.tabs.New()
+    new_tab.Activate()
+    action_runner.Wait(background_time)
+    new_tab.Close()
+    action_runner.Wait(.5)
+    # Generate memory dump for memoryMetric.
+    if self.page_set.measure_memory:
+      action_runner.MeasureMemory()
+
 
 class Page2(ToughVideoCasesPage):
 
@@ -387,6 +413,19 @@
     self.SeekBeforeAndAfterPlayhead(action_runner,
                                     action_timeout_in_seconds=120)
 
+class Page37(ToughVideoCasesPage):
+
+  def __init__(self, page_set):
+    super(Page37, self).__init__(
+      url='file://tough_video_cases/video.html?src=tulip2.vp9.webm&background',
+      page_set=page_set,
+      tags=['vp9', 'opus', 'audio_video', 'background'])
+
+    self.skip_basic_metrics = True
+
+  def RunPageInteractions(self, action_runner):
+    self.PlayInBackgroundTab(action_runner)
+
 
 class ToughVideoCasesPageSet(story.StorySet):
   """
@@ -426,17 +465,20 @@
     self.AddStory(Page33(self))
     self.AddStory(Page36(self))
 
+    # Background playback tests:
+    self.AddStory(Page37(self))
+
 
 class ToughVideoCasesDesktopStoryExpectations(
     story.expectations.StoryExpectations):
 
   def SetExpectations(self):
     self.PermanentlyDisableBenchmark(
-        [story.expectations.ALL_MOBILE],'Desktop Benchmark')
+        [story.expectations.ALL_MOBILE], 'Desktop Benchmark')
 
 class ToughVideoCasesAndroidStoryExpectations(
     story.expectations.StoryExpectations):
 
   def SetExpectations(self):
     self.PermanentlyDisableBenchmark(
-        [story.expectations.ALL_DESKTOP],'Android Benchmark')
+        [story.expectations.ALL_DESKTOP], 'Android Benchmark')
diff --git a/tools/perf/page_sets/update_webrtc_cases b/tools/perf/page_sets/update_webrtc_cases
index 4bed428..03a8218 100755
--- a/tools/perf/page_sets/update_webrtc_cases
+++ b/tools/perf/page_sets/update_webrtc_cases
@@ -22,6 +22,7 @@
         'dirs': [
             'src/canvas-capture',
             'src/multiple-peerconnections',
+            'src/pause-play',
         ],
     },
     'samples': {
@@ -60,7 +61,7 @@
 HTML_COPYRIGHT_NOTICE = ' * '.join(['<!--\n'] + COPYRIGHT_NOTICE) + '-->\n'
 
 STRIPPED_TAGS_RE = ('( *<meta.*?>\n?| *<link.*?>\n?|'
-                    ' *<script.*>.*?</script>\n?|</body>.*?</html>)')
+                    ' *<script.*>.*?</script>\n?| *</body>.*?</html>)')
 
 
 class TemporaryDirectory(object):
diff --git a/tools/perf/page_sets/webrtc_cases.py b/tools/perf/page_sets/webrtc_cases.py
index 456e572..9652750 100644
--- a/tools/perf/page_sets/webrtc_cases.py
+++ b/tools/perf/page_sets/webrtc_cases.py
@@ -117,11 +117,29 @@
       action_runner.Wait(20)
 
 
+class PausePlayPeerConnections(WebrtcPage):
+  """Why: Ensures frequent pause and plays of peer connection streams work."""
+
+  def __init__(self, page_set, tags):
+    super(PausePlayPeerConnections, self).__init__(
+        url='file://webrtc_cases/pause-play.html',
+        name='pause_play_peerconnections',
+        page_set=page_set, tags=tags)
+
+  def RunPageInteractions(self, action_runner):
+    action_runner.ExecuteJavaScript(
+        'startTest({test_runtime_s}, {num_peerconnections},'
+        '{iteration_delay_ms}, "video");'.format(
+            test_runtime_s=20, num_peerconnections=10, iteration_delay_ms=20))
+    action_runner.Wait(20)
+
+
 class WebrtcPageSet(story.StorySet):
   def __init__(self):
     super(WebrtcPageSet, self).__init__(
         cloud_storage_bucket=story.PUBLIC_BUCKET)
 
+    self.AddStory(PausePlayPeerConnections(self, tags=['pauseplay']))
     self.AddStory(MultiplePeerConnections(self, tags=['stress']))
     self.AddStory(DataChannel(self, tags=['datachannel']))
     self.AddStory(GetUserMedia(self, tags=['getusermedia']))
diff --git a/tools/perf/page_sets/webrtc_cases/adapter.js b/tools/perf/page_sets/webrtc_cases/adapter.js
index 3af94d04..a3c0b08 100644
--- a/tools/perf/page_sets/webrtc_cases/adapter.js
+++ b/tools/perf/page_sets/webrtc_cases/adapter.js
@@ -54,7 +54,7 @@
 
   var candidate = {
     foundation: parts[0],
-    component: parts[1],
+    component: parseInt(parts[1], 10),
     protocol: parts[2].toLowerCase(),
     priority: parseInt(parts[3], 10),
     ip: parts[4],
@@ -74,7 +74,8 @@
       case 'tcptype':
         candidate.tcpType = parts[i + 1];
         break;
-      default: // Unknown extensions are silently ignored.
+      default: // extension handling, in particular ufrag
+        candidate[parts[i]] = parts[i + 1];
         break;
     }
   }
@@ -105,9 +106,19 @@
     sdp.push('tcptype');
     sdp.push(candidate.tcpType);
   }
+  if (candidate.ufrag) {
+    sdp.push('ufrag');
+    sdp.push(candidate.ufrag);
+  }
   return 'candidate:' + sdp.join(' ');
 };
 
+// Parses an ice-options line, returns an array of option tags.
+// a=ice-options:foo bar
+SDPUtils.parseIceOptions = function(line) {
+  return line.substr(14).split(' ');
+}
+
 // Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input:
 // a=rtpmap:111 opus/48000/2
 SDPUtils.parseRtpMap = function(line) {
@@ -138,10 +149,12 @@
 
 // Parses an a=extmap line (headerextension from RFC 5285). Sample input:
 // a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
+// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset
 SDPUtils.parseExtmap = function(line) {
   var parts = line.substr(9).split(' ');
   return {
     id: parseInt(parts[0], 10),
+    direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv',
     uri: parts[1]
   };
 };
@@ -150,7 +163,10 @@
 // RTCRtpHeaderExtension.
 SDPUtils.writeExtmap = function(headerExtension) {
   return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) +
-       ' ' + headerExtension.uri + '\r\n';
+      (headerExtension.direction && headerExtension.direction !== 'sendrecv'
+          ? '/' + headerExtension.direction
+          : '') +
+      ' ' + headerExtension.uri + '\r\n';
 };
 
 // Parses an ftmp line, returns dictionary. Sample input:
@@ -228,25 +244,35 @@
   return parts;
 };
 
+// Extracts the MID (RFC 5888) from a media section.
+// returns the MID or undefined if no mid line was found.
+SDPUtils.getMid = function(mediaSection) {
+  var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0];
+  if (mid) {
+    return mid.substr(6);
+  }
+}
+
+SDPUtils.parseFingerprint = function(line) {
+  var parts = line.substr(14).split(' ');
+  return {
+    algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge.
+    value: parts[1]
+  };
+};
+
 // Extracts DTLS parameters from SDP media section or sessionpart.
 // FIXME: for consistency with other functions this should only
 //   get the fingerprint line as input. See also getIceParameters.
 SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) {
-  var lines = SDPUtils.splitLines(mediaSection);
-  // Search in session part, too.
-  lines = lines.concat(SDPUtils.splitLines(sessionpart));
-  var fpLine = lines.filter(function(line) {
-    return line.indexOf('a=fingerprint:') === 0;
-  })[0].substr(14);
+  var lines = SDPUtils.matchPrefix(mediaSection + sessionpart,
+      'a=fingerprint:');
   // Note: a=setup line is ignored since we use the 'auto' role.
-  var dtlsParameters = {
+  // Note2: 'algorithm' is not case sensitive except in Edge.
+  return {
     role: 'auto',
-    fingerprints: [{
-      algorithm: fpLine.split(' ')[0],
-      value: fpLine.split(' ')[1]
-    }]
+    fingerprints: lines.map(SDPUtils.parseFingerprint)
   };
-  return dtlsParameters;
 };
 
 // Serializes DTLS parameters to SDP.
@@ -429,7 +455,11 @@
     if (bandwidth[0].indexOf('b=TIAS:') === 0) {
       bandwidth = parseInt(bandwidth[0].substr(7), 10);
     } else if (bandwidth[0].indexOf('b=AS:') === 0) {
-      bandwidth = parseInt(bandwidth[0].substr(5), 10);
+      // use formula from JSEP to convert b=AS to TIAS value.
+      bandwidth = parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95
+          - (50 * 40 * 8);
+    } else {
+      bandwidth = undefined;
     }
     encodingParameters.forEach(function(params) {
       params.maxBitrate = bandwidth;
@@ -471,7 +501,7 @@
   return rtcpParameters;
 };
 
-// parses either a=msid: or a=ssrc:... msid lines an returns
+// parses either a=msid: or a=ssrc:... msid lines and returns
 // the id of the MediaStream and MediaStreamTrack.
 SDPUtils.parseMsid = function(mediaSection) {
   var parts;
@@ -493,10 +523,29 @@
   }
 };
 
-SDPUtils.writeSessionBoilerplate = function() {
+// Generate a session ID for SDP.
+// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1
+// recommends using a cryptographically random +ve 64-bit value
+// but right now this should be acceptable and within the right range
+SDPUtils.generateSessionId = function() {
+  return Math.random().toString().substr(2, 21);
+};
+
+// Write boilder plate for start of SDP
+// sessId argument is optional - if not supplied it will
+// be generated randomly
+// sessVersion is optional and defaults to 2
+SDPUtils.writeSessionBoilerplate = function(sessId, sessVer) {
+  var sessionId;
+  var version = sessVer !== undefined ? sessVer : 2;
+  if (sessId) {
+    sessionId = sessId;
+  } else {
+    sessionId = SDPUtils.generateSessionId();
+  }
   // FIXME: sess-id should be an NTP timestamp.
   return 'v=0\r\n' +
-      'o=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\n' +
+      'o=thisisadapterortc ' + sessionId + ' ' + version + ' IN IP4 127.0.0.1\r\n' +
       's=-\r\n' +
       't=0 0\r\n';
 };
@@ -515,7 +564,9 @@
 
   sdp += 'a=mid:' + transceiver.mid + '\r\n';
 
-  if (transceiver.rtpSender && transceiver.rtpReceiver) {
+  if (transceiver.direction) {
+    sdp += 'a=' + transceiver.direction + '\r\n';
+  } else if (transceiver.rtpSender && transceiver.rtpReceiver) {
     sdp += 'a=sendrecv\r\n';
   } else if (transceiver.rtpSender) {
     sdp += 'a=sendonly\r\n';
@@ -588,6 +639,23 @@
 module.exports = SDPUtils;
 
 },{}],2:[function(require,module,exports){
+(function (global){
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+ /* eslint-env node */
+
+'use strict';
+
+var adapterFactory = require('./adapter_factory.js');
+module.exports = adapterFactory({window: global.window});
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./adapter_factory.js":3}],3:[function(require,module,exports){
 /*
  *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
  *
@@ -600,15 +668,34 @@
 'use strict';
 
 // Shimming starts here.
-(function() {
+module.exports = function(dependencies, opts) {
+  var window = dependencies && dependencies.window;
+
+  var options = {
+    shimChrome: true,
+    shimFirefox: true,
+    shimEdge: true,
+    shimSafari: true,
+  };
+
+  for (var key in opts) {
+    if (hasOwnProperty.call(opts, key)) {
+      options[key] = opts[key];
+    }
+  }
+
   // Utils.
   var utils = require('./utils');
   var logging = utils.log;
-  var browserDetails = utils.browserDetails;
+  var browserDetails = utils.detectBrowser(window);
+
   // Export to the adapter global object visible in the browser.
-  module.exports.browserDetails = browserDetails;
-  module.exports.extractVersion = utils.extractVersion;
-  module.exports.disableLog = utils.disableLog;
+  var adapter = {
+    browserDetails: browserDetails,
+    extractVersion: utils.extractVersion,
+    disableLog: utils.disableLog,
+    disableWarnings: utils.disableWarnings
+  };
 
   // Uncomment the line below if you want logging to occur, including logging
   // for the switch statement below. Can also be turned on in the browser via
@@ -625,67 +712,79 @@
   // Shim browser if found.
   switch (browserDetails.browser) {
     case 'chrome':
-      if (!chromeShim || !chromeShim.shimPeerConnection) {
+      if (!chromeShim || !chromeShim.shimPeerConnection ||
+          !options.shimChrome) {
         logging('Chrome shim is not included in this adapter release.');
-        return;
+        return adapter;
       }
       logging('adapter.js shimming chrome.');
       // Export to the adapter global object visible in the browser.
-      module.exports.browserShim = chromeShim;
+      adapter.browserShim = chromeShim;
 
-      chromeShim.shimGetUserMedia();
-      chromeShim.shimMediaStream();
-      utils.shimCreateObjectURL();
-      chromeShim.shimSourceObject();
-      chromeShim.shimPeerConnection();
-      chromeShim.shimOnTrack();
-      chromeShim.shimGetSendersWithDtmf();
+      chromeShim.shimGetUserMedia(window);
+      chromeShim.shimMediaStream(window);
+      utils.shimCreateObjectURL(window);
+      chromeShim.shimSourceObject(window);
+      chromeShim.shimPeerConnection(window);
+      chromeShim.shimOnTrack(window);
+      chromeShim.shimAddTrackRemoveTrack(window);
+      chromeShim.shimGetSendersWithDtmf(window);
       break;
     case 'firefox':
-      if (!firefoxShim || !firefoxShim.shimPeerConnection) {
+      if (!firefoxShim || !firefoxShim.shimPeerConnection ||
+          !options.shimFirefox) {
         logging('Firefox shim is not included in this adapter release.');
-        return;
+        return adapter;
       }
       logging('adapter.js shimming firefox.');
       // Export to the adapter global object visible in the browser.
-      module.exports.browserShim = firefoxShim;
+      adapter.browserShim = firefoxShim;
 
-      firefoxShim.shimGetUserMedia();
-      utils.shimCreateObjectURL();
-      firefoxShim.shimSourceObject();
-      firefoxShim.shimPeerConnection();
-      firefoxShim.shimOnTrack();
+      firefoxShim.shimGetUserMedia(window);
+      utils.shimCreateObjectURL(window);
+      firefoxShim.shimSourceObject(window);
+      firefoxShim.shimPeerConnection(window);
+      firefoxShim.shimOnTrack(window);
       break;
     case 'edge':
-      if (!edgeShim || !edgeShim.shimPeerConnection) {
+      if (!edgeShim || !edgeShim.shimPeerConnection || !options.shimEdge) {
         logging('MS edge shim is not included in this adapter release.');
-        return;
+        return adapter;
       }
       logging('adapter.js shimming edge.');
       // Export to the adapter global object visible in the browser.
-      module.exports.browserShim = edgeShim;
+      adapter.browserShim = edgeShim;
 
-      edgeShim.shimGetUserMedia();
-      utils.shimCreateObjectURL();
-      edgeShim.shimPeerConnection();
+      edgeShim.shimGetUserMedia(window);
+      utils.shimCreateObjectURL(window);
+      edgeShim.shimPeerConnection(window);
+      edgeShim.shimReplaceTrack(window);
       break;
     case 'safari':
-      if (!safariShim) {
+      if (!safariShim || !options.shimSafari) {
         logging('Safari shim is not included in this adapter release.');
-        return;
+        return adapter;
       }
       logging('adapter.js shimming safari.');
       // Export to the adapter global object visible in the browser.
-      module.exports.browserShim = safariShim;
-
-      safariShim.shimGetUserMedia();
+      adapter.browserShim = safariShim;
+      // shim window.URL.createObjectURL Safari (technical preview)
+      utils.shimCreateObjectURL(window);
+      safariShim.shimRTCIceServerUrls(window);
+      safariShim.shimCallbacksAPI(window);
+      safariShim.shimLocalStreamsAPI(window);
+      safariShim.shimRemoteStreamsAPI(window);
+      safariShim.shimGetUserMedia(window);
       break;
     default:
       logging('Unsupported browser!');
+      break;
   }
-})();
 
-},{"./chrome/chrome_shim":3,"./edge/edge_shim":5,"./firefox/firefox_shim":7,"./safari/safari_shim":9,"./utils":10}],3:[function(require,module,exports){
+  return adapter;
+};
+
+},{"./chrome/chrome_shim":4,"./edge/edge_shim":6,"./firefox/firefox_shim":9,"./safari/safari_shim":11,"./utils":12}],4:[function(require,module,exports){
 
 /*
  *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
@@ -696,15 +795,15 @@
  */
  /* eslint-env node */
 'use strict';
-var logging = require('../utils.js').log;
-var browserDetails = require('../utils.js').browserDetails;
+var utils = require('../utils.js');
+var logging = utils.log;
 
 var chromeShim = {
-  shimMediaStream: function() {
+  shimMediaStream: function(window) {
     window.MediaStream = window.MediaStream || window.webkitMediaStream;
   },
 
-  shimOnTrack: function() {
+  shimOnTrack: function(window) {
     if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in
         window.RTCPeerConnection.prototype)) {
       Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
@@ -712,70 +811,124 @@
           return this._ontrack;
         },
         set: function(f) {
-          var self = this;
           if (this._ontrack) {
             this.removeEventListener('track', this._ontrack);
-            this.removeEventListener('addstream', this._ontrackpoly);
           }
           this.addEventListener('track', this._ontrack = f);
-          this.addEventListener('addstream', this._ontrackpoly = function(e) {
+        }
+      });
+      var origSetRemoteDescription =
+          window.RTCPeerConnection.prototype.setRemoteDescription;
+      window.RTCPeerConnection.prototype.setRemoteDescription = function() {
+        var pc = this;
+        if (!pc._ontrackpoly) {
+          pc._ontrackpoly = function(e) {
             // onaddstream does not fire when a track is added to an existing
             // stream. But stream.onaddtrack is implemented so we use that.
             e.stream.addEventListener('addtrack', function(te) {
+              var receiver;
+              if (window.RTCPeerConnection.prototype.getReceivers) {
+                receiver = pc.getReceivers().find(function(r) {
+                  return r.track.id === te.track.id;
+                });
+              } else {
+                receiver = {track: te.track};
+              }
+
               var event = new Event('track');
               event.track = te.track;
-              event.receiver = {track: te.track};
+              event.receiver = receiver;
               event.streams = [e.stream];
-              self.dispatchEvent(event);
+              pc.dispatchEvent(event);
             });
             e.stream.getTracks().forEach(function(track) {
+              var receiver;
+              if (window.RTCPeerConnection.prototype.getReceivers) {
+                receiver = pc.getReceivers().find(function(r) {
+                  return r.track.id === track.id;
+                });
+              } else {
+                receiver = {track: track};
+              }
               var event = new Event('track');
               event.track = track;
-              event.receiver = {track: track};
+              event.receiver = receiver;
               event.streams = [e.stream];
-              this.dispatchEvent(event);
-            }.bind(this));
-          }.bind(this));
+              pc.dispatchEvent(event);
+            });
+          };
+          pc.addEventListener('addstream', pc._ontrackpoly);
         }
-      });
+        return origSetRemoteDescription.apply(pc, arguments);
+      };
     }
   },
 
-  shimGetSendersWithDtmf: function() {
+  shimGetSendersWithDtmf: function(window) {
+    // Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack.
     if (typeof window === 'object' && window.RTCPeerConnection &&
-        !('getSenders' in RTCPeerConnection.prototype) &&
-        'createDTMFSender' in RTCPeerConnection.prototype) {
-      RTCPeerConnection.prototype.getSenders = function() {
-        return this._senders;
+        !('getSenders' in window.RTCPeerConnection.prototype) &&
+        'createDTMFSender' in window.RTCPeerConnection.prototype) {
+      var shimSenderWithDtmf = function(pc, track) {
+        return {
+          track: track,
+          get dtmf() {
+            if (this._dtmf === undefined) {
+              if (track.kind === 'audio') {
+                this._dtmf = pc.createDTMFSender(track);
+              } else {
+                this._dtmf = null;
+              }
+            }
+            return this._dtmf;
+          },
+          _pc: pc
+        };
       };
-      var origAddStream = RTCPeerConnection.prototype.addStream;
-      var origRemoveStream = RTCPeerConnection.prototype.removeStream;
 
-      RTCPeerConnection.prototype.addStream = function(stream) {
+      // augment addTrack when getSenders is not available.
+      if (!window.RTCPeerConnection.prototype.getSenders) {
+        window.RTCPeerConnection.prototype.getSenders = function() {
+          this._senders = this._senders || [];
+          return this._senders.slice(); // return a copy of the internal state.
+        };
+        var origAddTrack = window.RTCPeerConnection.prototype.addTrack;
+        window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
+          var pc = this;
+          var sender = origAddTrack.apply(pc, arguments);
+          if (!sender) {
+            sender = shimSenderWithDtmf(pc, track);
+            pc._senders.push(sender);
+          }
+          return sender;
+        };
+
+        var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack;
+        window.RTCPeerConnection.prototype.removeTrack = function(sender) {
+          var pc = this;
+          origRemoveTrack.apply(pc, arguments);
+          var idx = pc._senders.indexOf(sender);
+          if (idx !== -1) {
+            pc._senders.splice(idx, 1);
+          }
+        };
+      }
+      var origAddStream = window.RTCPeerConnection.prototype.addStream;
+      window.RTCPeerConnection.prototype.addStream = function(stream) {
         var pc = this;
         pc._senders = pc._senders || [];
         origAddStream.apply(pc, [stream]);
         stream.getTracks().forEach(function(track) {
-          pc._senders.push({
-            track: track,
-            get dtmf() {
-              if (this._dtmf === undefined) {
-                if (track.kind === 'audio') {
-                  this._dtmf = pc.createDTMFSender(track);
-                } else {
-                  this._dtmf = null;
-                }
-              }
-              return this._dtmf;
-            }
-          });
+          pc._senders.push(shimSenderWithDtmf(pc, track));
         });
       };
 
-      RTCPeerConnection.prototype.removeStream = function(stream) {
+      var origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
+      window.RTCPeerConnection.prototype.removeStream = function(stream) {
         var pc = this;
         pc._senders = pc._senders || [];
-        origRemoveStream.apply(pc, [stream]);
+        origRemoveStream.apply(pc, [(pc._streams[stream.id] || stream)]);
+
         stream.getTracks().forEach(function(track) {
           var sender = pc._senders.find(function(s) {
             return s.track === track;
@@ -785,10 +938,39 @@
           }
         });
       };
+    } else if (typeof window === 'object' && window.RTCPeerConnection &&
+               'getSenders' in window.RTCPeerConnection.prototype &&
+               'createDTMFSender' in window.RTCPeerConnection.prototype &&
+               window.RTCRtpSender &&
+               !('dtmf' in window.RTCRtpSender.prototype)) {
+      var origGetSenders = window.RTCPeerConnection.prototype.getSenders;
+      window.RTCPeerConnection.prototype.getSenders = function() {
+        var pc = this;
+        var senders = origGetSenders.apply(pc, []);
+        senders.forEach(function(sender) {
+          sender._pc = pc;
+        });
+        return senders;
+      };
+
+      Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', {
+        get: function() {
+          if (this._dtmf === undefined) {
+            if (this.track.kind === 'audio') {
+              this._dtmf = this._pc.createDTMFSender(this.track);
+            } else {
+              this._dtmf = null;
+            }
+          }
+          return this._dtmf;
+        }
+      });
     }
   },
 
-  shimSourceObject: function() {
+  shimSourceObject: function(window) {
+    var URL = window && window.URL;
+
     if (typeof window === 'object') {
       if (window.HTMLMediaElement &&
         !('srcObject' in window.HTMLMediaElement.prototype)) {
@@ -830,7 +1012,154 @@
     }
   },
 
-  shimPeerConnection: function() {
+  shimAddTrackRemoveTrack: function(window) {
+    // shim addTrack and removeTrack.
+    if (window.RTCPeerConnection.prototype.addTrack) {
+      return;
+    }
+
+    // also shim pc.getLocalStreams when addTrack is shimmed
+    // to return the original streams.
+    var origGetLocalStreams = window.RTCPeerConnection.prototype
+        .getLocalStreams;
+    window.RTCPeerConnection.prototype.getLocalStreams = function() {
+      var self = this;
+      var nativeStreams = origGetLocalStreams.apply(this);
+      self._reverseStreams = self._reverseStreams || {};
+      return nativeStreams.map(function(stream) {
+        return self._reverseStreams[stream.id];
+      });
+    };
+
+    var origAddStream = window.RTCPeerConnection.prototype.addStream;
+    window.RTCPeerConnection.prototype.addStream = function(stream) {
+      var pc = this;
+      pc._streams = pc._streams || {};
+      pc._reverseStreams = pc._reverseStreams || {};
+
+      stream.getTracks().forEach(function(track) {
+        var alreadyExists = pc.getSenders().find(function(s) {
+          return s.track === track;
+        });
+        if (alreadyExists) {
+          throw new DOMException('Track already exists.',
+              'InvalidAccessError');
+        }
+      });
+      // Add identity mapping for consistency with addTrack.
+      // Unless this is being used with a stream from addTrack.
+      if (!pc._reverseStreams[stream.id]) {
+        var newStream = new window.MediaStream(stream.getTracks());
+        pc._streams[stream.id] = newStream;
+        pc._reverseStreams[newStream.id] = stream;
+        stream = newStream;
+      }
+      origAddStream.apply(pc, [stream]);
+    };
+
+    var origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
+    window.RTCPeerConnection.prototype.removeStream = function(stream) {
+      var pc = this;
+      pc._streams = pc._streams || {};
+      pc._reverseStreams = pc._reverseStreams || {};
+
+      origRemoveStream.apply(pc, [(pc._streams[stream.id] || stream)]);
+      delete pc._reverseStreams[(pc._streams[stream.id] ?
+          pc._streams[stream.id].id : stream.id)];
+      delete pc._streams[stream.id];
+    };
+
+    window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
+      var pc = this;
+      if (pc.signalingState === 'closed') {
+        throw new DOMException(
+          'The RTCPeerConnection\'s signalingState is \'closed\'.',
+          'InvalidStateError');
+      }
+      var streams = [].slice.call(arguments, 1);
+      if (streams.length !== 1 ||
+          !streams[0].getTracks().find(function(t) {
+            return t === track;
+          })) {
+        // this is not fully correct but all we can manage without
+        // [[associated MediaStreams]] internal slot.
+        throw new DOMException(
+          'The adapter.js addTrack polyfill only supports a single ' +
+          ' stream which is associated with the specified track.',
+          'NotSupportedError');
+      }
+
+      var alreadyExists = pc.getSenders().find(function(s) {
+        return s.track === track;
+      });
+      if (alreadyExists) {
+        throw new DOMException('Track already exists.',
+            'InvalidAccessError');
+      }
+
+      pc._streams = pc._streams || {};
+      pc._reverseStreams = pc._reverseStreams || {};
+      var oldStream = pc._streams[stream.id];
+      if (oldStream) {
+        // this is using odd Chrome behaviour, use with caution:
+        // https://bugs.chromium.org/p/webrtc/issues/detail?id=7815
+        // Note: we rely on the high-level addTrack/dtmf shim to
+        // create the sender with a dtmf sender.
+        oldStream.addTrack(track);
+        pc.dispatchEvent(new Event('negotiationneeded'));
+      } else {
+        var newStream = new window.MediaStream([track]);
+        pc._streams[stream.id] = newStream;
+        pc._reverseStreams[newStream.id] = stream;
+        pc.addStream(newStream);
+      }
+      return pc.getSenders().find(function(s) {
+        return s.track === track;
+      });
+    };
+
+    window.RTCPeerConnection.prototype.removeTrack = function(sender) {
+      var pc = this;
+      if (pc.signalingState === 'closed') {
+        throw new DOMException(
+          'The RTCPeerConnection\'s signalingState is \'closed\'.',
+          'InvalidStateError');
+      }
+      var isLocal = sender._pc === pc;
+      if (!isLocal) {
+        throw new DOMException('Sender was not created by this connection.',
+            'InvalidAccessError');
+      }
+
+      // Search for the native stream the senders track belongs to.
+      pc._streams = pc._streams || {};
+      var stream;
+      Object.keys(pc._streams).forEach(function(streamid) {
+        var hasTrack = pc._streams[streamid].getTracks().find(function(track) {
+          return sender.track === track;
+        });
+        if (hasTrack) {
+          stream = pc._streams[streamid];
+        }
+      });
+
+      if (stream) {
+        if (stream.getTracks().length === 1) {
+          // if this is the last track of the stream, remove the stream. This
+          // takes care of any shimmed _senders.
+          pc.removeStream(stream);
+        } else {
+          // relying on the same odd chrome behaviour as above.
+          stream.removeTrack(sender.track);
+        }
+        pc.dispatchEvent(new Event('negotiationneeded'));
+      }
+    };
+  },
+
+  shimPeerConnection: function(window) {
+    var browserDetails = utils.detectBrowser(window);
+
     // The RTCPeerConnection object.
     if (!window.RTCPeerConnection) {
       window.RTCPeerConnection = function(pcConfig, pcConstraints) {
@@ -842,21 +1171,51 @@
           pcConfig.iceTransports = pcConfig.iceTransportPolicy;
         }
 
-        return new webkitRTCPeerConnection(pcConfig, pcConstraints);
+        return new window.webkitRTCPeerConnection(pcConfig, pcConstraints);
       };
-      window.RTCPeerConnection.prototype = webkitRTCPeerConnection.prototype;
+      window.RTCPeerConnection.prototype =
+          window.webkitRTCPeerConnection.prototype;
       // wrap static methods. Currently just generateCertificate.
-      if (webkitRTCPeerConnection.generateCertificate) {
+      if (window.webkitRTCPeerConnection.generateCertificate) {
         Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
           get: function() {
-            return webkitRTCPeerConnection.generateCertificate;
+            return window.webkitRTCPeerConnection.generateCertificate;
           }
         });
       }
+    } else {
+      // migrate from non-spec RTCIceServer.url to RTCIceServer.urls
+      var OrigPeerConnection = window.RTCPeerConnection;
+      window.RTCPeerConnection = function(pcConfig, pcConstraints) {
+        if (pcConfig && pcConfig.iceServers) {
+          var newIceServers = [];
+          for (var i = 0; i < pcConfig.iceServers.length; i++) {
+            var server = pcConfig.iceServers[i];
+            if (!server.hasOwnProperty('urls') &&
+                server.hasOwnProperty('url')) {
+              console.warn('RTCIceServer.url is deprecated! Use urls instead.');
+              server = JSON.parse(JSON.stringify(server));
+              server.urls = server.url;
+              newIceServers.push(server);
+            } else {
+              newIceServers.push(pcConfig.iceServers[i]);
+            }
+          }
+          pcConfig.iceServers = newIceServers;
+        }
+        return new OrigPeerConnection(pcConfig, pcConstraints);
+      };
+      window.RTCPeerConnection.prototype = OrigPeerConnection.prototype;
+      // wrap static methods. Currently just generateCertificate.
+      Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
+        get: function() {
+          return OrigPeerConnection.generateCertificate;
+        }
+      });
     }
 
-    var origGetStats = RTCPeerConnection.prototype.getStats;
-    RTCPeerConnection.prototype.getStats = function(selector,
+    var origGetStats = window.RTCPeerConnection.prototype.getStats;
+    window.RTCPeerConnection.prototype.getStats = function(selector,
         successCallback, errorCallback) {
       var self = this;
       var args = arguments;
@@ -898,7 +1257,7 @@
       // shim getStats with maplike support
       var makeMapStats = function(stats) {
         return new Map(Object.keys(stats).map(function(key) {
-          return[key, stats[key]];
+          return [key, stats[key]];
         }));
       };
 
@@ -908,7 +1267,7 @@
         };
 
         return origGetStats.apply(this, [successCallbackWrapper_,
-            arguments[0]]);
+          arguments[0]]);
       }
 
       // promise-support
@@ -924,8 +1283,8 @@
     if (browserDetails.version < 51) {
       ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
           .forEach(function(method) {
-            var nativeMethod = RTCPeerConnection.prototype[method];
-            RTCPeerConnection.prototype[method] = function() {
+            var nativeMethod = window.RTCPeerConnection.prototype[method];
+            window.RTCPeerConnection.prototype[method] = function() {
               var args = arguments;
               var self = this;
               var promise = new Promise(function(resolve, reject) {
@@ -950,8 +1309,8 @@
     // bugs) since M52: crbug/619289
     if (browserDetails.version < 52) {
       ['createOffer', 'createAnswer'].forEach(function(method) {
-        var nativeMethod = RTCPeerConnection.prototype[method];
-        RTCPeerConnection.prototype[method] = function() {
+        var nativeMethod = window.RTCPeerConnection.prototype[method];
+        window.RTCPeerConnection.prototype[method] = function() {
           var self = this;
           if (arguments.length < 1 || (arguments.length === 1 &&
               typeof arguments[0] === 'object')) {
@@ -968,18 +1327,19 @@
     // shim implicit creation of RTCSessionDescription/RTCIceCandidate
     ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
         .forEach(function(method) {
-          var nativeMethod = RTCPeerConnection.prototype[method];
-          RTCPeerConnection.prototype[method] = function() {
+          var nativeMethod = window.RTCPeerConnection.prototype[method];
+          window.RTCPeerConnection.prototype[method] = function() {
             arguments[0] = new ((method === 'addIceCandidate') ?
-                RTCIceCandidate : RTCSessionDescription)(arguments[0]);
+                window.RTCIceCandidate :
+                window.RTCSessionDescription)(arguments[0]);
             return nativeMethod.apply(this, arguments);
           };
         });
 
     // support for addIceCandidate(null or undefined)
     var nativeAddIceCandidate =
-        RTCPeerConnection.prototype.addIceCandidate;
-    RTCPeerConnection.prototype.addIceCandidate = function() {
+        window.RTCPeerConnection.prototype.addIceCandidate;
+    window.RTCPeerConnection.prototype.addIceCandidate = function() {
       if (!arguments[0]) {
         if (arguments[1]) {
           arguments[1].apply(null);
@@ -996,13 +1356,14 @@
 module.exports = {
   shimMediaStream: chromeShim.shimMediaStream,
   shimOnTrack: chromeShim.shimOnTrack,
+  shimAddTrackRemoveTrack: chromeShim.shimAddTrackRemoveTrack,
   shimGetSendersWithDtmf: chromeShim.shimGetSendersWithDtmf,
   shimSourceObject: chromeShim.shimSourceObject,
   shimPeerConnection: chromeShim.shimPeerConnection,
   shimGetUserMedia: require('./getusermedia')
 };
 
-},{"../utils.js":10,"./getusermedia":4}],4:[function(require,module,exports){
+},{"../utils.js":12,"./getusermedia":5}],5:[function(require,module,exports){
 /*
  *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
  *
@@ -1012,11 +1373,14 @@
  */
  /* eslint-env node */
 'use strict';
-var logging = require('../utils.js').log;
-var browserDetails = require('../utils.js').browserDetails;
+var utils = require('../utils.js');
+var logging = utils.log;
 
 // Expose public methods.
-module.exports = function() {
+module.exports = function(window) {
+  var browserDetails = utils.detectBrowser(window);
+  var navigator = window && window.navigator;
+
   var constraintsToChrome_ = function(c) {
     if (typeof c !== 'object' || c.mandatory || c.optional) {
       return c;
@@ -1070,14 +1434,23 @@
 
   var shimConstraints_ = function(constraints, func) {
     constraints = JSON.parse(JSON.stringify(constraints));
-    if (constraints && constraints.audio) {
+    if (constraints && typeof constraints.audio === 'object') {
+      var remap = function(obj, a, b) {
+        if (a in obj && !(b in obj)) {
+          obj[b] = obj[a];
+          delete obj[a];
+        }
+      };
+      constraints = JSON.parse(JSON.stringify(constraints));
+      remap(constraints.audio, 'autoGainControl', 'googAutoGainControl');
+      remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression');
       constraints.audio = constraintsToChrome_(constraints.audio);
     }
     if (constraints && typeof constraints.video === 'object') {
-      // Shim facingMode for mobile, where it defaults to "user".
+      // Shim facingMode for mobile & surface pro.
       var face = constraints.video.facingMode;
       face = face && ((typeof face === 'object') ? face : {ideal: face});
-      var getSupportedFacingModeLies = browserDetails.version < 59;
+      var getSupportedFacingModeLies = browserDetails.version < 61;
 
       if ((face && (face.exact === 'user' || face.exact === 'environment' ||
                     face.ideal === 'user' || face.ideal === 'environment')) &&
@@ -1085,19 +1458,30 @@
             navigator.mediaDevices.getSupportedConstraints().facingMode &&
             !getSupportedFacingModeLies)) {
         delete constraints.video.facingMode;
+        var matches;
         if (face.exact === 'environment' || face.ideal === 'environment') {
-          // Look for "back" in label, or use last cam (typically back cam).
+          matches = ['back', 'rear'];
+        } else if (face.exact === 'user' || face.ideal === 'user') {
+          matches = ['front'];
+        }
+        if (matches) {
+          // Look for matches in label, or use last cam for back (typical).
           return navigator.mediaDevices.enumerateDevices()
           .then(function(devices) {
             devices = devices.filter(function(d) {
               return d.kind === 'videoinput';
             });
-            var back = devices.find(function(d) {
-              return d.label.toLowerCase().indexOf('back') !== -1;
-            }) || (devices.length && devices[devices.length - 1]);
-            if (back) {
-              constraints.video.deviceId = face.exact ? {exact: back.deviceId} :
-                                                        {ideal: back.deviceId};
+            var dev = devices.find(function(d) {
+              return matches.some(function(match) {
+                return d.label.toLowerCase().indexOf(match) !== -1;
+              });
+            });
+            if (!dev && devices.length && matches.indexOf('back') !== -1) {
+              dev = devices[devices.length - 1]; // more likely the back cam
+            }
+            if (dev) {
+              constraints.video.deviceId = face.exact ? {exact: dev.deviceId} :
+                                                        {ideal: dev.deviceId};
             }
             constraints.video = constraintsToChrome_(constraints.video);
             logging('chrome: ' + JSON.stringify(constraints));
@@ -1115,7 +1499,12 @@
     return {
       name: {
         PermissionDeniedError: 'NotAllowedError',
-        ConstraintNotSatisfiedError: 'OverconstrainedError'
+        InvalidStateError: 'NotReadableError',
+        DevicesNotFoundError: 'NotFoundError',
+        ConstraintNotSatisfiedError: 'OverconstrainedError',
+        TrackStartError: 'NotReadableError',
+        MediaDeviceFailedDueToShutdown: 'NotReadableError',
+        MediaDeviceKillSwitchOn: 'NotReadableError'
       }[e.name] || e.name,
       message: e.message,
       constraint: e.constraintName,
@@ -1148,12 +1537,12 @@
       enumerateDevices: function() {
         return new Promise(function(resolve) {
           var kinds = {audio: 'audioinput', video: 'videoinput'};
-          return MediaStreamTrack.getSources(function(devices) {
+          return window.MediaStreamTrack.getSources(function(devices) {
             resolve(devices.map(function(device) {
               return {label: device.label,
-                      kind: kinds[device.kind],
-                      deviceId: device.id,
-                      groupId: ''};
+                kind: kinds[device.kind],
+                deviceId: device.id,
+                groupId: ''};
             }));
           });
         });
@@ -1211,7 +1600,7 @@
   }
 };
 
-},{"../utils.js":10}],5:[function(require,module,exports){
+},{"../utils.js":12}],6:[function(require,module,exports){
 /*
  *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
  *
@@ -1222,8 +1611,125 @@
  /* eslint-env node */
 'use strict';
 
+var utils = require('../utils');
+var shimRTCPeerConnection = require('./rtcpeerconnection_shim');
+
+module.exports = {
+  shimGetUserMedia: require('./getusermedia'),
+  shimPeerConnection: function(window) {
+    var browserDetails = utils.detectBrowser(window);
+
+    if (window.RTCIceGatherer) {
+      // ORTC defines an RTCIceCandidate object but no constructor.
+      // Not implemented in Edge.
+      if (!window.RTCIceCandidate) {
+        window.RTCIceCandidate = function(args) {
+          return args;
+        };
+      }
+      // ORTC does not have a session description object but
+      // other browsers (i.e. Chrome) that will support both PC and ORTC
+      // in the future might have this defined already.
+      if (!window.RTCSessionDescription) {
+        window.RTCSessionDescription = function(args) {
+          return args;
+        };
+      }
+      // this adds an additional event listener to MediaStrackTrack that signals
+      // when a tracks enabled property was changed. Workaround for a bug in
+      // addStream, see below. No longer required in 15025+
+      if (browserDetails.version < 15025) {
+        var origMSTEnabled = Object.getOwnPropertyDescriptor(
+            window.MediaStreamTrack.prototype, 'enabled');
+        Object.defineProperty(window.MediaStreamTrack.prototype, 'enabled', {
+          set: function(value) {
+            origMSTEnabled.set.call(this, value);
+            var ev = new Event('enabled');
+            ev.enabled = value;
+            this.dispatchEvent(ev);
+          }
+        });
+      }
+    }
+
+    // ORTC defines the DTMF sender a bit different.
+    // https://github.com/w3c/ortc/issues/714
+    if (window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) {
+      Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', {
+        get: function() {
+          if (this._dtmf === undefined) {
+            if (this.track.kind === 'audio') {
+              this._dtmf = new window.RTCDtmfSender(this);
+            } else if (this.track.kind === 'video') {
+              this._dtmf = null;
+            }
+          }
+          return this._dtmf;
+        }
+      });
+    }
+
+    window.RTCPeerConnection =
+        shimRTCPeerConnection(window, browserDetails.version);
+  },
+  shimReplaceTrack: function(window) {
+    // ORTC has replaceTrack -- https://github.com/w3c/ortc/issues/614
+    if (window.RTCRtpSender &&
+        !('replaceTrack' in window.RTCRtpSender.prototype)) {
+      window.RTCRtpSender.prototype.replaceTrack =
+          window.RTCRtpSender.prototype.setTrack;
+    }
+  }
+};
+
+},{"../utils":12,"./getusermedia":7,"./rtcpeerconnection_shim":8}],7:[function(require,module,exports){
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+ /* eslint-env node */
+'use strict';
+
+// Expose public methods.
+module.exports = function(window) {
+  var navigator = window && window.navigator;
+
+  var shimError_ = function(e) {
+    return {
+      name: {PermissionDeniedError: 'NotAllowedError'}[e.name] || e.name,
+      message: e.message,
+      constraint: e.constraint,
+      toString: function() {
+        return this.name;
+      }
+    };
+  };
+
+  // getUserMedia error shim.
+  var origGetUserMedia = navigator.mediaDevices.getUserMedia.
+      bind(navigator.mediaDevices);
+  navigator.mediaDevices.getUserMedia = function(c) {
+    return origGetUserMedia(c).catch(function(e) {
+      return Promise.reject(shimError_(e));
+    });
+  };
+};
+
+},{}],8:[function(require,module,exports){
+/*
+ *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree.
+ */
+ /* eslint-env node */
+'use strict';
+
 var SDPUtils = require('sdp');
-var browserDetails = require('../utils').browserDetails;
 
 // sort tracks such that they follow an a-v-a-v...
 // pattern.
@@ -1251,12 +1757,15 @@
 // 2) turn: that does not have all of turn:host:port?transport=udp
 // 3) turn: with ipv6 addresses
 // 4) turn: occurring muliple times
-function filterIceServers(iceServers) {
+function filterIceServers(iceServers, edgeVersion) {
   var hasTurn = false;
   iceServers = JSON.parse(JSON.stringify(iceServers));
   return iceServers.filter(function(server) {
     if (server && (server.urls || server.url)) {
       var urls = server.urls || server.url;
+      if (server.url && !server.urls) {
+        console.warn('RTCIceServer.url is deprecated! Use urls instead.');
+      }
       var isString = typeof urls === 'string';
       if (isString) {
         urls = [urls];
@@ -1271,8 +1780,7 @@
           hasTurn = true;
           return true;
         }
-        return url.indexOf('stun:') === 0 &&
-            browserDetails.version >= 14393;
+        return url.indexOf('stun:') === 0 && edgeVersion >= 14393;
       });
 
       delete server.url;
@@ -1283,1191 +1791,1325 @@
   });
 }
 
-var edgeShim = {
-  shimPeerConnection: function() {
-    if (window.RTCIceGatherer) {
-      // ORTC defines an RTCIceCandidate object but no constructor.
-      // Not implemented in Edge.
-      if (!window.RTCIceCandidate) {
-        window.RTCIceCandidate = function(args) {
-          return args;
-        };
-      }
-      // ORTC does not have a session description object but
-      // other browsers (i.e. Chrome) that will support both PC and ORTC
-      // in the future might have this defined already.
-      if (!window.RTCSessionDescription) {
-        window.RTCSessionDescription = function(args) {
-          return args;
-        };
-      }
-      // this adds an additional event listener to MediaStrackTrack that signals
-      // when a tracks enabled property was changed. Workaround for a bug in
-      // addStream, see below. No longer required in 15025+
-      if (browserDetails.version < 15025) {
-        var origMSTEnabled = Object.getOwnPropertyDescriptor(
-            MediaStreamTrack.prototype, 'enabled');
-        Object.defineProperty(MediaStreamTrack.prototype, 'enabled', {
-          set: function(value) {
-            origMSTEnabled.set.call(this, value);
-            var ev = new Event('enabled');
-            ev.enabled = value;
-            this.dispatchEvent(ev);
-          }
-        });
+// Determines the intersection of local and remote capabilities.
+function getCommonCapabilities(localCapabilities, remoteCapabilities) {
+  var commonCapabilities = {
+    codecs: [],
+    headerExtensions: [],
+    fecMechanisms: []
+  };
+
+  var findCodecByPayloadType = function(pt, codecs) {
+    pt = parseInt(pt, 10);
+    for (var i = 0; i < codecs.length; i++) {
+      if (codecs[i].payloadType === pt ||
+          codecs[i].preferredPayloadType === pt) {
+        return codecs[i];
       }
     }
+  };
 
-    window.RTCPeerConnection = function(config) {
-      var self = this;
+  var rtxCapabilityMatches = function(lRtx, rRtx, lCodecs, rCodecs) {
+    var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs);
+    var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs);
+    return lCodec && rCodec &&
+        lCodec.name.toLowerCase() === rCodec.name.toLowerCase();
+  };
 
-      var _eventTarget = document.createDocumentFragment();
-      ['addEventListener', 'removeEventListener', 'dispatchEvent']
-          .forEach(function(method) {
-            self[method] = _eventTarget[method].bind(_eventTarget);
-          });
-
-      this.onicecandidate = null;
-      this.onaddstream = null;
-      this.ontrack = null;
-      this.onremovestream = null;
-      this.onsignalingstatechange = null;
-      this.oniceconnectionstatechange = null;
-      this.onicegatheringstatechange = null;
-      this.onnegotiationneeded = null;
-      this.ondatachannel = null;
-
-      this.localStreams = [];
-      this.remoteStreams = [];
-      this.getLocalStreams = function() {
-        return self.localStreams;
-      };
-      this.getRemoteStreams = function() {
-        return self.remoteStreams;
-      };
-
-      this.localDescription = new RTCSessionDescription({
-        type: '',
-        sdp: ''
-      });
-      this.remoteDescription = new RTCSessionDescription({
-        type: '',
-        sdp: ''
-      });
-      this.signalingState = 'stable';
-      this.iceConnectionState = 'new';
-      this.iceGatheringState = 'new';
-
-      this.iceOptions = {
-        gatherPolicy: 'all',
-        iceServers: []
-      };
-      if (config && config.iceTransportPolicy) {
-        switch (config.iceTransportPolicy) {
-          case 'all':
-          case 'relay':
-            this.iceOptions.gatherPolicy = config.iceTransportPolicy;
-            break;
-          default:
-            // don't set iceTransportPolicy.
-            break;
+  localCapabilities.codecs.forEach(function(lCodec) {
+    for (var i = 0; i < remoteCapabilities.codecs.length; i++) {
+      var rCodec = remoteCapabilities.codecs[i];
+      if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() &&
+          lCodec.clockRate === rCodec.clockRate) {
+        if (lCodec.name.toLowerCase() === 'rtx' &&
+            lCodec.parameters && rCodec.parameters.apt) {
+          // for RTX we need to find the local rtx that has a apt
+          // which points to the same local codec as the remote one.
+          if (!rtxCapabilityMatches(lCodec, rCodec,
+              localCapabilities.codecs, remoteCapabilities.codecs)) {
+            continue;
+          }
         }
-      }
-      this.usingBundle = config && config.bundlePolicy === 'max-bundle';
+        rCodec = JSON.parse(JSON.stringify(rCodec)); // deepcopy
+        // number of channels is the highest common number of channels
+        rCodec.numChannels = Math.min(lCodec.numChannels,
+            rCodec.numChannels);
+        // push rCodec so we reply with offerer payload type
+        commonCapabilities.codecs.push(rCodec);
 
-      if (config && config.iceServers) {
-        this.iceOptions.iceServers = filterIceServers(config.iceServers);
-      }
-      this._config = config;
-
-      // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ...
-      // everything that is needed to describe a SDP m-line.
-      this.transceivers = [];
-
-      // since the iceGatherer is currently created in createOffer but we
-      // must not emit candidates until after setLocalDescription we buffer
-      // them in this array.
-      this._localIceCandidatesBuffer = [];
-    };
-
-    window.RTCPeerConnection.prototype._emitGatheringStateChange = function() {
-      var event = new Event('icegatheringstatechange');
-      this.dispatchEvent(event);
-      if (this.onicegatheringstatechange !== null) {
-        this.onicegatheringstatechange(event);
-      }
-    };
-
-    window.RTCPeerConnection.prototype._emitBufferedCandidates = function() {
-      var self = this;
-      var sections = SDPUtils.splitSections(self.localDescription.sdp);
-      // FIXME: need to apply ice candidates in a way which is async but
-      // in-order
-      this._localIceCandidatesBuffer.forEach(function(event) {
-        var end = !event.candidate || Object.keys(event.candidate).length === 0;
-        if (end) {
-          for (var j = 1; j < sections.length; j++) {
-            if (sections[j].indexOf('\r\na=end-of-candidates\r\n') === -1) {
-              sections[j] += 'a=end-of-candidates\r\n';
+        // determine common feedback mechanisms
+        rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(fb) {
+          for (var j = 0; j < lCodec.rtcpFeedback.length; j++) {
+            if (lCodec.rtcpFeedback[j].type === fb.type &&
+                lCodec.rtcpFeedback[j].parameter === fb.parameter) {
+              return true;
             }
           }
-        } else {
-          sections[event.candidate.sdpMLineIndex + 1] +=
-              'a=' + event.candidate.candidate + '\r\n';
+          return false;
+        });
+        // FIXME: also need to determine .parameters
+        //  see https://github.com/openpeer/ortc/issues/569
+        break;
+      }
+    }
+  });
+
+  localCapabilities.headerExtensions.forEach(function(lHeaderExtension) {
+    for (var i = 0; i < remoteCapabilities.headerExtensions.length;
+         i++) {
+      var rHeaderExtension = remoteCapabilities.headerExtensions[i];
+      if (lHeaderExtension.uri === rHeaderExtension.uri) {
+        commonCapabilities.headerExtensions.push(rHeaderExtension);
+        break;
+      }
+    }
+  });
+
+  // FIXME: fecMechanisms
+  return commonCapabilities;
+}
+
+// is action=setLocalDescription with type allowed in signalingState
+function isActionAllowedInSignalingState(action, type, signalingState) {
+  return {
+    offer: {
+      setLocalDescription: ['stable', 'have-local-offer'],
+      setRemoteDescription: ['stable', 'have-remote-offer']
+    },
+    answer: {
+      setLocalDescription: ['have-remote-offer', 'have-local-pranswer'],
+      setRemoteDescription: ['have-local-offer', 'have-remote-pranswer']
+    }
+  }[type][action].indexOf(signalingState) !== -1;
+}
+
+module.exports = function(window, edgeVersion) {
+  var RTCPeerConnection = function(config) {
+    var self = this;
+
+    var _eventTarget = document.createDocumentFragment();
+    ['addEventListener', 'removeEventListener', 'dispatchEvent']
+        .forEach(function(method) {
+          self[method] = _eventTarget[method].bind(_eventTarget);
+        });
+
+    this.needNegotiation = false;
+
+    this.onicecandidate = null;
+    this.onaddstream = null;
+    this.ontrack = null;
+    this.onremovestream = null;
+    this.onsignalingstatechange = null;
+    this.oniceconnectionstatechange = null;
+    this.onicegatheringstatechange = null;
+    this.onnegotiationneeded = null;
+    this.ondatachannel = null;
+    this.canTrickleIceCandidates = null;
+
+    this.localStreams = [];
+    this.remoteStreams = [];
+    this.getLocalStreams = function() {
+      return self.localStreams;
+    };
+    this.getRemoteStreams = function() {
+      return self.remoteStreams;
+    };
+
+    this.localDescription = new window.RTCSessionDescription({
+      type: '',
+      sdp: ''
+    });
+    this.remoteDescription = new window.RTCSessionDescription({
+      type: '',
+      sdp: ''
+    });
+    this.signalingState = 'stable';
+    this.iceConnectionState = 'new';
+    this.iceGatheringState = 'new';
+
+    this.iceOptions = {
+      gatherPolicy: 'all',
+      iceServers: []
+    };
+    if (config && config.iceTransportPolicy) {
+      switch (config.iceTransportPolicy) {
+        case 'all':
+        case 'relay':
+          this.iceOptions.gatherPolicy = config.iceTransportPolicy;
+          break;
+        default:
+          // don't set iceTransportPolicy.
+          break;
+      }
+    }
+    this.usingBundle = config && config.bundlePolicy === 'max-bundle';
+
+    if (config && config.iceServers) {
+      this.iceOptions.iceServers = filterIceServers(config.iceServers,
+          edgeVersion);
+    }
+    this._config = config || {};
+
+    // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ...
+    // everything that is needed to describe a SDP m-line.
+    this.transceivers = [];
+
+    // since the iceGatherer is currently created in createOffer but we
+    // must not emit candidates until after setLocalDescription we buffer
+    // them in this array.
+    this._localIceCandidatesBuffer = [];
+
+    this._sdpSessionId = SDPUtils.generateSessionId();
+  };
+
+  RTCPeerConnection.prototype._emitGatheringStateChange = function() {
+    var event = new Event('icegatheringstatechange');
+    this.dispatchEvent(event);
+    if (this.onicegatheringstatechange !== null) {
+      this.onicegatheringstatechange(event);
+    }
+  };
+
+  RTCPeerConnection.prototype._emitBufferedCandidates = function() {
+    var self = this;
+    var sections = SDPUtils.splitSections(self.localDescription.sdp);
+    // FIXME: need to apply ice candidates in a way which is async but
+    // in-order
+    this._localIceCandidatesBuffer.forEach(function(event) {
+      var end = !event.candidate || Object.keys(event.candidate).length === 0;
+      if (end) {
+        for (var j = 1; j < sections.length; j++) {
+          if (sections[j].indexOf('\r\na=end-of-candidates\r\n') === -1) {
+            sections[j] += 'a=end-of-candidates\r\n';
+          }
         }
-        self.localDescription.sdp = sections.join('');
-        self.dispatchEvent(event);
-        if (self.onicecandidate !== null) {
-          self.onicecandidate(event);
+      } else {
+        sections[event.candidate.sdpMLineIndex + 1] +=
+            'a=' + event.candidate.candidate + '\r\n';
+      }
+      self.localDescription.sdp = sections.join('');
+      self.dispatchEvent(event);
+      if (self.onicecandidate !== null) {
+        self.onicecandidate(event);
+      }
+      if (!event.candidate && self.iceGatheringState !== 'complete') {
+        var complete = self.transceivers.every(function(transceiver) {
+          return transceiver.iceGatherer &&
+              transceiver.iceGatherer.state === 'completed';
+        });
+        if (complete && self.iceGatheringStateChange !== 'complete') {
+          self.iceGatheringState = 'complete';
+          self._emitGatheringStateChange();
         }
-        if (!event.candidate && self.iceGatheringState !== 'complete') {
-          var complete = self.transceivers.every(function(transceiver) {
-            return transceiver.iceGatherer &&
-                transceiver.iceGatherer.state === 'completed';
-          });
-          if (complete && self.iceGatheringStateChange !== 'complete') {
+      }
+    });
+    this._localIceCandidatesBuffer = [];
+  };
+
+  RTCPeerConnection.prototype.getConfiguration = function() {
+    return this._config;
+  };
+
+  // internal helper to create a transceiver object.
+  // (whih is not yet the same as the WebRTC 1.0 transceiver)
+  RTCPeerConnection.prototype._createTransceiver = function(kind) {
+    var hasBundleTransport = this.transceivers.length > 0;
+    var transceiver = {
+      track: null,
+      iceGatherer: null,
+      iceTransport: null,
+      dtlsTransport: null,
+      localCapabilities: null,
+      remoteCapabilities: null,
+      rtpSender: null,
+      rtpReceiver: null,
+      kind: kind,
+      mid: null,
+      sendEncodingParameters: null,
+      recvEncodingParameters: null,
+      stream: null,
+      wantReceive: true
+    };
+    if (this.usingBundle && hasBundleTransport) {
+      transceiver.iceTransport = this.transceivers[0].iceTransport;
+      transceiver.dtlsTransport = this.transceivers[0].dtlsTransport;
+    } else {
+      var transports = this._createIceAndDtlsTransports();
+      transceiver.iceTransport = transports.iceTransport;
+      transceiver.dtlsTransport = transports.dtlsTransport;
+    }
+    this.transceivers.push(transceiver);
+    return transceiver;
+  };
+
+  RTCPeerConnection.prototype.addTrack = function(track, stream) {
+    var transceiver;
+    for (var i = 0; i < this.transceivers.length; i++) {
+      if (!this.transceivers[i].track &&
+          this.transceivers[i].kind === track.kind) {
+        transceiver = this.transceivers[i];
+      }
+    }
+    if (!transceiver) {
+      transceiver = this._createTransceiver(track.kind);
+    }
+
+    transceiver.track = track;
+    transceiver.stream = stream;
+    transceiver.rtpSender = new window.RTCRtpSender(track,
+        transceiver.dtlsTransport);
+
+    this._maybeFireNegotiationNeeded();
+    return transceiver.rtpSender;
+  };
+
+  RTCPeerConnection.prototype.addStream = function(stream) {
+    var self = this;
+    if (edgeVersion >= 15025) {
+      this.localStreams.push(stream);
+      stream.getTracks().forEach(function(track) {
+        self.addTrack(track, stream);
+      });
+    } else {
+      // Clone is necessary for local demos mostly, attaching directly
+      // to two different senders does not work (build 10547).
+      // Fixed in 15025 (or earlier)
+      var clonedStream = stream.clone();
+      stream.getTracks().forEach(function(track, idx) {
+        var clonedTrack = clonedStream.getTracks()[idx];
+        track.addEventListener('enabled', function(event) {
+          clonedTrack.enabled = event.enabled;
+        });
+      });
+      clonedStream.getTracks().forEach(function(track) {
+        self.addTrack(track, clonedStream);
+      });
+      this.localStreams.push(clonedStream);
+    }
+    this._maybeFireNegotiationNeeded();
+  };
+
+  RTCPeerConnection.prototype.removeStream = function(stream) {
+    var idx = this.localStreams.indexOf(stream);
+    if (idx > -1) {
+      this.localStreams.splice(idx, 1);
+      this._maybeFireNegotiationNeeded();
+    }
+  };
+
+  RTCPeerConnection.prototype.getSenders = function() {
+    return this.transceivers.filter(function(transceiver) {
+      return !!transceiver.rtpSender;
+    })
+    .map(function(transceiver) {
+      return transceiver.rtpSender;
+    });
+  };
+
+  RTCPeerConnection.prototype.getReceivers = function() {
+    return this.transceivers.filter(function(transceiver) {
+      return !!transceiver.rtpReceiver;
+    })
+    .map(function(transceiver) {
+      return transceiver.rtpReceiver;
+    });
+  };
+
+  // Create ICE gatherer and hook it up.
+  RTCPeerConnection.prototype._createIceGatherer = function(mid,
+      sdpMLineIndex) {
+    var self = this;
+    var iceGatherer = new window.RTCIceGatherer(self.iceOptions);
+    iceGatherer.onlocalcandidate = function(evt) {
+      var event = new Event('icecandidate');
+      event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex};
+
+      var cand = evt.candidate;
+      var end = !cand || Object.keys(cand).length === 0;
+      // Edge emits an empty object for RTCIceCandidateComplete‥
+      if (end) {
+        // polyfill since RTCIceGatherer.state is not implemented in
+        // Edge 10547 yet.
+        if (iceGatherer.state === undefined) {
+          iceGatherer.state = 'completed';
+        }
+      } else {
+        // RTCIceCandidate doesn't have a component, needs to be added
+        cand.component = 1;
+        event.candidate.candidate = SDPUtils.writeCandidate(cand);
+      }
+
+      // update local description.
+      var sections = SDPUtils.splitSections(self.localDescription.sdp);
+      if (!end) {
+        sections[event.candidate.sdpMLineIndex + 1] +=
+            'a=' + event.candidate.candidate + '\r\n';
+      } else {
+        sections[event.candidate.sdpMLineIndex + 1] +=
+            'a=end-of-candidates\r\n';
+      }
+      self.localDescription.sdp = sections.join('');
+      var transceivers = self._pendingOffer ? self._pendingOffer :
+          self.transceivers;
+      var complete = transceivers.every(function(transceiver) {
+        return transceiver.iceGatherer &&
+            transceiver.iceGatherer.state === 'completed';
+      });
+
+      // Emit candidate if localDescription is set.
+      // Also emits null candidate when all gatherers are complete.
+      switch (self.iceGatheringState) {
+        case 'new':
+          if (!end) {
+            self._localIceCandidatesBuffer.push(event);
+          }
+          if (end && complete) {
+            self._localIceCandidatesBuffer.push(
+                new Event('icecandidate'));
+          }
+          break;
+        case 'gathering':
+          self._emitBufferedCandidates();
+          if (!end) {
+            self.dispatchEvent(event);
+            if (self.onicecandidate !== null) {
+              self.onicecandidate(event);
+            }
+          }
+          if (complete) {
+            self.dispatchEvent(new Event('icecandidate'));
+            if (self.onicecandidate !== null) {
+              self.onicecandidate(new Event('icecandidate'));
+            }
             self.iceGatheringState = 'complete';
             self._emitGatheringStateChange();
           }
-        }
-      });
-      this._localIceCandidatesBuffer = [];
-    };
-
-    window.RTCPeerConnection.prototype.getConfiguration = function() {
-      return this._config;
-    };
-
-    window.RTCPeerConnection.prototype.addStream = function(stream) {
-      if (browserDetails.version >= 15025) {
-        this.localStreams.push(stream);
-      } else {
-        // Clone is necessary for local demos mostly, attaching directly
-        // to two different senders does not work (build 10547).
-        // Fixed in 15025 (or earlier)
-        var clonedStream = stream.clone();
-        stream.getTracks().forEach(function(track, idx) {
-          var clonedTrack = clonedStream.getTracks()[idx];
-          track.addEventListener('enabled', function(event) {
-            clonedTrack.enabled = event.enabled;
-          });
-        });
-        this.localStreams.push(clonedStream);
-      }
-      this._maybeFireNegotiationNeeded();
-    };
-
-    window.RTCPeerConnection.prototype.removeStream = function(stream) {
-      var idx = this.localStreams.indexOf(stream);
-      if (idx > -1) {
-        this.localStreams.splice(idx, 1);
-        this._maybeFireNegotiationNeeded();
+          break;
+        case 'complete':
+          // should not happen... currently!
+          break;
+        default: // no-op.
+          break;
       }
     };
+    return iceGatherer;
+  };
 
-    window.RTCPeerConnection.prototype.getSenders = function() {
-      return this.transceivers.filter(function(transceiver) {
-        return !!transceiver.rtpSender;
-      })
-      .map(function(transceiver) {
-        return transceiver.rtpSender;
-      });
+  // Create ICE transport and DTLS transport.
+  RTCPeerConnection.prototype._createIceAndDtlsTransports = function() {
+    var self = this;
+    var iceTransport = new window.RTCIceTransport(null);
+    iceTransport.onicestatechange = function() {
+      self._updateConnectionState();
     };
 
-    window.RTCPeerConnection.prototype.getReceivers = function() {
-      return this.transceivers.filter(function(transceiver) {
-        return !!transceiver.rtpReceiver;
-      })
-      .map(function(transceiver) {
-        return transceiver.rtpReceiver;
-      });
+    var dtlsTransport = new window.RTCDtlsTransport(iceTransport);
+    dtlsTransport.ondtlsstatechange = function() {
+      self._updateConnectionState();
+    };
+    dtlsTransport.onerror = function() {
+      // onerror does not set state to failed by itself.
+      Object.defineProperty(dtlsTransport, 'state',
+          {value: 'failed', writable: true});
+      self._updateConnectionState();
     };
 
-    // Determines the intersection of local and remote capabilities.
-    window.RTCPeerConnection.prototype._getCommonCapabilities =
-        function(localCapabilities, remoteCapabilities) {
-          var commonCapabilities = {
-            codecs: [],
-            headerExtensions: [],
-            fecMechanisms: []
-          };
-
-          var findCodecByPayloadType = function(pt, codecs) {
-            pt = parseInt(pt, 10);
-            for (var i = 0; i < codecs.length; i++) {
-              if (codecs[i].payloadType === pt ||
-                  codecs[i].preferredPayloadType === pt) {
-                return codecs[i];
-              }
-            }
-          };
-
-          var rtxCapabilityMatches = function(lRtx, rRtx, lCodecs, rCodecs) {
-            var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs);
-            var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs);
-            return lCodec && rCodec &&
-                lCodec.name.toLowerCase() === rCodec.name.toLowerCase();
-          };
-
-          localCapabilities.codecs.forEach(function(lCodec) {
-            for (var i = 0; i < remoteCapabilities.codecs.length; i++) {
-              var rCodec = remoteCapabilities.codecs[i];
-              if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() &&
-                  lCodec.clockRate === rCodec.clockRate) {
-                if (lCodec.name.toLowerCase() === 'rtx' &&
-                    lCodec.parameters && rCodec.parameters.apt) {
-                  // for RTX we need to find the local rtx that has a apt
-                  // which points to the same local codec as the remote one.
-                  if (!rtxCapabilityMatches(lCodec, rCodec,
-                      localCapabilities.codecs, remoteCapabilities.codecs)) {
-                    continue;
-                  }
-                }
-                rCodec = JSON.parse(JSON.stringify(rCodec)); // deepcopy
-                // number of channels is the highest common number of channels
-                rCodec.numChannels = Math.min(lCodec.numChannels,
-                    rCodec.numChannels);
-                // push rCodec so we reply with offerer payload type
-                commonCapabilities.codecs.push(rCodec);
-
-                // determine common feedback mechanisms
-                rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(fb) {
-                  for (var j = 0; j < lCodec.rtcpFeedback.length; j++) {
-                    if (lCodec.rtcpFeedback[j].type === fb.type &&
-                        lCodec.rtcpFeedback[j].parameter === fb.parameter) {
-                      return true;
-                    }
-                  }
-                  return false;
-                });
-                // FIXME: also need to determine .parameters
-                //  see https://github.com/openpeer/ortc/issues/569
-                break;
-              }
-            }
-          });
-
-          localCapabilities.headerExtensions
-              .forEach(function(lHeaderExtension) {
-                for (var i = 0; i < remoteCapabilities.headerExtensions.length;
-                     i++) {
-                  var rHeaderExtension = remoteCapabilities.headerExtensions[i];
-                  if (lHeaderExtension.uri === rHeaderExtension.uri) {
-                    commonCapabilities.headerExtensions.push(rHeaderExtension);
-                    break;
-                  }
-                }
-              });
-
-          // FIXME: fecMechanisms
-          return commonCapabilities;
-        };
-
-    // Create ICE gatherer, ICE transport and DTLS transport.
-    window.RTCPeerConnection.prototype._createIceAndDtlsTransports =
-        function(mid, sdpMLineIndex) {
-          var self = this;
-          var iceGatherer = new RTCIceGatherer(self.iceOptions);
-          var iceTransport = new RTCIceTransport(iceGatherer);
-          iceGatherer.onlocalcandidate = function(evt) {
-            var event = new Event('icecandidate');
-            event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex};
-
-            var cand = evt.candidate;
-            var end = !cand || Object.keys(cand).length === 0;
-            // Edge emits an empty object for RTCIceCandidateComplete‥
-            if (end) {
-              // polyfill since RTCIceGatherer.state is not implemented in
-              // Edge 10547 yet.
-              if (iceGatherer.state === undefined) {
-                iceGatherer.state = 'completed';
-              }
-            } else {
-              // RTCIceCandidate doesn't have a component, needs to be added
-              cand.component = iceTransport.component === 'RTCP' ? 2 : 1;
-              event.candidate.candidate = SDPUtils.writeCandidate(cand);
-            }
-
-            // update local description.
-            var sections = SDPUtils.splitSections(self.localDescription.sdp);
-            if (!end) {
-              sections[event.candidate.sdpMLineIndex + 1] +=
-                  'a=' + event.candidate.candidate + '\r\n';
-            } else {
-              sections[event.candidate.sdpMLineIndex + 1] +=
-                  'a=end-of-candidates\r\n';
-            }
-            self.localDescription.sdp = sections.join('');
-            var transceivers = self._pendingOffer ? self._pendingOffer :
-                self.transceivers;
-            var complete = transceivers.every(function(transceiver) {
-              return transceiver.iceGatherer &&
-                  transceiver.iceGatherer.state === 'completed';
-            });
-
-            // Emit candidate if localDescription is set.
-            // Also emits null candidate when all gatherers are complete.
-            switch (self.iceGatheringState) {
-              case 'new':
-                if (!end) {
-                  self._localIceCandidatesBuffer.push(event);
-                }
-                if (end && complete) {
-                  self._localIceCandidatesBuffer.push(
-                      new Event('icecandidate'));
-                }
-                break;
-              case 'gathering':
-                self._emitBufferedCandidates();
-                if (!end) {
-                  self.dispatchEvent(event);
-                  if (self.onicecandidate !== null) {
-                    self.onicecandidate(event);
-                  }
-                }
-                if (complete) {
-                  self.dispatchEvent(new Event('icecandidate'));
-                  if (self.onicecandidate !== null) {
-                    self.onicecandidate(new Event('icecandidate'));
-                  }
-                  self.iceGatheringState = 'complete';
-                  self._emitGatheringStateChange();
-                }
-                break;
-              case 'complete':
-                // should not happen... currently!
-                break;
-              default: // no-op.
-                break;
-            }
-          };
-          iceTransport.onicestatechange = function() {
-            self._updateConnectionState();
-          };
-
-          var dtlsTransport = new RTCDtlsTransport(iceTransport);
-          dtlsTransport.ondtlsstatechange = function() {
-            self._updateConnectionState();
-          };
-          dtlsTransport.onerror = function() {
-            // onerror does not set state to failed by itself.
-            dtlsTransport.state = 'failed';
-            self._updateConnectionState();
-          };
-
-          return {
-            iceGatherer: iceGatherer,
-            iceTransport: iceTransport,
-            dtlsTransport: dtlsTransport
-          };
-        };
-
-    // Start the RTP Sender and Receiver for a transceiver.
-    window.RTCPeerConnection.prototype._transceive = function(transceiver,
-        send, recv) {
-      var params = this._getCommonCapabilities(transceiver.localCapabilities,
-          transceiver.remoteCapabilities);
-      if (send && transceiver.rtpSender) {
-        params.encodings = transceiver.sendEncodingParameters;
-        params.rtcp = {
-          cname: SDPUtils.localCName
-        };
-        if (transceiver.recvEncodingParameters.length) {
-          params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc;
-        }
-        transceiver.rtpSender.send(params);
-      }
-      if (recv && transceiver.rtpReceiver) {
-        // remove RTX field in Edge 14942
-        if (transceiver.kind === 'video'
-            && transceiver.recvEncodingParameters
-            && browserDetails.version < 15019) {
-          transceiver.recvEncodingParameters.forEach(function(p) {
-            delete p.rtx;
-          });
-        }
-        params.encodings = transceiver.recvEncodingParameters;
-        params.rtcp = {
-          cname: transceiver.cname
-        };
-        if (transceiver.sendEncodingParameters.length) {
-          params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc;
-        }
-        transceiver.rtpReceiver.receive(params);
-      }
+    return {
+      iceTransport: iceTransport,
+      dtlsTransport: dtlsTransport
     };
+  };
 
-    window.RTCPeerConnection.prototype.setLocalDescription =
-        function(description) {
-          var self = this;
-          var sections;
-          var sessionpart;
-          if (description.type === 'offer') {
-            // FIXME: What was the purpose of this empty if statement?
-            // if (!this._pendingOffer) {
-            // } else {
-            if (this._pendingOffer) {
-              // VERY limited support for SDP munging. Limited to:
-              // * changing the order of codecs
-              sections = SDPUtils.splitSections(description.sdp);
-              sessionpart = sections.shift();
-              sections.forEach(function(mediaSection, sdpMLineIndex) {
-                var caps = SDPUtils.parseRtpParameters(mediaSection);
-                self._pendingOffer[sdpMLineIndex].localCapabilities = caps;
-              });
-              this.transceivers = this._pendingOffer;
-              delete this._pendingOffer;
-            }
-          } else if (description.type === 'answer') {
-            sections = SDPUtils.splitSections(self.remoteDescription.sdp);
-            sessionpart = sections.shift();
-            var isIceLite = SDPUtils.matchPrefix(sessionpart,
-                'a=ice-lite').length > 0;
-            sections.forEach(function(mediaSection, sdpMLineIndex) {
-              var transceiver = self.transceivers[sdpMLineIndex];
-              var iceGatherer = transceiver.iceGatherer;
-              var iceTransport = transceiver.iceTransport;
-              var dtlsTransport = transceiver.dtlsTransport;
-              var localCapabilities = transceiver.localCapabilities;
-              var remoteCapabilities = transceiver.remoteCapabilities;
+  // Destroy ICE gatherer, ICE transport and DTLS transport.
+  // Without triggering the callbacks.
+  RTCPeerConnection.prototype._disposeIceAndDtlsTransports = function(
+      sdpMLineIndex) {
+    var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer;
+    if (iceGatherer) {
+      delete iceGatherer.onlocalcandidate;
+      delete this.transceivers[sdpMLineIndex].iceGatherer;
+    }
+    var iceTransport = this.transceivers[sdpMLineIndex].iceTransport;
+    if (iceTransport) {
+      delete iceTransport.onicestatechange;
+      delete this.transceivers[sdpMLineIndex].iceTransport;
+    }
+    var dtlsTransport = this.transceivers[sdpMLineIndex].dtlsTransport;
+    if (dtlsTransport) {
+      delete dtlsTransport.ondtlsstatechange;
+      delete dtlsTransport.onerror;
+      delete this.transceivers[sdpMLineIndex].dtlsTransport;
+    }
+  };
 
-              var rejected = mediaSection.split('\n', 1)[0]
-                  .split(' ', 2)[1] === '0';
-
-              if (!rejected && !transceiver.isDatachannel) {
-                var remoteIceParameters = SDPUtils.getIceParameters(
-                    mediaSection, sessionpart);
-                var remoteDtlsParameters = SDPUtils.getDtlsParameters(
-                    mediaSection, sessionpart);
-                if (isIceLite) {
-                  remoteDtlsParameters.role = 'server';
-                }
-
-                if (!self.usingBundle || sdpMLineIndex === 0) {
-                  iceTransport.start(iceGatherer, remoteIceParameters,
-                      isIceLite ? 'controlling' : 'controlled');
-                  dtlsTransport.start(remoteDtlsParameters);
-                }
-
-                // Calculate intersection of capabilities.
-                var params = self._getCommonCapabilities(localCapabilities,
-                    remoteCapabilities);
-
-                // Start the RTCRtpSender. The RTCRtpReceiver for this
-                // transceiver has already been started in setRemoteDescription.
-                self._transceive(transceiver,
-                    params.codecs.length > 0,
-                    false);
-              }
-            });
-          }
-
-          this.localDescription = {
-            type: description.type,
-            sdp: description.sdp
-          };
-          switch (description.type) {
-            case 'offer':
-              this._updateSignalingState('have-local-offer');
-              break;
-            case 'answer':
-              this._updateSignalingState('stable');
-              break;
-            default:
-              throw new TypeError('unsupported type "' + description.type +
-                  '"');
-          }
-
-          // If a success callback was provided, emit ICE candidates after it
-          // has been executed. Otherwise, emit callback after the Promise is
-          // resolved.
-          var hasCallback = arguments.length > 1 &&
-            typeof arguments[1] === 'function';
-          if (hasCallback) {
-            var cb = arguments[1];
-            window.setTimeout(function() {
-              cb();
-              if (self.iceGatheringState === 'new') {
-                self.iceGatheringState = 'gathering';
-                self._emitGatheringStateChange();
-              }
-              self._emitBufferedCandidates();
-            }, 0);
-          }
-          var p = Promise.resolve();
-          p.then(function() {
-            if (!hasCallback) {
-              if (self.iceGatheringState === 'new') {
-                self.iceGatheringState = 'gathering';
-                self._emitGatheringStateChange();
-              }
-              // Usually candidates will be emitted earlier.
-              window.setTimeout(self._emitBufferedCandidates.bind(self), 500);
-            }
-          });
-          return p;
-        };
-
-    window.RTCPeerConnection.prototype.setRemoteDescription =
-        function(description) {
-          var self = this;
-          var stream = new MediaStream();
-          var receiverList = [];
-          var sections = SDPUtils.splitSections(description.sdp);
-          var sessionpart = sections.shift();
-          var isIceLite = SDPUtils.matchPrefix(sessionpart,
-              'a=ice-lite').length > 0;
-          this.usingBundle = SDPUtils.matchPrefix(sessionpart,
-              'a=group:BUNDLE ').length > 0;
-          sections.forEach(function(mediaSection, sdpMLineIndex) {
-            var lines = SDPUtils.splitLines(mediaSection);
-            var mline = lines[0].substr(2).split(' ');
-            var kind = mline[0];
-            var rejected = mline[1] === '0';
-            var direction = SDPUtils.getDirection(mediaSection, sessionpart);
-
-            var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:');
-            if (mid.length) {
-              mid = mid[0].substr(6);
-            } else {
-              mid = SDPUtils.generateIdentifier();
-            }
-
-            // Reject datachannels which are not implemented yet.
-            if (kind === 'application' && mline[2] === 'DTLS/SCTP') {
-              self.transceivers[sdpMLineIndex] = {
-                mid: mid,
-                isDatachannel: true
-              };
-              return;
-            }
-
-            var transceiver;
-            var iceGatherer;
-            var iceTransport;
-            var dtlsTransport;
-            var rtpSender;
-            var rtpReceiver;
-            var sendEncodingParameters;
-            var recvEncodingParameters;
-            var localCapabilities;
-
-            var track;
-            // FIXME: ensure the mediaSection has rtcp-mux set.
-            var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection);
-            var remoteIceParameters;
-            var remoteDtlsParameters;
-            if (!rejected) {
-              remoteIceParameters = SDPUtils.getIceParameters(mediaSection,
-                  sessionpart);
-              remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection,
-                  sessionpart);
-              remoteDtlsParameters.role = 'client';
-            }
-            recvEncodingParameters =
-                SDPUtils.parseRtpEncodingParameters(mediaSection);
-
-            var cname;
-            // Gets the first SSRC. Note that with RTX there might be multiple
-            // SSRCs.
-            var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
-                .map(function(line) {
-                  return SDPUtils.parseSsrcMedia(line);
-                })
-                .filter(function(obj) {
-                  return obj.attribute === 'cname';
-                })[0];
-            if (remoteSsrc) {
-              cname = remoteSsrc.value;
-            }
-
-            var isComplete = SDPUtils.matchPrefix(mediaSection,
-                'a=end-of-candidates', sessionpart).length > 0;
-            var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:')
-                .map(function(cand) {
-                  return SDPUtils.parseCandidate(cand);
-                })
-                .filter(function(cand) {
-                  return cand.component === '1';
-                });
-            if (description.type === 'offer' && !rejected) {
-              var transports = self.usingBundle && sdpMLineIndex > 0 ? {
-                iceGatherer: self.transceivers[0].iceGatherer,
-                iceTransport: self.transceivers[0].iceTransport,
-                dtlsTransport: self.transceivers[0].dtlsTransport
-              } : self._createIceAndDtlsTransports(mid, sdpMLineIndex);
-
-              if (isComplete && (!self.usingBundle || sdpMLineIndex === 0)) {
-                transports.iceTransport.setRemoteCandidates(cands);
-              }
-
-              localCapabilities = RTCRtpReceiver.getCapabilities(kind);
-
-              // filter RTX until additional stuff needed for RTX is implemented
-              // in adapter.js
-              if (browserDetails.version < 15019) {
-                localCapabilities.codecs = localCapabilities.codecs.filter(
-                    function(codec) {
-                      return codec.name !== 'rtx';
-                    });
-              }
-
-              sendEncodingParameters = [{
-                ssrc: (2 * sdpMLineIndex + 2) * 1001
-              }];
-
-              if (direction === 'sendrecv' || direction === 'sendonly') {
-                rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport,
-                    kind);
-
-                track = rtpReceiver.track;
-                receiverList.push([track, rtpReceiver]);
-                // FIXME: not correct when there are multiple streams but that
-                // is not currently supported in this shim.
-                stream.addTrack(track);
-              }
-
-              // FIXME: look at direction.
-              if (self.localStreams.length > 0 &&
-                  self.localStreams[0].getTracks().length >= sdpMLineIndex) {
-                var localTrack;
-                if (kind === 'audio') {
-                  localTrack = self.localStreams[0].getAudioTracks()[0];
-                } else if (kind === 'video') {
-                  localTrack = self.localStreams[0].getVideoTracks()[0];
-                }
-                if (localTrack) {
-                  // add RTX
-                  if (browserDetails.version >= 15019 && kind === 'video') {
-                    sendEncodingParameters[0].rtx = {
-                      ssrc: (2 * sdpMLineIndex + 2) * 1001 + 1
-                    };
-                  }
-                  rtpSender = new RTCRtpSender(localTrack,
-                      transports.dtlsTransport);
-                }
-              }
-
-              self.transceivers[sdpMLineIndex] = {
-                iceGatherer: transports.iceGatherer,
-                iceTransport: transports.iceTransport,
-                dtlsTransport: transports.dtlsTransport,
-                localCapabilities: localCapabilities,
-                remoteCapabilities: remoteCapabilities,
-                rtpSender: rtpSender,
-                rtpReceiver: rtpReceiver,
-                kind: kind,
-                mid: mid,
-                cname: cname,
-                sendEncodingParameters: sendEncodingParameters,
-                recvEncodingParameters: recvEncodingParameters
-              };
-              // Start the RTCRtpReceiver now. The RTPSender is started in
-              // setLocalDescription.
-              self._transceive(self.transceivers[sdpMLineIndex],
-                  false,
-                  direction === 'sendrecv' || direction === 'sendonly');
-            } else if (description.type === 'answer' && !rejected) {
-              transceiver = self.transceivers[sdpMLineIndex];
-              iceGatherer = transceiver.iceGatherer;
-              iceTransport = transceiver.iceTransport;
-              dtlsTransport = transceiver.dtlsTransport;
-              rtpSender = transceiver.rtpSender;
-              rtpReceiver = transceiver.rtpReceiver;
-              sendEncodingParameters = transceiver.sendEncodingParameters;
-              localCapabilities = transceiver.localCapabilities;
-
-              self.transceivers[sdpMLineIndex].recvEncodingParameters =
-                  recvEncodingParameters;
-              self.transceivers[sdpMLineIndex].remoteCapabilities =
-                  remoteCapabilities;
-              self.transceivers[sdpMLineIndex].cname = cname;
-
-              if ((isIceLite || isComplete) && cands.length) {
-                iceTransport.setRemoteCandidates(cands);
-              }
-              if (!self.usingBundle || sdpMLineIndex === 0) {
-                iceTransport.start(iceGatherer, remoteIceParameters,
-                    'controlling');
-                dtlsTransport.start(remoteDtlsParameters);
-              }
-
-              self._transceive(transceiver,
-                  direction === 'sendrecv' || direction === 'recvonly',
-                  direction === 'sendrecv' || direction === 'sendonly');
-
-              if (rtpReceiver &&
-                  (direction === 'sendrecv' || direction === 'sendonly')) {
-                track = rtpReceiver.track;
-                receiverList.push([track, rtpReceiver]);
-                stream.addTrack(track);
-              } else {
-                // FIXME: actually the receiver should be created later.
-                delete transceiver.rtpReceiver;
-              }
-            }
-          });
-
-          this.remoteDescription = {
-            type: description.type,
-            sdp: description.sdp
-          };
-          switch (description.type) {
-            case 'offer':
-              this._updateSignalingState('have-remote-offer');
-              break;
-            case 'answer':
-              this._updateSignalingState('stable');
-              break;
-            default:
-              throw new TypeError('unsupported type "' + description.type +
-                  '"');
-          }
-          if (stream.getTracks().length) {
-            self.remoteStreams.push(stream);
-            window.setTimeout(function() {
-              var event = new Event('addstream');
-              event.stream = stream;
-              self.dispatchEvent(event);
-              if (self.onaddstream !== null) {
-                window.setTimeout(function() {
-                  self.onaddstream(event);
-                }, 0);
-              }
-
-              receiverList.forEach(function(item) {
-                var track = item[0];
-                var receiver = item[1];
-                var trackEvent = new Event('track');
-                trackEvent.track = track;
-                trackEvent.receiver = receiver;
-                trackEvent.streams = [stream];
-                self.dispatchEvent(trackEvent);
-                if (self.ontrack !== null) {
-                  window.setTimeout(function() {
-                    self.ontrack(trackEvent);
-                  }, 0);
-                }
-              });
-            }, 0);
-          }
-          if (arguments.length > 1 && typeof arguments[1] === 'function') {
-            window.setTimeout(arguments[1], 0);
-          }
-          return Promise.resolve();
-        };
-
-    window.RTCPeerConnection.prototype.close = function() {
-      this.transceivers.forEach(function(transceiver) {
-        /* not yet
-        if (transceiver.iceGatherer) {
-          transceiver.iceGatherer.close();
-        }
-        */
-        if (transceiver.iceTransport) {
-          transceiver.iceTransport.stop();
-        }
-        if (transceiver.dtlsTransport) {
-          transceiver.dtlsTransport.stop();
-        }
-        if (transceiver.rtpSender) {
-          transceiver.rtpSender.stop();
-        }
-        if (transceiver.rtpReceiver) {
-          transceiver.rtpReceiver.stop();
-        }
-      });
-      // FIXME: clean up tracks, local streams, remote streams, etc
-      this._updateSignalingState('closed');
-    };
-
-    // Update the signaling state.
-    window.RTCPeerConnection.prototype._updateSignalingState =
-        function(newState) {
-          this.signalingState = newState;
-          var event = new Event('signalingstatechange');
-          this.dispatchEvent(event);
-          if (this.onsignalingstatechange !== null) {
-            this.onsignalingstatechange(event);
-          }
-        };
-
-    // Determine whether to fire the negotiationneeded event.
-    window.RTCPeerConnection.prototype._maybeFireNegotiationNeeded =
-        function() {
-          // Fire away (for now).
-          var event = new Event('negotiationneeded');
-          this.dispatchEvent(event);
-          if (this.onnegotiationneeded !== null) {
-            this.onnegotiationneeded(event);
-          }
-        };
-
-    // Update the connection state.
-    window.RTCPeerConnection.prototype._updateConnectionState = function() {
-      var self = this;
-      var newState;
-      var states = {
-        'new': 0,
-        closed: 0,
-        connecting: 0,
-        checking: 0,
-        connected: 0,
-        completed: 0,
-        failed: 0
+  // Start the RTP Sender and Receiver for a transceiver.
+  RTCPeerConnection.prototype._transceive = function(transceiver,
+      send, recv) {
+    var params = getCommonCapabilities(transceiver.localCapabilities,
+        transceiver.remoteCapabilities);
+    if (send && transceiver.rtpSender) {
+      params.encodings = transceiver.sendEncodingParameters;
+      params.rtcp = {
+        cname: SDPUtils.localCName,
+        compound: transceiver.rtcpParameters.compound
       };
-      this.transceivers.forEach(function(transceiver) {
-        states[transceiver.iceTransport.state]++;
-        states[transceiver.dtlsTransport.state]++;
-      });
-      // ICETransport.completed and connected are the same for this purpose.
-      states.connected += states.completed;
-
-      newState = 'new';
-      if (states.failed > 0) {
-        newState = 'failed';
-      } else if (states.connecting > 0 || states.checking > 0) {
-        newState = 'connecting';
-      } else if (states.disconnected > 0) {
-        newState = 'disconnected';
-      } else if (states.new > 0) {
-        newState = 'new';
-      } else if (states.connected > 0 || states.completed > 0) {
-        newState = 'connected';
+      if (transceiver.recvEncodingParameters.length) {
+        params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc;
       }
-
-      if (newState !== self.iceConnectionState) {
-        self.iceConnectionState = newState;
-        var event = new Event('iceconnectionstatechange');
-        this.dispatchEvent(event);
-        if (this.oniceconnectionstatechange !== null) {
-          this.oniceconnectionstatechange(event);
-        }
-      }
-    };
-
-    window.RTCPeerConnection.prototype.createOffer = function() {
-      var self = this;
-      if (this._pendingOffer) {
-        throw new Error('createOffer called while there is a pending offer.');
-      }
-      var offerOptions;
-      if (arguments.length === 1 && typeof arguments[0] !== 'function') {
-        offerOptions = arguments[0];
-      } else if (arguments.length === 3) {
-        offerOptions = arguments[2];
-      }
-
-      var tracks = [];
-      var numAudioTracks = 0;
-      var numVideoTracks = 0;
-      // Default to sendrecv.
-      if (this.localStreams.length) {
-        numAudioTracks = this.localStreams[0].getAudioTracks().length;
-        numVideoTracks = this.localStreams[0].getVideoTracks().length;
-      }
-      // Determine number of audio and video tracks we need to send/recv.
-      if (offerOptions) {
-        // Reject Chrome legacy constraints.
-        if (offerOptions.mandatory || offerOptions.optional) {
-          throw new TypeError(
-              'Legacy mandatory/optional constraints not supported.');
-        }
-        if (offerOptions.offerToReceiveAudio !== undefined) {
-          numAudioTracks = offerOptions.offerToReceiveAudio;
-        }
-        if (offerOptions.offerToReceiveVideo !== undefined) {
-          numVideoTracks = offerOptions.offerToReceiveVideo;
-        }
-      }
-      if (this.localStreams.length) {
-        // Push local streams.
-        this.localStreams[0].getTracks().forEach(function(track) {
-          tracks.push({
-            kind: track.kind,
-            track: track,
-            wantReceive: track.kind === 'audio' ?
-                numAudioTracks > 0 : numVideoTracks > 0
-          });
-          if (track.kind === 'audio') {
-            numAudioTracks--;
-          } else if (track.kind === 'video') {
-            numVideoTracks--;
-          }
+      transceiver.rtpSender.send(params);
+    }
+    if (recv && transceiver.rtpReceiver) {
+      // remove RTX field in Edge 14942
+      if (transceiver.kind === 'video'
+          && transceiver.recvEncodingParameters
+          && edgeVersion < 15019) {
+        transceiver.recvEncodingParameters.forEach(function(p) {
+          delete p.rtx;
         });
       }
-      // Create M-lines for recvonly streams.
-      while (numAudioTracks > 0 || numVideoTracks > 0) {
-        if (numAudioTracks > 0) {
-          tracks.push({
-            kind: 'audio',
-            wantReceive: true
-          });
-          numAudioTracks--;
+      params.encodings = transceiver.recvEncodingParameters;
+      params.rtcp = {
+        cname: transceiver.rtcpParameters.cname,
+        compound: transceiver.rtcpParameters.compound
+      };
+      if (transceiver.sendEncodingParameters.length) {
+        params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc;
+      }
+      transceiver.rtpReceiver.receive(params);
+    }
+  };
+
+  RTCPeerConnection.prototype.setLocalDescription = function(description) {
+    var self = this;
+
+    if (!isActionAllowedInSignalingState('setLocalDescription',
+        description.type, this.signalingState)) {
+      var e = new Error('Can not set local ' + description.type +
+          ' in state ' + this.signalingState);
+      e.name = 'InvalidStateError';
+      if (arguments.length > 2 && typeof arguments[2] === 'function') {
+        window.setTimeout(arguments[2], 0, e);
+      }
+      return Promise.reject(e);
+    }
+
+    var sections;
+    var sessionpart;
+    if (description.type === 'offer') {
+      // FIXME: What was the purpose of this empty if statement?
+      // if (!this._pendingOffer) {
+      // } else {
+      if (this._pendingOffer) {
+        // VERY limited support for SDP munging. Limited to:
+        // * changing the order of codecs
+        sections = SDPUtils.splitSections(description.sdp);
+        sessionpart = sections.shift();
+        sections.forEach(function(mediaSection, sdpMLineIndex) {
+          var caps = SDPUtils.parseRtpParameters(mediaSection);
+          self._pendingOffer[sdpMLineIndex].localCapabilities = caps;
+        });
+        this.transceivers = this._pendingOffer;
+        delete this._pendingOffer;
+      }
+    } else if (description.type === 'answer') {
+      sections = SDPUtils.splitSections(self.remoteDescription.sdp);
+      sessionpart = sections.shift();
+      var isIceLite = SDPUtils.matchPrefix(sessionpart,
+          'a=ice-lite').length > 0;
+      sections.forEach(function(mediaSection, sdpMLineIndex) {
+        var transceiver = self.transceivers[sdpMLineIndex];
+        var iceGatherer = transceiver.iceGatherer;
+        var iceTransport = transceiver.iceTransport;
+        var dtlsTransport = transceiver.dtlsTransport;
+        var localCapabilities = transceiver.localCapabilities;
+        var remoteCapabilities = transceiver.remoteCapabilities;
+
+        var rejected = SDPUtils.isRejected(mediaSection);
+
+        if (!rejected && !transceiver.isDatachannel) {
+          var remoteIceParameters = SDPUtils.getIceParameters(
+              mediaSection, sessionpart);
+          var remoteDtlsParameters = SDPUtils.getDtlsParameters(
+              mediaSection, sessionpart);
+          if (isIceLite) {
+            remoteDtlsParameters.role = 'server';
+          }
+
+          if (!self.usingBundle || sdpMLineIndex === 0) {
+            iceTransport.start(iceGatherer, remoteIceParameters,
+                isIceLite ? 'controlling' : 'controlled');
+            dtlsTransport.start(remoteDtlsParameters);
+          }
+
+          // Calculate intersection of capabilities.
+          var params = getCommonCapabilities(localCapabilities,
+              remoteCapabilities);
+
+          // Start the RTCRtpSender. The RTCRtpReceiver for this
+          // transceiver has already been started in setRemoteDescription.
+          self._transceive(transceiver,
+              params.codecs.length > 0,
+              false);
         }
-        if (numVideoTracks > 0) {
-          tracks.push({
-            kind: 'video',
-            wantReceive: true
+      });
+    }
+
+    this.localDescription = {
+      type: description.type,
+      sdp: description.sdp
+    };
+    switch (description.type) {
+      case 'offer':
+        this._updateSignalingState('have-local-offer');
+        break;
+      case 'answer':
+        this._updateSignalingState('stable');
+        break;
+      default:
+        throw new TypeError('unsupported type "' + description.type +
+            '"');
+    }
+
+    // If a success callback was provided, emit ICE candidates after it
+    // has been executed. Otherwise, emit callback after the Promise is
+    // resolved.
+    var hasCallback = arguments.length > 1 &&
+      typeof arguments[1] === 'function';
+    if (hasCallback) {
+      var cb = arguments[1];
+      window.setTimeout(function() {
+        cb();
+        if (self.iceGatheringState === 'new') {
+          self.iceGatheringState = 'gathering';
+          self._emitGatheringStateChange();
+        }
+        self._emitBufferedCandidates();
+      }, 0);
+    }
+    var p = Promise.resolve();
+    p.then(function() {
+      if (!hasCallback) {
+        if (self.iceGatheringState === 'new') {
+          self.iceGatheringState = 'gathering';
+          self._emitGatheringStateChange();
+        }
+        // Usually candidates will be emitted earlier.
+        window.setTimeout(self._emitBufferedCandidates.bind(self), 500);
+      }
+    });
+    return p;
+  };
+
+  RTCPeerConnection.prototype.setRemoteDescription = function(description) {
+    var self = this;
+
+    if (!isActionAllowedInSignalingState('setRemoteDescription',
+        description.type, this.signalingState)) {
+      var e = new Error('Can not set remote ' + description.type +
+          ' in state ' + this.signalingState);
+      e.name = 'InvalidStateError';
+      if (arguments.length > 2 && typeof arguments[2] === 'function') {
+        window.setTimeout(arguments[2], 0, e);
+      }
+      return Promise.reject(e);
+    }
+
+    var streams = {};
+    var receiverList = [];
+    var sections = SDPUtils.splitSections(description.sdp);
+    var sessionpart = sections.shift();
+    var isIceLite = SDPUtils.matchPrefix(sessionpart,
+        'a=ice-lite').length > 0;
+    var usingBundle = SDPUtils.matchPrefix(sessionpart,
+        'a=group:BUNDLE ').length > 0;
+    this.usingBundle = usingBundle;
+    var iceOptions = SDPUtils.matchPrefix(sessionpart,
+        'a=ice-options:')[0];
+    if (iceOptions) {
+      this.canTrickleIceCandidates = iceOptions.substr(14).split(' ')
+          .indexOf('trickle') >= 0;
+    } else {
+      this.canTrickleIceCandidates = false;
+    }
+
+    sections.forEach(function(mediaSection, sdpMLineIndex) {
+      var lines = SDPUtils.splitLines(mediaSection);
+      var kind = SDPUtils.getKind(mediaSection);
+      var rejected = SDPUtils.isRejected(mediaSection);
+      var protocol = lines[0].substr(2).split(' ')[2];
+
+      var direction = SDPUtils.getDirection(mediaSection, sessionpart);
+      var remoteMsid = SDPUtils.parseMsid(mediaSection);
+
+      var mid = SDPUtils.getMid(mediaSection) || SDPUtils.generateIdentifier();
+
+      // Reject datachannels which are not implemented yet.
+      if (kind === 'application' && protocol === 'DTLS/SCTP') {
+        self.transceivers[sdpMLineIndex] = {
+          mid: mid,
+          isDatachannel: true
+        };
+        return;
+      }
+
+      var transceiver;
+      var iceGatherer;
+      var iceTransport;
+      var dtlsTransport;
+      var rtpReceiver;
+      var sendEncodingParameters;
+      var recvEncodingParameters;
+      var localCapabilities;
+
+      var track;
+      // FIXME: ensure the mediaSection has rtcp-mux set.
+      var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection);
+      var remoteIceParameters;
+      var remoteDtlsParameters;
+      if (!rejected) {
+        remoteIceParameters = SDPUtils.getIceParameters(mediaSection,
+            sessionpart);
+        remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection,
+            sessionpart);
+        remoteDtlsParameters.role = 'client';
+      }
+      recvEncodingParameters =
+          SDPUtils.parseRtpEncodingParameters(mediaSection);
+
+      var rtcpParameters = SDPUtils.parseRtcpParameters(mediaSection);
+
+      var isComplete = SDPUtils.matchPrefix(mediaSection,
+          'a=end-of-candidates', sessionpart).length > 0;
+      var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:')
+          .map(function(cand) {
+            return SDPUtils.parseCandidate(cand);
+          })
+          .filter(function(cand) {
+            return cand.component === '1' || cand.component === 1;
           });
-          numVideoTracks--;
+
+      // Check if we can use BUNDLE and dispose transports.
+      if ((description.type === 'offer' || description.type === 'answer') &&
+          !rejected && usingBundle && sdpMLineIndex > 0 &&
+          self.transceivers[sdpMLineIndex]) {
+        self._disposeIceAndDtlsTransports(sdpMLineIndex);
+        self.transceivers[sdpMLineIndex].iceGatherer =
+            self.transceivers[0].iceGatherer;
+        self.transceivers[sdpMLineIndex].iceTransport =
+            self.transceivers[0].iceTransport;
+        self.transceivers[sdpMLineIndex].dtlsTransport =
+            self.transceivers[0].dtlsTransport;
+        if (self.transceivers[sdpMLineIndex].rtpSender) {
+          self.transceivers[sdpMLineIndex].rtpSender.setTransport(
+              self.transceivers[0].dtlsTransport);
+        }
+        if (self.transceivers[sdpMLineIndex].rtpReceiver) {
+          self.transceivers[sdpMLineIndex].rtpReceiver.setTransport(
+              self.transceivers[0].dtlsTransport);
         }
       }
-      // reorder tracks
-      tracks = sortTracks(tracks);
+      if (description.type === 'offer' && !rejected) {
+        transceiver = self.transceivers[sdpMLineIndex] ||
+            self._createTransceiver(kind);
+        transceiver.mid = mid;
 
-      var sdp = SDPUtils.writeSessionBoilerplate();
-      var transceivers = [];
-      tracks.forEach(function(mline, sdpMLineIndex) {
-        // For each track, create an ice gatherer, ice transport,
-        // dtls transport, potentially rtpsender and rtpreceiver.
-        var track = mline.track;
-        var kind = mline.kind;
-        var mid = SDPUtils.generateIdentifier();
+        if (!transceiver.iceGatherer) {
+          transceiver.iceGatherer = usingBundle && sdpMLineIndex > 0 ?
+              self.transceivers[0].iceGatherer :
+              self._createIceGatherer(mid, sdpMLineIndex);
+        }
 
-        var transports = self.usingBundle && sdpMLineIndex > 0 ? {
-          iceGatherer: transceivers[0].iceGatherer,
-          iceTransport: transceivers[0].iceTransport,
-          dtlsTransport: transceivers[0].dtlsTransport
-        } : self._createIceAndDtlsTransports(mid, sdpMLineIndex);
+        if (isComplete && cands.length &&
+            (!usingBundle || sdpMLineIndex === 0)) {
+          transceiver.iceTransport.setRemoteCandidates(cands);
+        }
 
-        var localCapabilities = RTCRtpSender.getCapabilities(kind);
+        localCapabilities = window.RTCRtpReceiver.getCapabilities(kind);
+
         // filter RTX until additional stuff needed for RTX is implemented
         // in adapter.js
-        if (browserDetails.version < 15019) {
+        if (edgeVersion < 15019) {
           localCapabilities.codecs = localCapabilities.codecs.filter(
               function(codec) {
                 return codec.name !== 'rtx';
               });
         }
-        localCapabilities.codecs.forEach(function(codec) {
-          // work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552
-          // by adding level-asymmetry-allowed=1
-          if (codec.name === 'H264' &&
-              codec.parameters['level-asymmetry-allowed'] === undefined) {
-            codec.parameters['level-asymmetry-allowed'] = '1';
-          }
-        });
 
-        var rtpSender;
-        var rtpReceiver;
-
-        // generate an ssrc now, to be used later in rtpSender.send
-        var sendEncodingParameters = [{
-          ssrc: (2 * sdpMLineIndex + 1) * 1001
+        sendEncodingParameters = [{
+          ssrc: (2 * sdpMLineIndex + 2) * 1001
         }];
-        if (track) {
-          // add RTX
-          if (browserDetails.version >= 15019 && kind === 'video') {
-            sendEncodingParameters[0].rtx = {
-              ssrc: (2 * sdpMLineIndex + 1) * 1001 + 1
-            };
-          }
-          rtpSender = new RTCRtpSender(track, transports.dtlsTransport);
-        }
 
-        if (mline.wantReceive) {
-          rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind);
-        }
+        if (direction === 'sendrecv' || direction === 'sendonly') {
+          rtpReceiver = new window.RTCRtpReceiver(transceiver.dtlsTransport,
+              kind);
 
-        transceivers[sdpMLineIndex] = {
-          iceGatherer: transports.iceGatherer,
-          iceTransport: transports.iceTransport,
-          dtlsTransport: transports.dtlsTransport,
-          localCapabilities: localCapabilities,
-          remoteCapabilities: null,
-          rtpSender: rtpSender,
-          rtpReceiver: rtpReceiver,
-          kind: kind,
-          mid: mid,
-          sendEncodingParameters: sendEncodingParameters,
-          recvEncodingParameters: null
-        };
-      });
-      if (this.usingBundle) {
-        sdp += 'a=group:BUNDLE ' + transceivers.map(function(t) {
-          return t.mid;
-        }).join(' ') + '\r\n';
-      }
-      tracks.forEach(function(mline, sdpMLineIndex) {
-        var transceiver = transceivers[sdpMLineIndex];
-        sdp += SDPUtils.writeMediaSection(transceiver,
-            transceiver.localCapabilities, 'offer', self.localStreams[0]);
-      });
-
-      this._pendingOffer = transceivers;
-      var desc = new RTCSessionDescription({
-        type: 'offer',
-        sdp: sdp
-      });
-      if (arguments.length && typeof arguments[0] === 'function') {
-        window.setTimeout(arguments[0], 0, desc);
-      }
-      return Promise.resolve(desc);
-    };
-
-    window.RTCPeerConnection.prototype.createAnswer = function() {
-      var self = this;
-
-      var sdp = SDPUtils.writeSessionBoilerplate();
-      if (this.usingBundle) {
-        sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) {
-          return t.mid;
-        }).join(' ') + '\r\n';
-      }
-      this.transceivers.forEach(function(transceiver) {
-        if (transceiver.isDatachannel) {
-          sdp += 'm=application 0 DTLS/SCTP 5000\r\n' +
-              'c=IN IP4 0.0.0.0\r\n' +
-              'a=mid:' + transceiver.mid + '\r\n';
-          return;
-        }
-        // Calculate intersection of capabilities.
-        var commonCapabilities = self._getCommonCapabilities(
-            transceiver.localCapabilities,
-            transceiver.remoteCapabilities);
-
-        sdp += SDPUtils.writeMediaSection(transceiver, commonCapabilities,
-            'answer', self.localStreams[0]);
-      });
-
-      var desc = new RTCSessionDescription({
-        type: 'answer',
-        sdp: sdp
-      });
-      if (arguments.length && typeof arguments[0] === 'function') {
-        window.setTimeout(arguments[0], 0, desc);
-      }
-      return Promise.resolve(desc);
-    };
-
-    window.RTCPeerConnection.prototype.addIceCandidate = function(candidate) {
-      if (!candidate) {
-        for (var j = 0; j < this.transceivers.length; j++) {
-          this.transceivers[j].iceTransport.addRemoteCandidate({});
-          if (this.usingBundle) {
-            return Promise.resolve();
-          }
-        }
-      } else {
-        var mLineIndex = candidate.sdpMLineIndex;
-        if (candidate.sdpMid) {
-          for (var i = 0; i < this.transceivers.length; i++) {
-            if (this.transceivers[i].mid === candidate.sdpMid) {
-              mLineIndex = i;
-              break;
+          track = rtpReceiver.track;
+          // FIXME: does not work with Plan B.
+          if (remoteMsid) {
+            if (!streams[remoteMsid.stream]) {
+              streams[remoteMsid.stream] = new window.MediaStream();
+              Object.defineProperty(streams[remoteMsid.stream], 'id', {
+                get: function() {
+                  return remoteMsid.stream;
+                }
+              });
             }
-          }
-        }
-        var transceiver = this.transceivers[mLineIndex];
-        if (transceiver) {
-          var cand = Object.keys(candidate.candidate).length > 0 ?
-              SDPUtils.parseCandidate(candidate.candidate) : {};
-          // Ignore Chrome's invalid candidates since Edge does not like them.
-          if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) {
-            return Promise.resolve();
-          }
-          // Ignore RTCP candidates, we assume RTCP-MUX.
-          if (cand.component !== '1') {
-            return Promise.resolve();
-          }
-          transceiver.iceTransport.addRemoteCandidate(cand);
-
-          // update the remoteDescription.
-          var sections = SDPUtils.splitSections(this.remoteDescription.sdp);
-          sections[mLineIndex + 1] += (cand.type ? candidate.candidate.trim()
-              : 'a=end-of-candidates') + '\r\n';
-          this.remoteDescription.sdp = sections.join('');
-        }
-      }
-      if (arguments.length > 1 && typeof arguments[1] === 'function') {
-        window.setTimeout(arguments[1], 0);
-      }
-      return Promise.resolve();
-    };
-
-    window.RTCPeerConnection.prototype.getStats = function() {
-      var promises = [];
-      this.transceivers.forEach(function(transceiver) {
-        ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport',
-            'dtlsTransport'].forEach(function(method) {
-              if (transceiver[method]) {
-                promises.push(transceiver[method].getStats());
+            Object.defineProperty(track, 'id', {
+              get: function() {
+                return remoteMsid.track;
               }
             });
-      });
-      var cb = arguments.length > 1 && typeof arguments[1] === 'function' &&
-          arguments[1];
-      var fixStatsType = function(stat) {
-        return {
-          inboundrtp: 'inbound-rtp',
-          outboundrtp: 'outbound-rtp',
-          candidatepair: 'candidate-pair',
-          localcandidate: 'local-candidate',
-          remotecandidate: 'remote-candidate'
-        }[stat.type] || stat.type;
-      };
-      return new Promise(function(resolve) {
-        // shim getStats with maplike support
-        var results = new Map();
-        Promise.all(promises).then(function(res) {
-          res.forEach(function(result) {
-            Object.keys(result).forEach(function(id) {
-              result[id].type = fixStatsType(result[id]);
-              results.set(id, result[id]);
-            });
-          });
-          if (cb) {
-            window.setTimeout(cb, 0, results);
+            streams[remoteMsid.stream].addTrack(track);
+            receiverList.push([track, rtpReceiver,
+              streams[remoteMsid.stream]]);
+          } else {
+            if (!streams.default) {
+              streams.default = new window.MediaStream();
+            }
+            streams.default.addTrack(track);
+            receiverList.push([track, rtpReceiver, streams.default]);
           }
-          resolve(results);
-        });
-      });
-    };
-  }
-};
+        }
 
-// Expose public methods.
-module.exports = {
-  shimPeerConnection: edgeShim.shimPeerConnection,
-  shimGetUserMedia: require('./getusermedia')
-};
+        transceiver.localCapabilities = localCapabilities;
+        transceiver.remoteCapabilities = remoteCapabilities;
+        transceiver.rtpReceiver = rtpReceiver;
+        transceiver.rtcpParameters = rtcpParameters;
+        transceiver.sendEncodingParameters = sendEncodingParameters;
+        transceiver.recvEncodingParameters = recvEncodingParameters;
 
-},{"../utils":10,"./getusermedia":6,"sdp":1}],6:[function(require,module,exports){
-/*
- *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
- *
- *  Use of this source code is governed by a BSD-style license
- *  that can be found in the LICENSE file in the root of the source
- *  tree.
- */
- /* eslint-env node */
-'use strict';
+        // Start the RTCRtpReceiver now. The RTPSender is started in
+        // setLocalDescription.
+        self._transceive(self.transceivers[sdpMLineIndex],
+            false,
+            direction === 'sendrecv' || direction === 'sendonly');
+      } else if (description.type === 'answer' && !rejected) {
+        transceiver = self.transceivers[sdpMLineIndex];
+        iceGatherer = transceiver.iceGatherer;
+        iceTransport = transceiver.iceTransport;
+        dtlsTransport = transceiver.dtlsTransport;
+        rtpReceiver = transceiver.rtpReceiver;
+        sendEncodingParameters = transceiver.sendEncodingParameters;
+        localCapabilities = transceiver.localCapabilities;
 
-// Expose public methods.
-module.exports = function() {
-  var shimError_ = function(e) {
-    return {
-      name: {PermissionDeniedError: 'NotAllowedError'}[e.name] || e.name,
-      message: e.message,
-      constraint: e.constraint,
-      toString: function() {
-        return this.name;
+        self.transceivers[sdpMLineIndex].recvEncodingParameters =
+            recvEncodingParameters;
+        self.transceivers[sdpMLineIndex].remoteCapabilities =
+            remoteCapabilities;
+        self.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters;
+
+        if (!usingBundle || sdpMLineIndex === 0) {
+          if ((isIceLite || isComplete) && cands.length) {
+            iceTransport.setRemoteCandidates(cands);
+          }
+          iceTransport.start(iceGatherer, remoteIceParameters,
+              'controlling');
+          dtlsTransport.start(remoteDtlsParameters);
+        }
+
+        self._transceive(transceiver,
+            direction === 'sendrecv' || direction === 'recvonly',
+            direction === 'sendrecv' || direction === 'sendonly');
+
+        if (rtpReceiver &&
+            (direction === 'sendrecv' || direction === 'sendonly')) {
+          track = rtpReceiver.track;
+          if (remoteMsid) {
+            if (!streams[remoteMsid.stream]) {
+              streams[remoteMsid.stream] = new window.MediaStream();
+            }
+            streams[remoteMsid.stream].addTrack(track);
+            receiverList.push([track, rtpReceiver, streams[remoteMsid.stream]]);
+          } else {
+            if (!streams.default) {
+              streams.default = new window.MediaStream();
+            }
+            streams.default.addTrack(track);
+            receiverList.push([track, rtpReceiver, streams.default]);
+          }
+        } else {
+          // FIXME: actually the receiver should be created later.
+          delete transceiver.rtpReceiver;
+        }
       }
+    });
+
+    this.remoteDescription = {
+      type: description.type,
+      sdp: description.sdp
     };
+    switch (description.type) {
+      case 'offer':
+        this._updateSignalingState('have-remote-offer');
+        break;
+      case 'answer':
+        this._updateSignalingState('stable');
+        break;
+      default:
+        throw new TypeError('unsupported type "' + description.type +
+            '"');
+    }
+    Object.keys(streams).forEach(function(sid) {
+      var stream = streams[sid];
+      if (stream.getTracks().length) {
+        self.remoteStreams.push(stream);
+        var event = new Event('addstream');
+        event.stream = stream;
+        self.dispatchEvent(event);
+        if (self.onaddstream !== null) {
+          window.setTimeout(function() {
+            self.onaddstream(event);
+          }, 0);
+        }
+
+        receiverList.forEach(function(item) {
+          var track = item[0];
+          var receiver = item[1];
+          if (stream.id !== item[2].id) {
+            return;
+          }
+          var trackEvent = new Event('track');
+          trackEvent.track = track;
+          trackEvent.receiver = receiver;
+          trackEvent.streams = [stream];
+          self.dispatchEvent(trackEvent);
+          if (self.ontrack !== null) {
+            window.setTimeout(function() {
+              self.ontrack(trackEvent);
+            }, 0);
+          }
+        });
+      }
+    });
+
+    // check whether addIceCandidate({}) was called within four seconds after
+    // setRemoteDescription.
+    window.setTimeout(function() {
+      if (!(self && self.transceivers)) {
+        return;
+      }
+      self.transceivers.forEach(function(transceiver) {
+        if (transceiver.iceTransport &&
+            transceiver.iceTransport.state === 'new' &&
+            transceiver.iceTransport.getRemoteCandidates().length > 0) {
+          console.warn('Timeout for addRemoteCandidate. Consider sending ' +
+              'an end-of-candidates notification');
+          transceiver.iceTransport.addRemoteCandidate({});
+        }
+      });
+    }, 4000);
+
+    if (arguments.length > 1 && typeof arguments[1] === 'function') {
+      window.setTimeout(arguments[1], 0);
+    }
+    return Promise.resolve();
   };
 
-  // getUserMedia error shim.
-  var origGetUserMedia = navigator.mediaDevices.getUserMedia.
-      bind(navigator.mediaDevices);
-  navigator.mediaDevices.getUserMedia = function(c) {
-    return origGetUserMedia(c).catch(function(e) {
-      return Promise.reject(shimError_(e));
+  RTCPeerConnection.prototype.close = function() {
+    this.transceivers.forEach(function(transceiver) {
+      /* not yet
+      if (transceiver.iceGatherer) {
+        transceiver.iceGatherer.close();
+      }
+      */
+      if (transceiver.iceTransport) {
+        transceiver.iceTransport.stop();
+      }
+      if (transceiver.dtlsTransport) {
+        transceiver.dtlsTransport.stop();
+      }
+      if (transceiver.rtpSender) {
+        transceiver.rtpSender.stop();
+      }
+      if (transceiver.rtpReceiver) {
+        transceiver.rtpReceiver.stop();
+      }
+    });
+    // FIXME: clean up tracks, local streams, remote streams, etc
+    this._updateSignalingState('closed');
+  };
+
+  // Update the signaling state.
+  RTCPeerConnection.prototype._updateSignalingState = function(newState) {
+    this.signalingState = newState;
+    var event = new Event('signalingstatechange');
+    this.dispatchEvent(event);
+    if (this.onsignalingstatechange !== null) {
+      this.onsignalingstatechange(event);
+    }
+  };
+
+  // Determine whether to fire the negotiationneeded event.
+  RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function() {
+    var self = this;
+    if (this.signalingState !== 'stable' || this.needNegotiation === true) {
+      return;
+    }
+    this.needNegotiation = true;
+    window.setTimeout(function() {
+      if (self.needNegotiation === false) {
+        return;
+      }
+      self.needNegotiation = false;
+      var event = new Event('negotiationneeded');
+      self.dispatchEvent(event);
+      if (self.onnegotiationneeded !== null) {
+        self.onnegotiationneeded(event);
+      }
+    }, 0);
+  };
+
+  // Update the connection state.
+  RTCPeerConnection.prototype._updateConnectionState = function() {
+    var self = this;
+    var newState;
+    var states = {
+      'new': 0,
+      closed: 0,
+      connecting: 0,
+      checking: 0,
+      connected: 0,
+      completed: 0,
+      disconnected: 0,
+      failed: 0
+    };
+    this.transceivers.forEach(function(transceiver) {
+      states[transceiver.iceTransport.state]++;
+      states[transceiver.dtlsTransport.state]++;
+    });
+    // ICETransport.completed and connected are the same for this purpose.
+    states.connected += states.completed;
+
+    newState = 'new';
+    if (states.failed > 0) {
+      newState = 'failed';
+    } else if (states.connecting > 0 || states.checking > 0) {
+      newState = 'connecting';
+    } else if (states.disconnected > 0) {
+      newState = 'disconnected';
+    } else if (states.new > 0) {
+      newState = 'new';
+    } else if (states.connected > 0 || states.completed > 0) {
+      newState = 'connected';
+    }
+
+    if (newState !== self.iceConnectionState) {
+      self.iceConnectionState = newState;
+      var event = new Event('iceconnectionstatechange');
+      this.dispatchEvent(event);
+      if (this.oniceconnectionstatechange !== null) {
+        this.oniceconnectionstatechange(event);
+      }
+    }
+  };
+
+  RTCPeerConnection.prototype.createOffer = function() {
+    var self = this;
+    if (this._pendingOffer) {
+      throw new Error('createOffer called while there is a pending offer.');
+    }
+    var offerOptions;
+    if (arguments.length === 1 && typeof arguments[0] !== 'function') {
+      offerOptions = arguments[0];
+    } else if (arguments.length === 3) {
+      offerOptions = arguments[2];
+    }
+
+    var numAudioTracks = this.transceivers.filter(function(t) {
+      return t.kind === 'audio';
+    }).length;
+    var numVideoTracks = this.transceivers.filter(function(t) {
+      return t.kind === 'video';
+    }).length;
+
+    // Determine number of audio and video tracks we need to send/recv.
+    if (offerOptions) {
+      // Reject Chrome legacy constraints.
+      if (offerOptions.mandatory || offerOptions.optional) {
+        throw new TypeError(
+            'Legacy mandatory/optional constraints not supported.');
+      }
+      if (offerOptions.offerToReceiveAudio !== undefined) {
+        if (offerOptions.offerToReceiveAudio === true) {
+          numAudioTracks = 1;
+        } else if (offerOptions.offerToReceiveAudio === false) {
+          numAudioTracks = 0;
+        } else {
+          numAudioTracks = offerOptions.offerToReceiveAudio;
+        }
+      }
+      if (offerOptions.offerToReceiveVideo !== undefined) {
+        if (offerOptions.offerToReceiveVideo === true) {
+          numVideoTracks = 1;
+        } else if (offerOptions.offerToReceiveVideo === false) {
+          numVideoTracks = 0;
+        } else {
+          numVideoTracks = offerOptions.offerToReceiveVideo;
+        }
+      }
+    }
+
+    this.transceivers.forEach(function(transceiver) {
+      if (transceiver.kind === 'audio') {
+        numAudioTracks--;
+        if (numAudioTracks < 0) {
+          transceiver.wantReceive = false;
+        }
+      } else if (transceiver.kind === 'video') {
+        numVideoTracks--;
+        if (numVideoTracks < 0) {
+          transceiver.wantReceive = false;
+        }
+      }
+    });
+
+    // Create M-lines for recvonly streams.
+    while (numAudioTracks > 0 || numVideoTracks > 0) {
+      if (numAudioTracks > 0) {
+        this._createTransceiver('audio');
+        numAudioTracks--;
+      }
+      if (numVideoTracks > 0) {
+        this._createTransceiver('video');
+        numVideoTracks--;
+      }
+    }
+    // reorder tracks
+    var transceivers = sortTracks(this.transceivers);
+
+    var sdp = SDPUtils.writeSessionBoilerplate(this._sdpSessionId);
+    transceivers.forEach(function(transceiver, sdpMLineIndex) {
+      // For each track, create an ice gatherer, ice transport,
+      // dtls transport, potentially rtpsender and rtpreceiver.
+      var track = transceiver.track;
+      var kind = transceiver.kind;
+      var mid = SDPUtils.generateIdentifier();
+      transceiver.mid = mid;
+
+      if (!transceiver.iceGatherer) {
+        transceiver.iceGatherer = self.usingBundle && sdpMLineIndex > 0 ?
+            transceivers[0].iceGatherer :
+            self._createIceGatherer(mid, sdpMLineIndex);
+      }
+
+      var localCapabilities = window.RTCRtpSender.getCapabilities(kind);
+      // filter RTX until additional stuff needed for RTX is implemented
+      // in adapter.js
+      if (edgeVersion < 15019) {
+        localCapabilities.codecs = localCapabilities.codecs.filter(
+            function(codec) {
+              return codec.name !== 'rtx';
+            });
+      }
+      localCapabilities.codecs.forEach(function(codec) {
+        // work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552
+        // by adding level-asymmetry-allowed=1
+        if (codec.name === 'H264' &&
+            codec.parameters['level-asymmetry-allowed'] === undefined) {
+          codec.parameters['level-asymmetry-allowed'] = '1';
+        }
+      });
+
+      // generate an ssrc now, to be used later in rtpSender.send
+      var sendEncodingParameters = [{
+        ssrc: (2 * sdpMLineIndex + 1) * 1001
+      }];
+      if (track) {
+        // add RTX
+        if (edgeVersion >= 15019 && kind === 'video') {
+          sendEncodingParameters[0].rtx = {
+            ssrc: (2 * sdpMLineIndex + 1) * 1001 + 1
+          };
+        }
+      }
+
+      if (transceiver.wantReceive) {
+        transceiver.rtpReceiver = new window.RTCRtpReceiver(
+          transceiver.dtlsTransport,
+          kind
+        );
+      }
+
+      transceiver.localCapabilities = localCapabilities;
+      transceiver.sendEncodingParameters = sendEncodingParameters;
+    });
+
+    // always offer BUNDLE and dispose on return if not supported.
+    if (this._config.bundlePolicy !== 'max-compat') {
+      sdp += 'a=group:BUNDLE ' + transceivers.map(function(t) {
+        return t.mid;
+      }).join(' ') + '\r\n';
+    }
+    sdp += 'a=ice-options:trickle\r\n';
+
+    transceivers.forEach(function(transceiver, sdpMLineIndex) {
+      sdp += SDPUtils.writeMediaSection(transceiver,
+          transceiver.localCapabilities, 'offer', transceiver.stream);
+      sdp += 'a=rtcp-rsize\r\n';
+    });
+
+    this._pendingOffer = transceivers;
+    var desc = new window.RTCSessionDescription({
+      type: 'offer',
+      sdp: sdp
+    });
+    if (arguments.length && typeof arguments[0] === 'function') {
+      window.setTimeout(arguments[0], 0, desc);
+    }
+    return Promise.resolve(desc);
+  };
+
+  RTCPeerConnection.prototype.createAnswer = function() {
+    var sdp = SDPUtils.writeSessionBoilerplate(this._sdpSessionId);
+    if (this.usingBundle) {
+      sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) {
+        return t.mid;
+      }).join(' ') + '\r\n';
+    }
+    this.transceivers.forEach(function(transceiver, sdpMLineIndex) {
+      if (transceiver.isDatachannel) {
+        sdp += 'm=application 0 DTLS/SCTP 5000\r\n' +
+            'c=IN IP4 0.0.0.0\r\n' +
+            'a=mid:' + transceiver.mid + '\r\n';
+        return;
+      }
+
+      // FIXME: look at direction.
+      if (transceiver.stream) {
+        var localTrack;
+        if (transceiver.kind === 'audio') {
+          localTrack = transceiver.stream.getAudioTracks()[0];
+        } else if (transceiver.kind === 'video') {
+          localTrack = transceiver.stream.getVideoTracks()[0];
+        }
+        if (localTrack) {
+          // add RTX
+          if (edgeVersion >= 15019 && transceiver.kind === 'video') {
+            transceiver.sendEncodingParameters[0].rtx = {
+              ssrc: (2 * sdpMLineIndex + 2) * 1001 + 1
+            };
+          }
+        }
+      }
+
+      // Calculate intersection of capabilities.
+      var commonCapabilities = getCommonCapabilities(
+          transceiver.localCapabilities,
+          transceiver.remoteCapabilities);
+
+      var hasRtx = commonCapabilities.codecs.filter(function(c) {
+        return c.name.toLowerCase() === 'rtx';
+      }).length;
+      if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) {
+        delete transceiver.sendEncodingParameters[0].rtx;
+      }
+
+      sdp += SDPUtils.writeMediaSection(transceiver, commonCapabilities,
+          'answer', transceiver.stream);
+      if (transceiver.rtcpParameters &&
+          transceiver.rtcpParameters.reducedSize) {
+        sdp += 'a=rtcp-rsize\r\n';
+      }
+    });
+
+    var desc = new window.RTCSessionDescription({
+      type: 'answer',
+      sdp: sdp
+    });
+    if (arguments.length && typeof arguments[0] === 'function') {
+      window.setTimeout(arguments[0], 0, desc);
+    }
+    return Promise.resolve(desc);
+  };
+
+  RTCPeerConnection.prototype.addIceCandidate = function(candidate) {
+    if (!candidate) {
+      for (var j = 0; j < this.transceivers.length; j++) {
+        this.transceivers[j].iceTransport.addRemoteCandidate({});
+        if (this.usingBundle) {
+          return Promise.resolve();
+        }
+      }
+    } else {
+      var mLineIndex = candidate.sdpMLineIndex;
+      if (candidate.sdpMid) {
+        for (var i = 0; i < this.transceivers.length; i++) {
+          if (this.transceivers[i].mid === candidate.sdpMid) {
+            mLineIndex = i;
+            break;
+          }
+        }
+      }
+      var transceiver = this.transceivers[mLineIndex];
+      if (transceiver) {
+        var cand = Object.keys(candidate.candidate).length > 0 ?
+            SDPUtils.parseCandidate(candidate.candidate) : {};
+        // Ignore Chrome's invalid candidates since Edge does not like them.
+        if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) {
+          return Promise.resolve();
+        }
+        // Ignore RTCP candidates, we assume RTCP-MUX.
+        if (cand.component &&
+            !(cand.component === '1' || cand.component === 1)) {
+          return Promise.resolve();
+        }
+        transceiver.iceTransport.addRemoteCandidate(cand);
+
+        // update the remoteDescription.
+        var sections = SDPUtils.splitSections(this.remoteDescription.sdp);
+        sections[mLineIndex + 1] += (cand.type ? candidate.candidate.trim()
+            : 'a=end-of-candidates') + '\r\n';
+        this.remoteDescription.sdp = sections.join('');
+      }
+    }
+    if (arguments.length > 1 && typeof arguments[1] === 'function') {
+      window.setTimeout(arguments[1], 0);
+    }
+    return Promise.resolve();
+  };
+
+  RTCPeerConnection.prototype.getStats = function() {
+    var promises = [];
+    this.transceivers.forEach(function(transceiver) {
+      ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport',
+        'dtlsTransport'].forEach(function(method) {
+          if (transceiver[method]) {
+            promises.push(transceiver[method].getStats());
+          }
+        });
+    });
+    var cb = arguments.length > 1 && typeof arguments[1] === 'function' &&
+        arguments[1];
+    var fixStatsType = function(stat) {
+      return {
+        inboundrtp: 'inbound-rtp',
+        outboundrtp: 'outbound-rtp',
+        candidatepair: 'candidate-pair',
+        localcandidate: 'local-candidate',
+        remotecandidate: 'remote-candidate'
+      }[stat.type] || stat.type;
+    };
+    return new Promise(function(resolve) {
+      // shim getStats with maplike support
+      var results = new Map();
+      Promise.all(promises).then(function(res) {
+        res.forEach(function(result) {
+          Object.keys(result).forEach(function(id) {
+            result[id].type = fixStatsType(result[id]);
+            results.set(id, result[id]);
+          });
+        });
+        if (cb) {
+          window.setTimeout(cb, 0, results);
+        }
+        resolve(results);
+      });
     });
   };
+  return RTCPeerConnection;
 };
 
-},{}],7:[function(require,module,exports){
+},{"sdp":1}],9:[function(require,module,exports){
 /*
  *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
  *
@@ -2478,10 +3120,10 @@
  /* eslint-env node */
 'use strict';
 
-var browserDetails = require('../utils').browserDetails;
+var utils = require('../utils');
 
 var firefoxShim = {
-  shimOnTrack: function() {
+  shimOnTrack: function(window) {
     if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in
         window.RTCPeerConnection.prototype)) {
       Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
@@ -2508,7 +3150,7 @@
     }
   },
 
-  shimSourceObject: function() {
+  shimSourceObject: function(window) {
     // Firefox has supported mozSrcObject since FF22, unprefixed in 42.
     if (typeof window === 'object') {
       if (window.HTMLMediaElement &&
@@ -2526,7 +3168,9 @@
     }
   },
 
-  shimPeerConnection: function() {
+  shimPeerConnection: function(window) {
+    var browserDetails = utils.detectBrowser(window);
+
     if (typeof window !== 'object' || !(window.RTCPeerConnection ||
         window.mozRTCPeerConnection)) {
       return; // probably media.peerconnection.enabled=false in about:config
@@ -2559,38 +3203,40 @@
             pcConfig.iceServers = newIceServers;
           }
         }
-        return new mozRTCPeerConnection(pcConfig, pcConstraints);
+        return new window.mozRTCPeerConnection(pcConfig, pcConstraints);
       };
-      window.RTCPeerConnection.prototype = mozRTCPeerConnection.prototype;
+      window.RTCPeerConnection.prototype =
+          window.mozRTCPeerConnection.prototype;
 
       // wrap static methods. Currently just generateCertificate.
-      if (mozRTCPeerConnection.generateCertificate) {
+      if (window.mozRTCPeerConnection.generateCertificate) {
         Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
           get: function() {
-            return mozRTCPeerConnection.generateCertificate;
+            return window.mozRTCPeerConnection.generateCertificate;
           }
         });
       }
 
-      window.RTCSessionDescription = mozRTCSessionDescription;
-      window.RTCIceCandidate = mozRTCIceCandidate;
+      window.RTCSessionDescription = window.mozRTCSessionDescription;
+      window.RTCIceCandidate = window.mozRTCIceCandidate;
     }
 
     // shim away need for obsolete RTCIceCandidate/RTCSessionDescription.
     ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
         .forEach(function(method) {
-          var nativeMethod = RTCPeerConnection.prototype[method];
-          RTCPeerConnection.prototype[method] = function() {
+          var nativeMethod = window.RTCPeerConnection.prototype[method];
+          window.RTCPeerConnection.prototype[method] = function() {
             arguments[0] = new ((method === 'addIceCandidate') ?
-                RTCIceCandidate : RTCSessionDescription)(arguments[0]);
+                window.RTCIceCandidate :
+                window.RTCSessionDescription)(arguments[0]);
             return nativeMethod.apply(this, arguments);
           };
         });
 
     // support for addIceCandidate(null or undefined)
     var nativeAddIceCandidate =
-        RTCPeerConnection.prototype.addIceCandidate;
-    RTCPeerConnection.prototype.addIceCandidate = function() {
+        window.RTCPeerConnection.prototype.addIceCandidate;
+    window.RTCPeerConnection.prototype.addIceCandidate = function() {
       if (!arguments[0]) {
         if (arguments[1]) {
           arguments[1].apply(null);
@@ -2618,8 +3264,12 @@
       remotecandidate: 'remote-candidate'
     };
 
-    var nativeGetStats = RTCPeerConnection.prototype.getStats;
-    RTCPeerConnection.prototype.getStats = function(selector, onSucc, onErr) {
+    var nativeGetStats = window.RTCPeerConnection.prototype.getStats;
+    window.RTCPeerConnection.prototype.getStats = function(
+      selector,
+      onSucc,
+      onErr
+    ) {
       return nativeGetStats.apply(this, [selector || null])
         .then(function(stats) {
           if (browserDetails.version < 48) {
@@ -2659,7 +3309,7 @@
   shimGetUserMedia: require('./getusermedia')
 };
 
-},{"../utils":10,"./getusermedia":8}],8:[function(require,module,exports){
+},{"../utils":12,"./getusermedia":10}],10:[function(require,module,exports){
 /*
  *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
  *
@@ -2670,16 +3320,22 @@
  /* eslint-env node */
 'use strict';
 
-var logging = require('../utils').log;
-var browserDetails = require('../utils').browserDetails;
+var utils = require('../utils');
+var logging = utils.log;
 
 // Expose public methods.
-module.exports = function() {
+module.exports = function(window) {
+  var browserDetails = utils.detectBrowser(window);
+  var navigator = window && window.navigator;
+  var MediaStreamTrack = window && window.MediaStreamTrack;
+
   var shimError_ = function(e) {
     return {
       name: {
-        SecurityError: 'NotAllowedError',
-        PermissionDeniedError: 'NotAllowedError'
+        InternalError: 'NotReadableError',
+        NotSupportedError: 'TypeError',
+        PermissionDeniedError: 'NotAllowedError',
+        SecurityError: 'NotAllowedError'
       }[e.name] || e.name,
       message: {
         'The operation is insecure.': 'The request is not allowed by the ' +
@@ -2811,6 +3467,48 @@
       });
     };
   }
+  if (!(browserDetails.version > 55 &&
+      'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) {
+    var remap = function(obj, a, b) {
+      if (a in obj && !(b in obj)) {
+        obj[b] = obj[a];
+        delete obj[a];
+      }
+    };
+
+    var nativeGetUserMedia = navigator.mediaDevices.getUserMedia.
+        bind(navigator.mediaDevices);
+    navigator.mediaDevices.getUserMedia = function(c) {
+      if (typeof c === 'object' && typeof c.audio === 'object') {
+        c = JSON.parse(JSON.stringify(c));
+        remap(c.audio, 'autoGainControl', 'mozAutoGainControl');
+        remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression');
+      }
+      return nativeGetUserMedia(c);
+    };
+
+    if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) {
+      var nativeGetSettings = MediaStreamTrack.prototype.getSettings;
+      MediaStreamTrack.prototype.getSettings = function() {
+        var obj = nativeGetSettings.apply(this, arguments);
+        remap(obj, 'mozAutoGainControl', 'autoGainControl');
+        remap(obj, 'mozNoiseSuppression', 'noiseSuppression');
+        return obj;
+      };
+    }
+
+    if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) {
+      var nativeApplyConstraints = MediaStreamTrack.prototype.applyConstraints;
+      MediaStreamTrack.prototype.applyConstraints = function(c) {
+        if (this.kind === 'audio' && typeof c === 'object') {
+          c = JSON.parse(JSON.stringify(c));
+          remap(c, 'autoGainControl', 'mozAutoGainControl');
+          remap(c, 'noiseSuppression', 'mozNoiseSuppression');
+        }
+        return nativeApplyConstraints.apply(this, [c]);
+      };
+    }
+  }
   navigator.getUserMedia = function(constraints, onSuccess, onError) {
     if (browserDetails.version < 44) {
       return getUserMedia_(constraints, onSuccess, onError);
@@ -2822,7 +3520,7 @@
   };
 };
 
-},{"../utils":10}],9:[function(require,module,exports){
+},{"../utils":12}],11:[function(require,module,exports){
 /*
  *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
  *
@@ -2831,15 +3529,194 @@
  *  tree.
  */
 'use strict';
+var utils = require('../utils');
+
 var safariShim = {
   // TODO: DrAlex, should be here, double check against LayoutTests
-  // shimOnTrack: function() { },
 
   // TODO: once the back-end for the mac port is done, add.
   // TODO: check for webkitGTK+
   // shimPeerConnection: function() { },
 
-  shimGetUserMedia: function() {
+  shimLocalStreamsAPI: function(window) {
+    if (typeof window !== 'object' || !window.RTCPeerConnection) {
+      return;
+    }
+    if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) {
+      window.RTCPeerConnection.prototype.getLocalStreams = function() {
+        if (!this._localStreams) {
+          this._localStreams = [];
+        }
+        return this._localStreams;
+      };
+    }
+    if (!('getStreamById' in window.RTCPeerConnection.prototype)) {
+      window.RTCPeerConnection.prototype.getStreamById = function(id) {
+        var result = null;
+        if (this._localStreams) {
+          this._localStreams.forEach(function(stream) {
+            if (stream.id === id) {
+              result = stream;
+            }
+          });
+        }
+        if (this._remoteStreams) {
+          this._remoteStreams.forEach(function(stream) {
+            if (stream.id === id) {
+              result = stream;
+            }
+          });
+        }
+        return result;
+      };
+    }
+    if (!('addStream' in window.RTCPeerConnection.prototype)) {
+      var _addTrack = window.RTCPeerConnection.prototype.addTrack;
+      window.RTCPeerConnection.prototype.addStream = function(stream) {
+        if (!this._localStreams) {
+          this._localStreams = [];
+        }
+        if (this._localStreams.indexOf(stream) === -1) {
+          this._localStreams.push(stream);
+        }
+        var self = this;
+        stream.getTracks().forEach(function(track) {
+          _addTrack.call(self, track, stream);
+        });
+      };
+
+      window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
+        if (stream) {
+          if (!this._localStreams) {
+            this._localStreams = [stream];
+          } else if (this._localStreams.indexOf(stream) === -1) {
+            this._localStreams.push(stream);
+          }
+        }
+        _addTrack.call(this, track, stream);
+      };
+    }
+    if (!('removeStream' in window.RTCPeerConnection.prototype)) {
+      window.RTCPeerConnection.prototype.removeStream = function(stream) {
+        if (!this._localStreams) {
+          this._localStreams = [];
+        }
+        var index = this._localStreams.indexOf(stream);
+        if (index === -1) {
+          return;
+        }
+        this._localStreams.splice(index, 1);
+        var self = this;
+        var tracks = stream.getTracks();
+        this.getSenders().forEach(function(sender) {
+          if (tracks.indexOf(sender.track) !== -1) {
+            self.removeTrack(sender);
+          }
+        });
+      };
+    }
+  },
+  shimRemoteStreamsAPI: function(window) {
+    if (typeof window !== 'object' || !window.RTCPeerConnection) {
+      return;
+    }
+    if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) {
+      window.RTCPeerConnection.prototype.getRemoteStreams = function() {
+        return this._remoteStreams ? this._remoteStreams : [];
+      };
+    }
+    if (!('onaddstream' in window.RTCPeerConnection.prototype)) {
+      Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', {
+        get: function() {
+          return this._onaddstream;
+        },
+        set: function(f) {
+          if (this._onaddstream) {
+            this.removeEventListener('addstream', this._onaddstream);
+            this.removeEventListener('track', this._onaddstreampoly);
+          }
+          this.addEventListener('addstream', this._onaddstream = f);
+          this.addEventListener('track', this._onaddstreampoly = function(e) {
+            var stream = e.streams[0];
+            if (!this._remoteStreams) {
+              this._remoteStreams = [];
+            }
+            if (this._remoteStreams.indexOf(stream) >= 0) {
+              return;
+            }
+            this._remoteStreams.push(stream);
+            var event = new Event('addstream');
+            event.stream = e.streams[0];
+            this.dispatchEvent(event);
+          }.bind(this));
+        }
+      });
+    }
+  },
+  shimCallbacksAPI: function(window) {
+    if (typeof window !== 'object' || !window.RTCPeerConnection) {
+      return;
+    }
+    var prototype = window.RTCPeerConnection.prototype;
+    var createOffer = prototype.createOffer;
+    var createAnswer = prototype.createAnswer;
+    var setLocalDescription = prototype.setLocalDescription;
+    var setRemoteDescription = prototype.setRemoteDescription;
+    var addIceCandidate = prototype.addIceCandidate;
+
+    prototype.createOffer = function(successCallback, failureCallback) {
+      var options = (arguments.length >= 2) ? arguments[2] : arguments[0];
+      var promise = createOffer.apply(this, [options]);
+      if (!failureCallback) {
+        return promise;
+      }
+      promise.then(successCallback, failureCallback);
+      return Promise.resolve();
+    };
+
+    prototype.createAnswer = function(successCallback, failureCallback) {
+      var options = (arguments.length >= 2) ? arguments[2] : arguments[0];
+      var promise = createAnswer.apply(this, [options]);
+      if (!failureCallback) {
+        return promise;
+      }
+      promise.then(successCallback, failureCallback);
+      return Promise.resolve();
+    };
+
+    var withCallback = function(description, successCallback, failureCallback) {
+      var promise = setLocalDescription.apply(this, [description]);
+      if (!failureCallback) {
+        return promise;
+      }
+      promise.then(successCallback, failureCallback);
+      return Promise.resolve();
+    };
+    prototype.setLocalDescription = withCallback;
+
+    withCallback = function(description, successCallback, failureCallback) {
+      var promise = setRemoteDescription.apply(this, [description]);
+      if (!failureCallback) {
+        return promise;
+      }
+      promise.then(successCallback, failureCallback);
+      return Promise.resolve();
+    };
+    prototype.setRemoteDescription = withCallback;
+
+    withCallback = function(candidate, successCallback, failureCallback) {
+      var promise = addIceCandidate.apply(this, [candidate]);
+      if (!failureCallback) {
+        return promise;
+      }
+      promise.then(successCallback, failureCallback);
+      return Promise.resolve();
+    };
+    prototype.addIceCandidate = withCallback;
+  },
+  shimGetUserMedia: function(window) {
+    var navigator = window && window.navigator;
+
     if (!navigator.getUserMedia) {
       if (navigator.webkitGetUserMedia) {
         navigator.getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
@@ -2851,18 +3728,52 @@
         }.bind(navigator);
       }
     }
+  },
+  shimRTCIceServerUrls: function(window) {
+    // migrate from non-spec RTCIceServer.url to RTCIceServer.urls
+    var OrigPeerConnection = window.RTCPeerConnection;
+    window.RTCPeerConnection = function(pcConfig, pcConstraints) {
+      if (pcConfig && pcConfig.iceServers) {
+        var newIceServers = [];
+        for (var i = 0; i < pcConfig.iceServers.length; i++) {
+          var server = pcConfig.iceServers[i];
+          if (!server.hasOwnProperty('urls') &&
+              server.hasOwnProperty('url')) {
+            utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls');
+            server = JSON.parse(JSON.stringify(server));
+            server.urls = server.url;
+            delete server.url;
+            newIceServers.push(server);
+          } else {
+            newIceServers.push(pcConfig.iceServers[i]);
+          }
+        }
+        pcConfig.iceServers = newIceServers;
+      }
+      return new OrigPeerConnection(pcConfig, pcConstraints);
+    };
+    window.RTCPeerConnection.prototype = OrigPeerConnection.prototype;
+    // wrap static methods. Currently just generateCertificate.
+    Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
+      get: function() {
+        return OrigPeerConnection.generateCertificate;
+      }
+    });
   }
 };
 
 // Expose public methods.
 module.exports = {
-  shimGetUserMedia: safariShim.shimGetUserMedia
+  shimCallbacksAPI: safariShim.shimCallbacksAPI,
+  shimLocalStreamsAPI: safariShim.shimLocalStreamsAPI,
+  shimRemoteStreamsAPI: safariShim.shimRemoteStreamsAPI,
+  shimGetUserMedia: safariShim.shimGetUserMedia,
+  shimRTCIceServerUrls: safariShim.shimRTCIceServerUrls
   // TODO
-  // shimOnTrack: safariShim.shimOnTrack,
   // shimPeerConnection: safariShim.shimPeerConnection
 };
 
-},{}],10:[function(require,module,exports){
+},{"../utils":12}],12:[function(require,module,exports){
 /*
  *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
  *
@@ -2874,6 +3785,7 @@
 'use strict';
 
 var logDisabled_ = true;
+var deprecationWarnings_ = true;
 
 // Utility methods.
 var utils = {
@@ -2887,6 +3799,19 @@
         'adapter.js logging enabled';
   },
 
+  /**
+   * Disable or enable deprecation warnings
+   * @param {!boolean} bool set to true to disable warnings.
+   */
+  disableWarnings: function(bool) {
+    if (typeof bool !== 'boolean') {
+      return new Error('Argument type: ' + typeof bool +
+          '. Please use a boolean.');
+    }
+    deprecationWarnings_ = !bool;
+    return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled');
+  },
+
   log: function() {
     if (typeof window === 'object') {
       if (logDisabled_) {
@@ -2899,6 +3824,17 @@
   },
 
   /**
+   * Shows a deprecation warning suggesting the modern and spec-compatible API.
+   */
+  deprecated: function(oldMethod, newMethod) {
+    if (!deprecationWarnings_) {
+      return;
+    }
+    console.warn(oldMethod + ' is deprecated, please use ' + newMethod +
+        ' instead.');
+  },
+
+  /**
    * Extract browser version out of the provided user agent string.
    *
    * @param {!string} uastring userAgent string.
@@ -2917,7 +3853,9 @@
    * @return {object} result containing browser and version
    *     properties.
    */
-  detectBrowser: function() {
+  detectBrowser: function(window) {
+    var navigator = window && window.navigator;
+
     // Returned result object.
     var result = {};
     result.browser = null;
@@ -2972,7 +3910,9 @@
 
   // shimCreateObjectURL must be called before shimSourceObject to avoid loop.
 
-  shimCreateObjectURL: function() {
+  shimCreateObjectURL: function(window) {
+    var URL = window && window.URL;
+
     if (!(typeof window === 'object' && window.HTMLMediaElement &&
           'srcObject' in window.HTMLMediaElement.prototype)) {
       // Only shim CreateObjectURL using srcObject if srcObject exists.
@@ -2987,8 +3927,8 @@
       if ('getTracks' in stream) {
         var url = 'polyblob:' + (++newId);
         streams.set(url, stream);
-        console.log('URL.createObjectURL(stream) is deprecated! ' +
-                    'Use elem.srcObject = stream instead!');
+        utils.deprecated('URL.createObjectURL(stream)',
+            'elem.srcObject = stream');
         return url;
       }
       return nativeCreateObjectURL(stream);
@@ -3010,8 +3950,8 @@
       }
     });
 
-    var nativeSetAttribute = HTMLMediaElement.prototype.setAttribute;
-    HTMLMediaElement.prototype.setAttribute = function() {
+    var nativeSetAttribute = window.HTMLMediaElement.prototype.setAttribute;
+    window.HTMLMediaElement.prototype.setAttribute = function() {
       if (arguments.length === 2 &&
           ('' + arguments[0]).toLowerCase() === 'src') {
         this.srcObject = streams.get(arguments[1]) || null;
@@ -3024,8 +3964,9 @@
 // Export.
 module.exports = {
   log: utils.log,
+  deprecated: utils.deprecated,
   disableLog: utils.disableLog,
-  browserDetails: utils.detectBrowser(),
+  disableWarnings: utils.disableWarnings,
   extractVersion: utils.extractVersion,
   shimCreateObjectURL: utils.shimCreateObjectURL,
   detectBrowser: utils.detectBrowser.bind(utils)
diff --git a/tools/perf/page_sets/webrtc_cases/audio.js b/tools/perf/page_sets/webrtc_cases/audio.js
index 2485837..588f643 100644
--- a/tools/perf/page_sets/webrtc_cases/audio.js
+++ b/tools/perf/page_sets/webrtc_cases/audio.js
@@ -40,7 +40,14 @@
   if (audioTracks.length > 0) {
     trace('Using Audio device: ' + audioTracks[0].label);
   }
-  pc1.addStream(localStream);
+  localStream.getTracks().forEach(
+    function(track) {
+      pc1.addTrack(
+        track,
+        localStream
+      );
+    }
+  );
   trace('Adding Local Stream to peer connection');
 
   pc1.createOffer(
@@ -81,7 +88,7 @@
   pc2.onicecandidate = function(e) {
     onIceCandidate(pc2, e);
   };
-  pc2.onaddstream = gotRemoteStream;
+  pc2.ontrack = gotRemoteStream;
   trace('Requesting local stream');
   navigator.mediaDevices.getUserMedia({
     audio: true,
@@ -142,8 +149,10 @@
 }
 
 function gotRemoteStream(e) {
-  audio2.srcObject = e.stream;
-  trace('Received remote stream');
+  if (audio2.srcObject !== e.streams[0]) {
+    audio2.srcObject = e.streams[0];
+    trace('Received remote stream');
+  }
 }
 
 function getOtherPc(pc) {
diff --git a/tools/perf/page_sets/webrtc_cases/canvas-capture.html b/tools/perf/page_sets/webrtc_cases/canvas-capture.html
index 340c6e16..018cb25 100644
--- a/tools/perf/page_sets/webrtc_cases/canvas-capture.html
+++ b/tools/perf/page_sets/webrtc_cases/canvas-capture.html
@@ -11,8 +11,8 @@
   <div id="container">
     <h1>Canvas capture stream to peerConnection</h1>
 
-    <canvas id="canvas" width=32 height=24></canvas>
-    <video id="remoteVideo" width=32 height=24 autoplay=""></video>
+    <canvas id="canvas" width="32" height="24"></canvas>
+    <video id="remoteVideo" width="32" height="24" autoplay=""></video>
 
     <div>
       <button id="startButton" class="green">Start test</button>
diff --git a/tools/perf/page_sets/webrtc_cases/canvas-capture.js b/tools/perf/page_sets/webrtc_cases/canvas-capture.js
index ce7e11d3..744e0e10 100644
--- a/tools/perf/page_sets/webrtc_cases/canvas-capture.js
+++ b/tools/perf/page_sets/webrtc_cases/canvas-capture.js
@@ -29,12 +29,13 @@
   context.rect(0, 0, canvas.clientWidth, canvas.clientHeight);
   var randomNumber = Math.random();
   var hue;
-  if (randomNumber < 0.33)
+  if (randomNumber < 0.33) {
     hue = 'red';
-  else if (randomNumber < 0.66)
+  } else if (randomNumber < 0.66) {
     hue = 'green';
-  else
+  } else {
     hue = 'blue';
+  }
   context.fillStyle = hue;
   context.fill();
 }
diff --git a/tools/perf/page_sets/webrtc_cases/constraints.js b/tools/perf/page_sets/webrtc_cases/constraints.js
index 16cf173..d19d2123 100644
--- a/tools/perf/page_sets/webrtc_cases/constraints.js
+++ b/tools/perf/page_sets/webrtc_cases/constraints.js
@@ -140,7 +140,14 @@
   timestampPrev = 0;
   localPeerConnection = new RTCPeerConnection(null);
   remotePeerConnection = new RTCPeerConnection(null);
-  localPeerConnection.addStream(localStream);
+  localStream.getTracks().forEach(
+    function(track) {
+      localPeerConnection.addTrack(
+        track,
+        localStream
+      );
+    }
+  );
   console.log('localPeerConnection creating offer');
   localPeerConnection.onnegotiationeeded = function() {
     console.log('Negotiation needed - localPeerConnection');
@@ -150,27 +157,25 @@
   };
   localPeerConnection.onicecandidate = function(e) {
     console.log('Candidate localPeerConnection');
-    if (e.candidate) {
-      remotePeerConnection.addIceCandidate(e.candidate)
-      .then(
-        onAddIceCandidateSuccess,
-        onAddIceCandidateError
-      );
-    }
+    remotePeerConnection.addIceCandidate(e.candidate)
+    .then(
+      onAddIceCandidateSuccess,
+      onAddIceCandidateError
+    );
   };
   remotePeerConnection.onicecandidate = function(e) {
     console.log('Candidate remotePeerConnection');
-    if (e.candidate) {
-      localPeerConnection.addIceCandidate(e.candidate)
-      .then(
-        onAddIceCandidateSuccess,
-        onAddIceCandidateError
-      );
-    }
+    localPeerConnection.addIceCandidate(e.candidate)
+    .then(
+      onAddIceCandidateSuccess,
+      onAddIceCandidateError
+    );
   };
-  remotePeerConnection.onaddstream = function(e) {
-    console.log('remotePeerConnection got stream');
-    remoteVideo.srcObject = e.stream;
+  remotePeerConnection.ontrack = function(e) {
+    if (remoteVideo.srcObject !== e.streams[0]) {
+      console.log('remotePeerConnection got stream');
+      remoteVideo.srcObject = e.streams[0];
+    }
   };
   localPeerConnection.createOffer().then(
     function(desc) {
@@ -239,22 +244,35 @@
       var activeCandidatePair = null;
       var remoteCandidate = null;
 
-      // search for the candidate pair
+      // Search for the candidate pair, spec-way first.
       results.forEach(function(report) {
-        if (report.type === 'candidatepair' && report.selected ||
-            report.type === 'googCandidatePair' &&
-            report.googActiveConnection === 'true') {
-          activeCandidatePair = report;
+        if (report.type === 'transport') {
+          activeCandidatePair = results.get(report.selectedCandidatePairId);
         }
       });
-      if (activeCandidatePair && activeCandidatePair.remoteCandidateId) {
-        remoteCandidate = results[activeCandidatePair.remoteCandidateId];
+      // Fallback for Firefox and Chrome legacy stats.
+      if (!activeCandidatePair) {
+        results.forEach(function(report) {
+          if (report.type === 'candidate-pair' && report.selected ||
+              report.type === 'googCandidatePair' &&
+              report.googActiveConnection === 'true') {
+            activeCandidatePair = report;
+          }
+        });
       }
-      if (remoteCandidate && remoteCandidate.ipAddress &&
-          remoteCandidate.portNumber) {
-        peerDiv.innerHTML = '<strong>Connected to:</strong> ' +
-            remoteCandidate.ipAddress +
-            ':' + remoteCandidate.portNumber;
+      if (activeCandidatePair && activeCandidatePair.remoteCandidateId) {
+        remoteCandidate = results.get(activeCandidatePair.remoteCandidateId);
+      }
+      if (remoteCandidate) {
+        if (remoteCandidate.ip && remoteCandidate.port) {
+          peerDiv.innerHTML = '<strong>Connected to:</strong> ' +
+              remoteCandidate.ip + ':' + remoteCandidate.port;
+        } else if (remoteCandidate.ipAddress && remoteCandidate.portNumber) {
+          // Fall back to old names.
+          peerDiv.innerHTML = '<strong>Connected to:</strong> ' +
+              remoteCandidate.ipAddress +
+              ':' + remoteCandidate.portNumber;
+        }
       }
     }, function(err) {
       console.log(err);
diff --git a/tools/perf/page_sets/webrtc_cases/multiple-peerconnections.js b/tools/perf/page_sets/webrtc_cases/multiple-peerconnections.js
index 4401d10..3af6c63 100644
--- a/tools/perf/page_sets/webrtc_cases/multiple-peerconnections.js
+++ b/tools/perf/page_sets/webrtc_cases/multiple-peerconnections.js
@@ -3,7 +3,7 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
-/*jshint esversion: 6 */
+/* jshint esversion: 6 */
 
 'use strict';
 
@@ -44,8 +44,8 @@
       audio: true,
       video: true
     })
-    .then(onGetUserMediaSuccess)
-    .catch(logError);
+      .then(onGetUserMediaSuccess)
+      .catch(logError);
   };
 
   this.onGetUserMediaSuccess = function(stream) {
@@ -79,7 +79,7 @@
       offerToReceiveAudio: 1,
       offerToReceiveVideo: 1
     })
-    .then(onCreateOfferSuccess, logError);
+      .then(onCreateOfferSuccess, logError);
   };
 
   this.onCreateOfferSuccess = function(desc) {
@@ -88,7 +88,7 @@
 
     var onCreateAnswerSuccess = this.onCreateAnswerSuccess.bind(this);
     this.remoteConnection.createAnswer()
-    .then(onCreateAnswerSuccess, logError);
+      .then(onCreateAnswerSuccess, logError);
   };
 
   this.onCreateAnswerSuccess = function(desc) {
diff --git a/tools/perf/page_sets/webrtc_cases/pause-play.html b/tools/perf/page_sets/webrtc_cases/pause-play.html
new file mode 100644
index 0000000..5b5825d
--- /dev/null
+++ b/tools/perf/page_sets/webrtc_cases/pause-play.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<!--
+ * Copyright 2017 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+-->
+    <title>Pause Play Test</title>
+  </head>
+  <body>
+    <h1>Pause Play Test</h1>
+    <p>Status: <span id="status">not-started</span></p>
+    <table border="0" id="test-table"></table>
+
+<script src="pause-play.js"></script>
+<script src="adapter.js"></script>
+<script src="common.js"></script>
+</body></html>
diff --git a/tools/perf/page_sets/webrtc_cases/pause-play.js b/tools/perf/page_sets/webrtc_cases/pause-play.js
new file mode 100644
index 0000000..cfc9083
--- /dev/null
+++ b/tools/perf/page_sets/webrtc_cases/pause-play.js
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2017 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+const $ = document.getElementById.bind(document);
+
+function logError(err) {
+  console.error(err);
+}
+
+/**
+ * FeedTable stores all video elements.
+ */
+class FeedTable {
+  constructor() {
+    this.numCols = 5;
+    this.col = 0;
+    this.testTable = document.getElementById('test-table');
+    this.row = this.testTable.insertRow(-1);
+  }
+
+  addNewCell(elementType) {
+    if (this.col === this.numCols) {
+      this.row = this.testTable.insertRow(-1);
+      this.col = 0;
+    }
+    var newCell = this.row.insertCell(-1);
+    var element = document.createElement(elementType);
+    element.autoplay = false;
+    newCell.appendChild(element);
+    this.col++;
+    return element;
+  }
+}
+
+/**
+ * A simple loopback connection;
+ * - localConnection is fed video from local camera
+ * - localConnection is linked to remoteConnection
+ * - remoteConnection is displayed in the given videoElement
+ */
+class PeerConnection {
+  /**
+   * @param {Object} element - An 'audio' or 'video' element.
+   * @param {Object} constraints - The constraints for the peer connection.
+   */
+  constructor(element, constraints) {
+    this.localConnection = null;
+    this.remoteConnection = null;
+    this.remoteView = element;
+    this.constraints = constraints;
+  }
+
+  start() {
+    return navigator.mediaDevices
+      .getUserMedia(this.constraints)
+      .then((stream) => {
+        this.onGetUserMediaSuccess(stream);
+      });
+  }
+
+  onGetUserMediaSuccess(stream) {
+    this.localConnection = new RTCPeerConnection(null);
+    this.localConnection.onicecandidate = (event) => {
+      this.onIceCandidate(this.remoteConnection, event);
+    };
+    this.localConnection.addStream(stream);
+
+    this.remoteConnection = new RTCPeerConnection(null);
+    this.remoteConnection.onicecandidate = (event) => {
+      this.onIceCandidate(this.localConnection, event);
+    };
+    this.remoteConnection.onaddstream = (e) => {
+      this.remoteView.srcObject = e.stream;
+    };
+
+    this.localConnection
+      .createOffer({offerToReceiveAudio: 1, offerToReceiveVideo: 1})
+      .then((offerDesc) => {
+        this.onCreateOfferSuccess(offerDesc);
+      }, logError);
+  }
+
+  onCreateOfferSuccess(offerDesc) {
+    this.localConnection.setLocalDescription(offerDesc);
+    this.remoteConnection.setRemoteDescription(offerDesc);
+
+    this.remoteConnection.createAnswer().then(
+      (answerDesc) => {
+        this.onCreateAnswerSuccess(answerDesc);
+      }, logError);
+  }
+
+  onCreateAnswerSuccess(answerDesc) {
+    this.remoteConnection.setLocalDescription(answerDesc);
+    this.localConnection.setRemoteDescription(answerDesc);
+  }
+
+  onIceCandidate(connection, event) {
+    if (event.candidate) {
+      connection.addIceCandidate(new RTCIceCandidate(event.candidate));
+    }
+  }
+}
+
+class TestRunner {
+  constructor(runtimeSeconds, pausePlayIterationDelayMillis) {
+    this.runtimeSeconds = runtimeSeconds;
+    this.pausePlayIterationDelayMillis = pausePlayIterationDelayMillis;
+    this.elements = [];
+    this.peerConnections = [];
+    this.feedTable = new FeedTable();
+    this.iteration = 0;
+    this.startTime = null;
+    this.lastIterationTime = null;
+  }
+
+  addPeerConnection(elementType) {
+    const element = this.feedTable.addNewCell(elementType);
+    const constraints = {audio: true};
+    if (elementType === 'video') {
+      constraints.video = {
+        width: {exact: 300}
+      };
+    } else if (elementType === 'audio') {
+      constraints.video = false;
+    } else {
+      throw new Error('elementType must be one of "audio" or "video"');
+    }
+    this.elements.push(element);
+    this.peerConnections.push(new PeerConnection(element, constraints));
+  }
+
+  startTest() {
+    this.startTime = Date.now();
+    let promises = testRunner.peerConnections.map((conn) => conn.start());
+    Promise.all(promises)
+      .then(() => {
+        this.startTime = Date.now();
+        this.pauseAndPlayLoop();
+      })
+      .catch((e) => {
+        throw e;
+      });
+  }
+
+  pauseAndPlayLoop() {
+    this.iteration++;
+    this.elements.forEach((feed) => {
+      if (Math.random() >= 0.5) {
+        feed.play();
+      } else {
+        feed.pause();
+      }
+    });
+    const status = this.getStatus();
+    this.lastIterationTime = Date.now();
+    $('status').textContent = status;
+    if (status !== 'ok-done') {
+      setTimeout(
+        () => {
+          this.pauseAndPlayLoop();
+        }, this.pausePlayIterationDelayMillis);
+    } else { // We're done. Pause all feeds.
+      this.elements.forEach((feed) => {
+        feed.pause();
+      });
+    }
+  }
+
+  getStatus() {
+    if (this.iteration === 0) {
+      return 'not-started';
+    }
+    const timeSpent = Date.now() - this.startTime;
+    if (timeSpent >= this.runtimeSeconds * 1000) {
+      return 'ok-done';
+    }
+    return `running, iteration: ${this.iteration}`;
+  }
+
+  getResults() {
+    const runTimeMillis = this.lastIterationTime - this.startTime;
+    return {'runTimeSeconds': runTimeMillis / 1000};
+  }
+}
+
+let testRunner;
+
+function startTest(
+  runtimeSeconds, numPeerConnections, pausePlayIterationDelayMillis,
+  elementType) {
+  testRunner = new TestRunner(
+    runtimeSeconds, pausePlayIterationDelayMillis);
+  for (let i = 0; i < numPeerConnections; i++) {
+    testRunner.addPeerConnection(elementType);
+  }
+  testRunner.startTest();
+}
diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc
index b597ccf..15fc211 100644
--- a/ui/accessibility/platform/ax_platform_node_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_win.cc
@@ -962,6 +962,570 @@
 }
 
 //
+// IAccessibleTable methods.
+//
+
+STDMETHODIMP AXPlatformNodeWin::get_accessibleAt(long row,
+                                                 long column,
+                                                 IUnknown** accessible) {
+  if (!accessible)
+    return E_INVALIDARG;
+
+  AXPlatformNodeBase* cell =
+      GetTableCell(static_cast<int>(row), static_cast<int>(column));
+  if (cell) {
+    auto* node_win = static_cast<AXPlatformNodeWin*>(cell);
+    node_win->AddRef();
+
+    *accessible = static_cast<IAccessible*>(node_win);
+    return S_OK;
+  }
+
+  *accessible = nullptr;
+  return E_INVALIDARG;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_caption(IUnknown** accessible) {
+  if (!accessible)
+    return E_INVALIDARG;
+
+  // TODO(dmazzoni): implement
+  *accessible = nullptr;
+  return S_FALSE;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_childIndex(long row,
+                                               long column,
+                                               long* cell_index) {
+  if (!cell_index)
+    return E_INVALIDARG;
+
+  auto* cell = GetTableCell(static_cast<int>(row), static_cast<int>(column));
+  if (cell) {
+    *cell_index = static_cast<LONG>(cell->GetTableCellIndex());
+    return S_OK;
+  }
+
+  *cell_index = 0;
+  return E_INVALIDARG;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_columnDescription(long column,
+                                                      BSTR* description) {
+  if (!description)
+    return E_INVALIDARG;
+
+  int columns = GetTableColumnCount();
+  if (column < 0 || column >= columns)
+    return E_INVALIDARG;
+
+  int rows = GetTableRowCount();
+  if (rows <= 0) {
+    *description = nullptr;
+    return S_FALSE;
+  }
+
+  for (int i = 0; i < rows; ++i) {
+    auto* cell = GetTableCell(i, column);
+    if (cell && cell->GetData().role == ui::AX_ROLE_COLUMN_HEADER) {
+      base::string16 cell_name = cell->GetString16Attribute(ui::AX_ATTR_NAME);
+      if (cell_name.size() > 0) {
+        *description = SysAllocString(cell_name.c_str());
+        return S_OK;
+      }
+
+      cell_name = cell->GetString16Attribute(ui::AX_ATTR_DESCRIPTION);
+      if (cell_name.size() > 0) {
+        *description = SysAllocString(cell_name.c_str());
+        return S_OK;
+      }
+    }
+  }
+
+  *description = nullptr;
+  return S_FALSE;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_columnExtentAt(long row,
+                                                   long column,
+                                                   long* n_columns_spanned) {
+  if (!n_columns_spanned)
+    return E_INVALIDARG;
+
+  auto* cell = GetTableCell(static_cast<int>(row), static_cast<int>(column));
+  if (!cell)
+    return E_INVALIDARG;
+
+  *n_columns_spanned = cell->GetTableColumnSpan();
+  return S_OK;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_columnHeader(
+    IAccessibleTable** accessible_table,
+    long* starting_row_index) {
+  // TODO(dmazzoni): implement
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_columnIndex(long cell_index,
+                                                long* column_index) {
+  if (!column_index)
+    return E_INVALIDARG;
+
+  auto* cell = GetTableCell(cell_index);
+  if (!cell)
+    return E_INVALIDARG;
+  *column_index = cell->GetTableColumn();
+  return S_OK;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_nColumns(long* column_count) {
+  if (!column_count)
+    return E_INVALIDARG;
+
+  *column_count = GetTableColumnCount();
+  return S_OK;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_nRows(long* row_count) {
+  if (!row_count)
+    return E_INVALIDARG;
+
+  *row_count = GetTableRowCount();
+  return S_OK;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_nSelectedChildren(long* cell_count) {
+  if (!cell_count)
+    return E_INVALIDARG;
+
+  // TODO(dmazzoni): add support for selected cells/rows/columns in tables.
+  *cell_count = 0;
+  return S_FALSE;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_nSelectedColumns(long* column_count) {
+  if (!column_count)
+    return E_INVALIDARG;
+
+  *column_count = 0;
+  return S_FALSE;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_nSelectedRows(long* row_count) {
+  if (!row_count)
+    return E_INVALIDARG;
+
+  *row_count = 0;
+  return S_FALSE;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_rowDescription(long row,
+                                                   BSTR* description) {
+  if (!description)
+    return E_INVALIDARG;
+
+  if (row < 0 || row >= GetTableRowCount())
+    return E_INVALIDARG;
+
+  int columns = GetTableColumnCount();
+  if (columns <= 0) {
+    *description = nullptr;
+    return S_FALSE;
+  }
+
+  for (int i = 0; i < columns; ++i) {
+    auto* cell = GetTableCell(row, i);
+    if (cell && cell->GetData().role == ui::AX_ROLE_ROW_HEADER) {
+      base::string16 cell_name = cell->GetString16Attribute(ui::AX_ATTR_NAME);
+      if (cell_name.size() > 0) {
+        *description = SysAllocString(cell_name.c_str());
+        return S_OK;
+      }
+      cell_name = cell->GetString16Attribute(ui::AX_ATTR_DESCRIPTION);
+      if (cell_name.size() > 0) {
+        *description = SysAllocString(cell_name.c_str());
+        return S_OK;
+      }
+    }
+  }
+
+  *description = nullptr;
+  return S_FALSE;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_rowExtentAt(long row,
+                                                long column,
+                                                long* n_rows_spanned) {
+  if (!n_rows_spanned)
+    return E_INVALIDARG;
+
+  auto* cell = GetTableCell(row, column);
+  if (!cell)
+    return E_INVALIDARG;
+
+  *n_rows_spanned = GetTableRowSpan();
+  return S_OK;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_rowHeader(
+    IAccessibleTable** accessible_table,
+    long* starting_column_index) {
+  // TODO(dmazzoni): implement
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_rowIndex(long cell_index, long* row_index) {
+  if (!row_index)
+    return E_INVALIDARG;
+
+  auto* cell = GetTableCell(cell_index);
+  if (!cell)
+    return E_INVALIDARG;
+
+  *row_index = cell->GetTableRow();
+  return S_OK;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_selectedChildren(long max_children,
+                                                     long** children,
+                                                     long* n_children) {
+  if (!children || !n_children)
+    return E_INVALIDARG;
+
+  // TODO(dmazzoni): Implement this.
+  *n_children = 0;
+  return S_FALSE;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_selectedColumns(long max_columns,
+                                                    long** columns,
+                                                    long* n_columns) {
+  if (!columns || !n_columns)
+    return E_INVALIDARG;
+
+  // TODO(dmazzoni): Implement this.
+  *n_columns = 0;
+  return S_FALSE;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_selectedRows(long max_rows,
+                                                 long** rows,
+                                                 long* n_rows) {
+  if (!rows || !n_rows)
+    return E_INVALIDARG;
+
+  // TODO(dmazzoni): Implement this.
+  *n_rows = 0;
+  return S_FALSE;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_summary(IUnknown** accessible) {
+  if (!accessible)
+    return E_INVALIDARG;
+
+  // TODO(dmazzoni): implement
+  *accessible = nullptr;
+  return S_FALSE;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_isColumnSelected(long column,
+                                                     boolean* is_selected) {
+  if (!is_selected)
+    return E_INVALIDARG;
+
+  // TODO(dmazzoni): Implement this.
+  *is_selected = false;
+  return S_OK;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_isRowSelected(long row,
+                                                  boolean* is_selected) {
+  if (!is_selected)
+    return E_INVALIDARG;
+
+  // TODO(dmazzoni): Implement this.
+  *is_selected = false;
+  return S_OK;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_isSelected(long row,
+                                               long column,
+                                               boolean* is_selected) {
+  if (!is_selected)
+    return E_INVALIDARG;
+
+  // TODO(dmazzoni): Implement this.
+  *is_selected = false;
+  return S_OK;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_rowColumnExtentsAtIndex(
+    long index,
+    long* row,
+    long* column,
+    long* row_extents,
+    long* column_extents,
+    boolean* is_selected) {
+  if (!row || !column || !row_extents || !column_extents || !is_selected)
+    return E_INVALIDARG;
+
+  auto* cell = GetTableCell(index);
+  if (!cell)
+    return E_INVALIDARG;
+
+  *row = cell->GetTableRow();
+  *column = cell->GetTableColumn();
+  *row_extents = GetTableRowSpan();
+  *column_extents = GetTableColumnSpan();
+  *is_selected = false;  // Not supported.
+
+  return S_OK;
+}
+
+STDMETHODIMP AXPlatformNodeWin::selectRow(long row) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeWin::selectColumn(long column) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeWin::unselectRow(long row) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP AXPlatformNodeWin::unselectColumn(long column) {
+  return E_NOTIMPL;
+}
+
+STDMETHODIMP
+AXPlatformNodeWin::get_modelChange(IA2TableModelChange* model_change) {
+  return E_NOTIMPL;
+}
+
+//
+// IAccessibleTable2 methods.
+//
+
+STDMETHODIMP AXPlatformNodeWin::get_cellAt(long row,
+                                           long column,
+                                           IUnknown** cell) {
+  if (!cell)
+    return E_INVALIDARG;
+
+  AXPlatformNodeBase* table_cell =
+      GetTableCell(static_cast<int>(row), static_cast<int>(column));
+  if (table_cell) {
+    auto* node_win = static_cast<AXPlatformNodeWin*>(table_cell);
+    node_win->AddRef();
+    *cell = static_cast<IAccessible*>(node_win);
+    return S_OK;
+  }
+
+  *cell = nullptr;
+  return E_INVALIDARG;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_nSelectedCells(long* cell_count) {
+  return get_nSelectedChildren(cell_count);
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_selectedCells(IUnknown*** cells,
+                                                  long* n_selected_cells) {
+  if (!cells || !n_selected_cells)
+    return E_INVALIDARG;
+
+  // TODO(dmazzoni): Implement this.
+  *n_selected_cells = 0;
+  return S_OK;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_selectedColumns(long** columns,
+                                                    long* n_columns) {
+  if (!columns || !n_columns)
+    return E_INVALIDARG;
+
+  // TODO(dmazzoni): Implement this.
+  *n_columns = 0;
+  return S_OK;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_selectedRows(long** rows, long* n_rows) {
+  if (!rows || !n_rows)
+    return E_INVALIDARG;
+
+  // TODO(dmazzoni): Implement this.
+  *n_rows = 0;
+  return S_OK;
+}
+
+//
+// IAccessibleTableCell methods.
+//
+
+STDMETHODIMP AXPlatformNodeWin::get_columnExtent(long* n_columns_spanned) {
+  if (!n_columns_spanned)
+    return E_INVALIDARG;
+
+  *n_columns_spanned = GetTableColumnSpan();
+  return S_OK;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_columnHeaderCells(
+    IUnknown*** cell_accessibles,
+    long* n_column_header_cells) {
+  if (!cell_accessibles || !n_column_header_cells)
+    return E_INVALIDARG;
+
+  *n_column_header_cells = 0;
+  auto* table = GetTable();
+  if (!table) {
+    NOTREACHED();
+    return S_FALSE;
+  }
+
+  int column = GetTableColumn();
+  int columns = GetTableColumnCount();
+  int rows = GetTableRowCount();
+  if (columns <= 0 || rows <= 0 || column < 0 || column >= columns)
+    return S_FALSE;
+
+  for (int i = 0; i < rows; ++i) {
+    auto* cell = GetTableCell(i, column);
+    if (cell && cell->GetData().role == ui::AX_ROLE_COLUMN_HEADER)
+      (*n_column_header_cells)++;
+  }
+
+  *cell_accessibles = static_cast<IUnknown**>(
+      CoTaskMemAlloc((*n_column_header_cells) * sizeof(cell_accessibles[0])));
+  int index = 0;
+  for (int i = 0; i < rows; ++i) {
+    AXPlatformNodeBase* cell = GetTableCell(i, column);
+    if (cell && cell->GetData().role == ui::AX_ROLE_COLUMN_HEADER) {
+      auto* node_win = static_cast<AXPlatformNodeWin*>(cell);
+      node_win->AddRef();
+
+      (*cell_accessibles)[index] = static_cast<IAccessible*>(node_win);
+      ++index;
+    }
+  }
+
+  return S_OK;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_columnIndex(long* column_index) {
+  if (!column_index)
+    return E_INVALIDARG;
+
+  *column_index = GetTableColumn();
+  return S_OK;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_rowExtent(long* n_rows_spanned) {
+  if (!n_rows_spanned)
+    return E_INVALIDARG;
+
+  *n_rows_spanned = GetTableRowSpan();
+  return S_OK;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_rowHeaderCells(IUnknown*** cell_accessibles,
+                                                   long* n_row_header_cells) {
+  if (!cell_accessibles || !n_row_header_cells)
+    return E_INVALIDARG;
+
+  *n_row_header_cells = 0;
+  auto* table = GetTable();
+  if (!table) {
+    NOTREACHED();
+    return S_FALSE;
+  }
+
+  int row = GetTableRow();
+  int columns = GetTableColumnCount();
+  int rows = GetTableRowCount();
+  if (columns <= 0 || rows <= 0 || row < 0 || row >= rows)
+    return S_FALSE;
+
+  for (int i = 0; i < columns; ++i) {
+    auto* cell = GetTableCell(row, i);
+    if (cell && cell->GetData().role == ui::AX_ROLE_ROW_HEADER)
+      (*n_row_header_cells)++;
+  }
+
+  *cell_accessibles = static_cast<IUnknown**>(
+      CoTaskMemAlloc((*n_row_header_cells) * sizeof(cell_accessibles[0])));
+  int index = 0;
+  for (int i = 0; i < columns; ++i) {
+    AXPlatformNodeBase* cell = GetTableCell(row, i);
+    if (cell && cell->GetData().role == ui::AX_ROLE_ROW_HEADER) {
+      auto* node_win = static_cast<AXPlatformNodeWin*>(cell);
+      node_win->AddRef();
+
+      (*cell_accessibles)[index] = static_cast<IAccessible*>(node_win);
+      ++index;
+    }
+  }
+
+  return S_OK;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_rowIndex(long* row_index) {
+  if (!row_index)
+    return E_INVALIDARG;
+
+  *row_index = GetTableRow();
+  return S_OK;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_isSelected(boolean* is_selected) {
+  if (!is_selected)
+    return E_INVALIDARG;
+
+  *is_selected = false;
+  return S_OK;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_rowColumnExtents(long* row_index,
+                                                     long* column_index,
+                                                     long* row_extents,
+                                                     long* column_extents,
+                                                     boolean* is_selected) {
+  if (!row_index || !column_index || !row_extents || !column_extents ||
+      !is_selected) {
+    return E_INVALIDARG;
+  }
+
+  *row_index = GetTableRow();
+  *column_index = GetTableColumn();
+  *row_extents = GetTableRowSpan();
+  *column_extents = GetTableColumnSpan();
+  *is_selected = false;  // Not supported.
+
+  return S_OK;
+}
+
+STDMETHODIMP AXPlatformNodeWin::get_table(IUnknown** table) {
+  if (!table)
+    return E_INVALIDARG;
+
+  auto* find_table = GetTable();
+  if (!find_table) {
+    *table = nullptr;
+    return S_FALSE;
+  }
+
+  // The IAccessibleTable interface is still on the AXPlatformNodeWin
+  // class.
+  auto* node_win = static_cast<AXPlatformNodeWin*>(find_table);
+  node_win->AddRef();
+
+  *table = static_cast<IAccessibleTable*>(node_win);
+  return S_OK;
+}
+
+//
 // IAccessibleText
 //
 
diff --git a/ui/accessibility/platform/ax_platform_node_win.h b/ui/accessibility/platform/ax_platform_node_win.h
index 2ca22ca..a08df79 100644
--- a/ui/accessibility/platform/ax_platform_node_win.h
+++ b/ui/accessibility/platform/ax_platform_node_win.h
@@ -40,6 +40,9 @@
                            &IID_IAccessible2,
                            &LIBID_IAccessible2Lib>,
       public IAccessibleText,
+      public IAccessibleTable,
+      public IAccessibleTable2,
+      public IAccessibleTableCell,
       public IServiceProvider,
       public AXPlatformNodeBase {
  public:
@@ -50,6 +53,9 @@
     COM_INTERFACE_ENTRY(IAccessible2)
     COM_INTERFACE_ENTRY(IAccessible2_2)
     COM_INTERFACE_ENTRY(IAccessibleText)
+    COM_INTERFACE_ENTRY(IAccessibleTable)
+    COM_INTERFACE_ENTRY(IAccessibleTable2)
+    COM_INTERFACE_ENTRY(IAccessibleTableCell)
     COM_INTERFACE_ENTRY(IServiceProvider)
   END_COM_MAP()
 
@@ -228,6 +234,147 @@
                                  LONG* offset) override;
 
   //
+  // IAccessibleTable methods.
+  //
+
+  // get_description - also used by IAccessibleImage
+
+  STDMETHODIMP get_accessibleAt(long row,
+                                long column,
+                                IUnknown** accessible) override;
+
+  STDMETHODIMP get_caption(IUnknown** accessible) override;
+
+  STDMETHODIMP get_childIndex(long row_index,
+                              long column_index,
+                              long* cell_index) override;
+
+  STDMETHODIMP get_columnDescription(long column, BSTR* description) override;
+
+  STDMETHODIMP
+  get_columnExtentAt(long row, long column, long* n_columns_spanned) override;
+
+  STDMETHODIMP
+  get_columnHeader(IAccessibleTable** accessible_table,
+                   long* starting_row_index) override;
+
+  STDMETHODIMP get_columnIndex(long cell_index, long* column_index) override;
+
+  STDMETHODIMP get_nColumns(long* column_count) override;
+
+  STDMETHODIMP get_nRows(long* row_count) override;
+
+  STDMETHODIMP get_nSelectedChildren(long* cell_count) override;
+
+  STDMETHODIMP get_nSelectedColumns(long* column_count) override;
+
+  STDMETHODIMP get_nSelectedRows(long* row_count) override;
+
+  STDMETHODIMP get_rowDescription(long row, BSTR* description) override;
+
+  STDMETHODIMP get_rowExtentAt(long row,
+                               long column,
+                               long* n_rows_spanned) override;
+
+  STDMETHODIMP
+  get_rowHeader(IAccessibleTable** accessible_table,
+                long* starting_column_index) override;
+
+  STDMETHODIMP get_rowIndex(long cell_index, long* row_index) override;
+
+  STDMETHODIMP get_selectedChildren(long max_children,
+                                    long** children,
+                                    long* n_children) override;
+
+  STDMETHODIMP get_selectedColumns(long max_columns,
+                                   long** columns,
+                                   long* n_columns) override;
+
+  STDMETHODIMP get_selectedRows(long max_rows,
+                                long** rows,
+                                long* n_rows) override;
+
+  STDMETHODIMP get_summary(IUnknown** accessible) override;
+
+  STDMETHODIMP
+  get_isColumnSelected(long column, boolean* is_selected) override;
+
+  STDMETHODIMP get_isRowSelected(long row, boolean* is_selected) override;
+
+  STDMETHODIMP get_isSelected(long row,
+                              long column,
+                              boolean* is_selected) override;
+
+  STDMETHODIMP
+  get_rowColumnExtentsAtIndex(long index,
+                              long* row,
+                              long* column,
+                              long* row_extents,
+                              long* column_extents,
+                              boolean* is_selected) override;
+
+  STDMETHODIMP selectRow(long row) override;
+
+  STDMETHODIMP selectColumn(long column) override;
+
+  STDMETHODIMP unselectRow(long row) override;
+
+  STDMETHODIMP unselectColumn(long column) override;
+
+  STDMETHODIMP
+  get_modelChange(IA2TableModelChange* model_change) override;
+
+  //
+  // IAccessibleTable2 methods.
+  //
+  // (Most of these are duplicates of IAccessibleTable methods, only the
+  // unique ones are included here.)
+  //
+
+  STDMETHODIMP get_cellAt(long row, long column, IUnknown** cell) override;
+
+  STDMETHODIMP get_nSelectedCells(long* cell_count) override;
+
+  STDMETHODIMP
+  get_selectedCells(IUnknown*** cells, long* n_selected_cells) override;
+
+  STDMETHODIMP get_selectedColumns(long** columns, long* n_columns) override;
+
+  STDMETHODIMP get_selectedRows(long** rows, long* n_rows) override;
+
+  //
+  // IAccessibleTableCell methods.
+  //
+
+  STDMETHODIMP
+  get_columnExtent(long* n_columns_spanned) override;
+
+  STDMETHODIMP
+  get_columnHeaderCells(IUnknown*** cell_accessibles,
+                        long* n_column_header_cells) override;
+
+  STDMETHODIMP get_columnIndex(long* column_index) override;
+
+  STDMETHODIMP get_rowExtent(long* n_rows_spanned) override;
+
+  STDMETHODIMP
+  get_rowHeaderCells(IUnknown*** cell_accessibles,
+                     long* n_row_header_cells) override;
+
+  STDMETHODIMP get_rowIndex(long* row_index) override;
+
+  STDMETHODIMP get_isSelected(boolean* is_selected) override;
+
+  STDMETHODIMP
+  get_rowColumnExtents(long* row,
+                       long* column,
+                       long* row_extents,
+                       long* column_extents,
+                       boolean* is_selected) override;
+
+  STDMETHODIMP get_table(IUnknown** table) override;
+
+  //
   // IAccessibleText methods not implemented.
   //
 
diff --git a/ui/accessibility/platform/ax_platform_node_win_unittest.cc b/ui/accessibility/platform/ax_platform_node_win_unittest.cc
index 0ccd3fa..b212248 100644
--- a/ui/accessibility/platform/ax_platform_node_win_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_win_unittest.cc
@@ -106,6 +106,16 @@
     return IAccessibleFromNode(GetRootNode());
   }
 
+  ScopedComPtr<IAccessible2> ToIAccessible2(ScopedComPtr<IUnknown> unknown) {
+    CHECK(unknown);
+    ScopedComPtr<IServiceProvider> service_provider;
+    unknown.CopyTo(service_provider.GetAddressOf());
+    ScopedComPtr<IAccessible2> result;
+    CHECK(SUCCEEDED(service_provider->QueryService(IID_IAccessible2,
+                                                   result.GetAddressOf())));
+    return result;
+  }
+
   ScopedComPtr<IAccessible2> ToIAccessible2(
       ScopedComPtr<IAccessible> accessible) {
     CHECK(accessible);
@@ -117,6 +127,173 @@
     return result;
   }
 
+  void CheckVariantHasName(ScopedVariant& variant,
+                           const wchar_t* expected_name) {
+    ScopedComPtr<IAccessible> accessible;
+    HRESULT hr =
+        V_DISPATCH(variant.ptr())->QueryInterface(IID_PPV_ARGS(&accessible));
+    EXPECT_EQ(S_OK, hr);
+    ScopedBstr name;
+    EXPECT_EQ(S_OK, accessible->get_accName(SELF, name.Receive()));
+    EXPECT_STREQ(expected_name, name);
+  }
+
+  void CheckIUnknownHasName(ScopedComPtr<IUnknown> unknown,
+                            const wchar_t* expected_name) {
+    ScopedComPtr<IAccessible2> accessible = ToIAccessible2(unknown);
+    ASSERT_NE(nullptr, accessible);
+
+    ScopedBstr name;
+    EXPECT_EQ(S_OK, accessible->get_accName(SELF, name.Receive()));
+    EXPECT_STREQ(expected_name, name);
+  }
+
+  void Build3X3Table() {
+    /*
+      Build a table the looks like:
+
+      ----------------------        (A) Column Header
+      |        | (A) | (B) |        (B) Column Header
+      ----------------------        (C) Row Header
+      |  (C)  |  1  |  2   |        (D) Row Header
+      ----------------------
+      |  (D)  |  3  |  4   |
+      ----------------------
+   */
+
+    AXNodeData table;
+    table.id = 0;
+    table.role = ui::AX_ROLE_TABLE;
+
+    table.AddIntAttribute(AX_ATTR_TABLE_ROW_COUNT, 3);
+    table.AddIntAttribute(AX_ATTR_TABLE_COLUMN_COUNT, 3);
+
+    // Ordering in this list matters.  It is used in the calculation
+    // of where cells are by the following:
+    // int position = row * GetTableColumnCount() + column;
+
+    std::vector<int32_t> ids{51, 52, 53, 2, 3, 4, 11, 12, 13};
+    table.AddIntListAttribute(AX_ATTR_CELL_IDS, ids);
+    table.AddIntListAttribute(AX_ATTR_UNIQUE_CELL_IDS, ids);
+
+    table.child_ids.push_back(50);  // Header
+    table.child_ids.push_back(1);   // Row 1
+    table.child_ids.push_back(10);  // Row 2
+
+    // Table column header
+    AXNodeData table_row_header;
+    table_row_header.id = 50;
+    table_row_header.role = ui::AX_ROLE_ROW;
+    table_row_header.child_ids.push_back(51);
+    table_row_header.child_ids.push_back(52);
+    table_row_header.child_ids.push_back(53);
+
+    AXNodeData table_column_header_1;
+    table_column_header_1.id = 51;
+    table_column_header_1.role = ui::AX_ROLE_COLUMN_HEADER;
+
+    AXNodeData table_column_header_2;
+    table_column_header_2.id = 52;
+    table_column_header_2.role = ui::AX_ROLE_COLUMN_HEADER;
+    table_column_header_2.AddStringAttribute(AX_ATTR_NAME, "column header 1");
+
+    AXNodeData table_column_header_3;
+    table_column_header_3.id = 53;
+    table_column_header_3.role = ui::AX_ROLE_COLUMN_HEADER;
+    // Either AX_ATTR_NAME -or- AX_ATTR_DESCRIPTION is acceptable for a
+    // description
+    table_column_header_3.AddStringAttribute(AX_ATTR_DESCRIPTION,
+                                             "column header 2");
+
+    // Row 1
+    AXNodeData table_row_1;
+    table_row_1.id = 1;
+    table_row_1.role = ui::AX_ROLE_ROW;
+    table_row_1.child_ids.push_back(2);
+    table_row_1.child_ids.push_back(3);
+    table_row_1.child_ids.push_back(4);
+
+    AXNodeData table_row_header_1;
+    table_row_header_1.id = 2;
+    table_row_header_1.role = ui::AX_ROLE_ROW_HEADER;
+    table_row_header_1.AddStringAttribute(AX_ATTR_NAME, "row header 1");
+
+    AXNodeData table_cell_1;
+    table_cell_1.id = 3;
+    table_cell_1.role = ui::AX_ROLE_CELL;
+    table_cell_1.AddStringAttribute(AX_ATTR_NAME, "1");
+
+    AXNodeData table_cell_2;
+    table_cell_2.id = 4;
+    table_cell_2.role = ui::AX_ROLE_CELL;
+    table_cell_2.AddStringAttribute(AX_ATTR_NAME, "2");
+
+    // Row 2
+    AXNodeData table_row_2;
+    table_row_2.id = 10;
+    table_row_2.role = ui::AX_ROLE_ROW;
+    table_row_2.child_ids.push_back(11);
+    table_row_2.child_ids.push_back(12);
+    table_row_2.child_ids.push_back(13);
+
+    AXNodeData table_row_header_2;
+    table_row_header_2.id = 11;
+    table_row_header_2.role = ui::AX_ROLE_ROW_HEADER;
+    // Either AX_ATTR_NAME -or- AX_ATTR_DESCRIPTION is acceptable for a
+    // description
+    table_row_header_2.AddStringAttribute(AX_ATTR_DESCRIPTION, "row header 2");
+
+    AXNodeData table_cell_3;
+    table_cell_3.id = 12;
+    table_cell_3.role = ui::AX_ROLE_CELL;
+    table_cell_3.AddStringAttribute(AX_ATTR_NAME, "3");
+
+    AXNodeData table_cell_4;
+    table_cell_4.id = 13;
+    table_cell_4.role = ui::AX_ROLE_CELL;
+    table_cell_4.AddStringAttribute(AX_ATTR_NAME, "4");
+
+    AXTreeUpdate update;
+    update.root_id = table.id;
+
+    update.nodes.push_back(table);
+
+    update.nodes.push_back(table_row_header);
+    update.nodes.push_back(table_column_header_1);
+    update.nodes.push_back(table_column_header_2);
+    update.nodes.push_back(table_column_header_3);
+
+    update.nodes.push_back(table_row_1);
+    update.nodes.push_back(table_row_header_1);
+    update.nodes.push_back(table_cell_1);
+    update.nodes.push_back(table_cell_2);
+
+    update.nodes.push_back(table_row_2);
+    update.nodes.push_back(table_row_header_2);
+    update.nodes.push_back(table_cell_3);
+    update.nodes.push_back(table_cell_4);
+
+    Init(update);
+  }
+
+  ScopedComPtr<IAccessibleTableCell> GetCellInTable() {
+    ScopedComPtr<IAccessible> root_obj(GetRootIAccessible());
+
+    ScopedComPtr<IAccessibleTable2> table;
+    root_obj.CopyTo(table.GetAddressOf());
+    if (!table)
+      return ScopedComPtr<IAccessibleTableCell>();
+
+    ScopedComPtr<IUnknown> cell;
+    table->get_cellAt(1, 1, cell.GetAddressOf());
+    if (!cell)
+      return ScopedComPtr<IAccessibleTableCell>();
+
+    ScopedComPtr<IAccessibleTableCell> table_cell;
+    cell.CopyTo(table_cell.GetAddressOf());
+    return table_cell;
+  }
+
   std::unique_ptr<AXTree> tree_;
 };
 
@@ -167,13 +344,7 @@
   EXPECT_EQ(S_OK, root_obj->accHitTest(5, 5, obj.Receive()));
   ASSERT_NE(nullptr, obj.ptr());
 
-  // We got something back, make sure that it has the correct name.
-  base::win::ScopedComPtr<IAccessible> accessible;
-  HRESULT hr = V_DISPATCH(obj.ptr())->QueryInterface(IID_PPV_ARGS(&accessible));
-  EXPECT_EQ(S_OK, hr);
-  ScopedBstr name;
-  EXPECT_EQ(S_OK, accessible->get_accName(SELF, name.Receive()));
-  EXPECT_STREQ(L"Name1", name);
+  CheckVariantHasName(obj, L"Name1");
 }
 
 TEST_F(AXPlatformNodeWinTest, TestIAccessibleName) {
@@ -321,14 +492,7 @@
   EXPECT_EQ(S_OK, root_obj->get_accSelection(selection.Receive()));
   ASSERT_NE(nullptr, selection.ptr());
 
-  // We got something back, make sure that it has the correct name.
-  base::win::ScopedComPtr<IAccessible> accessible;
-  HRESULT hr =
-      V_DISPATCH(selection.ptr())->QueryInterface(IID_PPV_ARGS(&accessible));
-  EXPECT_EQ(S_OK, hr);
-  ScopedBstr name;
-  EXPECT_EQ(S_OK, accessible->get_accName(SELF, name.Receive()));
-  EXPECT_STREQ(L"Name2", name);
+  CheckVariantHasName(selection, L"Name2");
 }
 
 TEST_F(AXPlatformNodeWinTest, TestIAccessibleSelectionMultipleSelected) {
@@ -366,8 +530,8 @@
   EXPECT_EQ(S_OK, root_obj->get_accSelection(selection.Receive()));
   ASSERT_NE(nullptr, selection.ptr());
 
-  // We got something back, make sure that it has the corrent name.
-  base::win::ScopedComPtr<IEnumVARIANT> accessibles;
+  // Loop through the selections and  make sure we have the right ones
+  ScopedComPtr<IEnumVARIANT> accessibles;
   HRESULT hr =
       V_DISPATCH(selection.ptr())->QueryInterface(IID_PPV_ARGS(&accessibles));
   EXPECT_EQ(S_OK, hr);
@@ -379,7 +543,7 @@
     hr = accessibles->Next(1, item.Receive(), &ignore);
     EXPECT_EQ(S_OK, hr);
 
-    base::win::ScopedComPtr<IAccessible> accessible;
+    ScopedComPtr<IAccessible> accessible;
     HRESULT hr =
         V_DISPATCH(item.ptr())->QueryInterface(IID_PPV_ARGS(&accessible));
     EXPECT_EQ(S_OK, hr);
@@ -394,7 +558,7 @@
     hr = accessibles->Next(1, item.Receive(), &ignore);
     EXPECT_EQ(S_OK, hr);
 
-    base::win::ScopedComPtr<IAccessible> accessible;
+    ScopedComPtr<IAccessible> accessible;
     HRESULT hr =
         V_DISPATCH(item.ptr())->QueryInterface(IID_PPV_ARGS(&accessible));
     EXPECT_EQ(S_OK, hr);
@@ -737,4 +901,396 @@
   EXPECT_HRESULT_FAILED(text_field->setSelection(0, 0, 5));
 }
 
+TEST_F(AXPlatformNodeWinTest, TestIAccessibleTableGetAccessibilityAt) {
+  Build3X3Table();
+
+  ScopedComPtr<IAccessible> root_obj(GetRootIAccessible());
+
+  ScopedComPtr<IAccessibleTable> result;
+  root_obj.CopyTo(result.GetAddressOf());
+  ASSERT_NE(nullptr, result);
+
+  ScopedComPtr<IUnknown> cell_1;
+  EXPECT_EQ(S_OK, result->get_accessibleAt(1, 1, cell_1.GetAddressOf()));
+  CheckIUnknownHasName(cell_1, L"1");
+
+  ScopedComPtr<IUnknown> cell_2;
+  EXPECT_EQ(S_OK, result->get_accessibleAt(1, 2, cell_2.GetAddressOf()));
+  CheckIUnknownHasName(cell_2, L"2");
+
+  ScopedComPtr<IUnknown> cell_3;
+  EXPECT_EQ(S_OK, result->get_accessibleAt(2, 1, cell_3.GetAddressOf()));
+  CheckIUnknownHasName(cell_3, L"3");
+
+  ScopedComPtr<IUnknown> cell_4;
+  EXPECT_EQ(S_OK, result->get_accessibleAt(2, 2, cell_4.GetAddressOf()));
+  CheckIUnknownHasName(cell_4, L"4");
+}
+
+TEST_F(AXPlatformNodeWinTest,
+       TestIAccessibleTableGetAccessibilityAtOutOfBounds) {
+  Build3X3Table();
+
+  ScopedComPtr<IAccessible> root_obj(GetRootIAccessible());
+
+  ScopedComPtr<IAccessibleTable> result;
+  root_obj.CopyTo(result.GetAddressOf());
+  ASSERT_NE(nullptr, result);
+
+  {
+    ScopedComPtr<IUnknown> cell;
+    EXPECT_EQ(E_INVALIDARG,
+              result->get_accessibleAt(-1, -1, cell.GetAddressOf()));
+  }
+
+  {
+    ScopedComPtr<IUnknown> cell;
+    EXPECT_EQ(E_INVALIDARG,
+              result->get_accessibleAt(0, 5, cell.GetAddressOf()));
+  }
+
+  {
+    ScopedComPtr<IUnknown> cell;
+    EXPECT_EQ(E_INVALIDARG,
+              result->get_accessibleAt(5, 0, cell.GetAddressOf()));
+  }
+
+  {
+    ScopedComPtr<IUnknown> cell;
+    EXPECT_EQ(E_INVALIDARG,
+              result->get_accessibleAt(10, 10, cell.GetAddressOf()));
+  }
+}
+
+TEST_F(AXPlatformNodeWinTest, TestIAccessibleTableGetChildIndex) {
+  Build3X3Table();
+
+  ScopedComPtr<IAccessible> root_obj(GetRootIAccessible());
+
+  ScopedComPtr<IAccessibleTable> result;
+  root_obj.CopyTo(result.GetAddressOf());
+  ASSERT_NE(nullptr, result);
+
+  long id;
+  EXPECT_EQ(S_OK, result->get_childIndex(0, 0, &id));
+  EXPECT_EQ(id, 0);
+
+  EXPECT_EQ(S_OK, result->get_childIndex(0, 1, &id));
+  EXPECT_EQ(id, 1);
+
+  EXPECT_EQ(S_OK, result->get_childIndex(1, 0, &id));
+  EXPECT_EQ(id, 3);
+
+  EXPECT_EQ(S_OK, result->get_childIndex(1, 1, &id));
+  EXPECT_EQ(id, 4);
+
+  EXPECT_EQ(E_INVALIDARG, result->get_childIndex(-1, -1, &id));
+  EXPECT_EQ(E_INVALIDARG, result->get_childIndex(0, 5, &id));
+  EXPECT_EQ(E_INVALIDARG, result->get_childIndex(5, 0, &id));
+  EXPECT_EQ(E_INVALIDARG, result->get_childIndex(5, 5, &id));
+}
+
+TEST_F(AXPlatformNodeWinTest, TestIAccessibleTableGetColumnDescription) {
+  Build3X3Table();
+
+  ScopedComPtr<IAccessible> root_obj(GetRootIAccessible());
+
+  ScopedComPtr<IAccessibleTable> result;
+  root_obj.CopyTo(result.GetAddressOf());
+  ASSERT_NE(nullptr, result);
+
+  {
+    ScopedBstr name;
+    EXPECT_EQ(S_FALSE, result->get_columnDescription(0, name.Receive()));
+  }
+  {
+    ScopedBstr name;
+    EXPECT_EQ(S_OK, result->get_columnDescription(1, name.Receive()));
+    EXPECT_STREQ(L"column header 1", name);
+  }
+
+  {
+    ScopedBstr name;
+    EXPECT_EQ(S_OK, result->get_columnDescription(2, name.Receive()));
+    EXPECT_STREQ(L"column header 2", name);
+  }
+}
+
+TEST_F(AXPlatformNodeWinTest, TestIAccessibleTableGetColumnExtentAt) {
+  // TODO(dougt) This table doesn't have any spanning cells. This test
+  // tests get_columnExtentAt for (1) and an invalid input.
+  Build3X3Table();
+
+  ScopedComPtr<IAccessible> root_obj(GetRootIAccessible());
+
+  ScopedComPtr<IAccessibleTable> result;
+  root_obj.CopyTo(result.GetAddressOf());
+  ASSERT_NE(nullptr, result);
+
+  long columns_spanned;
+  EXPECT_EQ(S_OK, result->get_columnExtentAt(1, 1, &columns_spanned));
+  EXPECT_EQ(columns_spanned, 1);
+
+  EXPECT_EQ(E_INVALIDARG, result->get_columnExtentAt(-1, -1, &columns_spanned));
+}
+
+TEST_F(AXPlatformNodeWinTest, TestIAccessibleTableGetColumnIndex) {
+  Build3X3Table();
+
+  ScopedComPtr<IAccessible> root_obj(GetRootIAccessible());
+
+  ScopedComPtr<IAccessibleTable> result;
+  root_obj.CopyTo(result.GetAddressOf());
+  ASSERT_NE(nullptr, result);
+
+  long index;
+  EXPECT_EQ(S_OK, result->get_columnIndex(1, &index));
+  EXPECT_EQ(index, 0);
+
+  EXPECT_EQ(E_INVALIDARG, result->get_columnIndex(-1, &index));
+}
+
+TEST_F(AXPlatformNodeWinTest, TestIAccessibleTableGetNColumns) {
+  Build3X3Table();
+
+  ScopedComPtr<IAccessible> root_obj(GetRootIAccessible());
+
+  ScopedComPtr<IAccessibleTable> result;
+  root_obj.CopyTo(result.GetAddressOf());
+  ASSERT_NE(nullptr, result);
+
+  long count;
+  EXPECT_EQ(S_OK, result->get_nColumns(&count));
+  EXPECT_EQ(count, 3);
+}
+
+TEST_F(AXPlatformNodeWinTest, TestIAccessibleTableGetNRows) {
+  Build3X3Table();
+
+  ScopedComPtr<IAccessible> root_obj(GetRootIAccessible());
+
+  ScopedComPtr<IAccessibleTable> result;
+  root_obj.CopyTo(result.GetAddressOf());
+  ASSERT_NE(nullptr, result);
+
+  long count;
+  EXPECT_EQ(S_OK, result->get_nRows(&count));
+  EXPECT_EQ(count, 3);
+}
+
+TEST_F(AXPlatformNodeWinTest, TestIAccessibleTableGetRowDescription) {
+  Build3X3Table();
+
+  ScopedComPtr<IAccessible> root_obj(GetRootIAccessible());
+
+  ScopedComPtr<IAccessibleTable> result;
+  root_obj.CopyTo(result.GetAddressOf());
+  ASSERT_NE(nullptr, result);
+
+  {
+    ScopedBstr name;
+    EXPECT_EQ(S_FALSE, result->get_rowDescription(0, name.Receive()));
+  }
+  {
+    ScopedBstr name;
+    EXPECT_EQ(S_OK, result->get_rowDescription(1, name.Receive()));
+    EXPECT_STREQ(L"row header 1", name);
+  }
+
+  {
+    ScopedBstr name;
+    EXPECT_EQ(S_OK, result->get_rowDescription(2, name.Receive()));
+    EXPECT_STREQ(L"row header 2", name);
+  }
+}
+
+TEST_F(AXPlatformNodeWinTest, TestIAccessibleTableGetRowExtentAt) {
+  // TODO(dougt) This table doesn't have any spanning cells. This test
+  // tests get_rowExtentAt for (1) and an invalid input.
+  Build3X3Table();
+
+  ScopedComPtr<IAccessible> root_obj(GetRootIAccessible());
+
+  ScopedComPtr<IAccessibleTable> result;
+  root_obj.CopyTo(result.GetAddressOf());
+  ASSERT_NE(nullptr, result);
+
+  long rows_spanned;
+  EXPECT_EQ(S_OK, result->get_rowExtentAt(0, 1, &rows_spanned));
+  EXPECT_EQ(rows_spanned, 0);
+
+  EXPECT_EQ(E_INVALIDARG, result->get_columnExtentAt(-1, -1, &rows_spanned));
+}
+
+TEST_F(AXPlatformNodeWinTest, TestIAccessibleTableGetRowIndex) {
+  Build3X3Table();
+
+  ScopedComPtr<IAccessible> root_obj(GetRootIAccessible());
+
+  ScopedComPtr<IAccessibleTable> result;
+  root_obj.CopyTo(result.GetAddressOf());
+  ASSERT_NE(nullptr, result);
+
+  long index;
+  EXPECT_EQ(S_OK, result->get_rowIndex(1, &index));
+  EXPECT_EQ(index, 0);
+
+  EXPECT_EQ(E_INVALIDARG, result->get_rowIndex(-1, &index));
+}
+
+TEST_F(AXPlatformNodeWinTest, TestIAccessibleTableGetRowColumnExtentsAtIndex) {
+  Build3X3Table();
+
+  ScopedComPtr<IAccessible> root_obj(GetRootIAccessible());
+
+  ScopedComPtr<IAccessibleTable> result;
+  root_obj.CopyTo(result.GetAddressOf());
+  ASSERT_NE(nullptr, result);
+
+  long row, column, row_extents, column_extents;
+  boolean is_selected;
+  EXPECT_EQ(S_OK,
+            result->get_rowColumnExtentsAtIndex(0, &row, &column, &row_extents,
+                                                &column_extents, &is_selected));
+
+  EXPECT_EQ(row, 0);
+  EXPECT_EQ(column, 0);
+  EXPECT_EQ(row_extents, 0);
+  EXPECT_EQ(column_extents, 0);
+
+  EXPECT_EQ(E_INVALIDARG,
+            result->get_rowColumnExtentsAtIndex(-1, &row, &column, &row_extents,
+                                                &column_extents, &is_selected));
+}
+
+TEST_F(AXPlatformNodeWinTest, TestIAccessibleTableGetCellAt) {
+  Build3X3Table();
+
+  ScopedComPtr<IAccessible> root_obj(GetRootIAccessible());
+
+  ScopedComPtr<IAccessibleTable2> result;
+  root_obj.CopyTo(result.GetAddressOf());
+  ASSERT_NE(nullptr, result);
+
+  {
+    ScopedComPtr<IUnknown> cell;
+    EXPECT_EQ(S_OK, result->get_cellAt(1, 1, cell.GetAddressOf()));
+    CheckIUnknownHasName(cell, L"1");
+  }
+
+  {
+    ScopedComPtr<IUnknown> cell;
+    EXPECT_EQ(E_INVALIDARG, result->get_cellAt(-1, -1, cell.GetAddressOf()));
+  }
+}
+
+TEST_F(AXPlatformNodeWinTest, TestIAccessibleTableCellGetColumnExtent) {
+  Build3X3Table();
+
+  ScopedComPtr<IAccessibleTableCell> cell = GetCellInTable();
+  ASSERT_NE(nullptr, cell);
+
+  long column_spanned;
+  EXPECT_EQ(S_OK, cell->get_columnExtent(&column_spanned));
+  EXPECT_EQ(column_spanned, 1);
+}
+
+TEST_F(AXPlatformNodeWinTest, TestIAccessibleTableCellGetColumnHeaderCells) {
+  Build3X3Table();
+
+  ScopedComPtr<IAccessibleTableCell> cell = GetCellInTable();
+  ASSERT_NE(nullptr, cell);
+
+  IUnknown** cell_accessibles;
+
+  long number_cells;
+  EXPECT_EQ(S_OK,
+            cell->get_columnHeaderCells(&cell_accessibles, &number_cells));
+  EXPECT_EQ(number_cells, 1);
+}
+
+TEST_F(AXPlatformNodeWinTest, TestIAccessibleTableCellGetColumnIndex) {
+  Build3X3Table();
+
+  ScopedComPtr<IAccessibleTableCell> cell = GetCellInTable();
+  ASSERT_NE(nullptr, cell);
+
+  long index;
+  EXPECT_EQ(S_OK, cell->get_columnIndex(&index));
+  EXPECT_EQ(index, 0);
+}
+
+TEST_F(AXPlatformNodeWinTest, TestIAccessibleTableCellGetRowExtent) {
+  Build3X3Table();
+
+  ScopedComPtr<IAccessibleTableCell> cell = GetCellInTable();
+  ASSERT_NE(nullptr, cell);
+
+  long rows_spanned;
+  EXPECT_EQ(S_OK, cell->get_rowExtent(&rows_spanned));
+  EXPECT_EQ(rows_spanned, 1);
+}
+
+TEST_F(AXPlatformNodeWinTest, TestIAccessibleTableCellGetRowHeaderCells) {
+  Build3X3Table();
+
+  ScopedComPtr<IAccessibleTableCell> cell = GetCellInTable();
+  ASSERT_NE(nullptr, cell);
+
+  IUnknown** cell_accessibles;
+
+  long number_cells;
+  EXPECT_EQ(S_OK, cell->get_rowHeaderCells(&cell_accessibles, &number_cells));
+
+  // Since we do not have AX_ATTR_TABLE_CELL_ROW_INDEX set, the evaluated row
+  // will be 0.  In this case, we do not expect any row headers.
+  EXPECT_EQ(number_cells, 0);
+}
+
+TEST_F(AXPlatformNodeWinTest, TestIAccessibleTableCellGetRowIndex) {
+  Build3X3Table();
+
+  ScopedComPtr<IAccessibleTableCell> cell = GetCellInTable();
+  ASSERT_NE(nullptr, cell);
+
+  long index;
+  EXPECT_EQ(S_OK, cell->get_rowIndex(&index));
+  EXPECT_EQ(index, 0);
+}
+
+TEST_F(AXPlatformNodeWinTest, TestIAccessibleTableCellGetRowColumnExtent) {
+  Build3X3Table();
+
+  ScopedComPtr<IAccessibleTableCell> cell = GetCellInTable();
+  ASSERT_NE(nullptr, cell);
+
+  long row, column, row_extents, column_extents;
+  boolean is_selected;
+  EXPECT_EQ(S_OK, cell->get_rowColumnExtents(&row, &column, &row_extents,
+                                             &column_extents, &is_selected));
+  EXPECT_EQ(row, 0);
+  EXPECT_EQ(column, 0);
+  EXPECT_EQ(row_extents, 1);
+  EXPECT_EQ(column_extents, 1);
+}
+
+TEST_F(AXPlatformNodeWinTest, TestIAccessibleTableCellGetTable) {
+  Build3X3Table();
+
+  ScopedComPtr<IAccessibleTableCell> cell = GetCellInTable();
+  ASSERT_NE(nullptr, cell);
+
+  ScopedComPtr<IUnknown> table;
+  EXPECT_EQ(S_OK, cell->get_table(table.GetAddressOf()));
+
+  ScopedComPtr<IAccessibleTable> result;
+  table.CopyTo(result.GetAddressOf());
+  ASSERT_NE(nullptr, result);
+
+  // Check to make sure that this is the right table by checking one cell.
+  ScopedComPtr<IUnknown> cell_1;
+  EXPECT_EQ(S_OK, result->get_accessibleAt(1, 1, cell_1.GetAddressOf()));
+  CheckIUnknownHasName(cell_1, L"1");
+}
+
 }  // namespace ui
diff --git a/ui/accessibility/platform/test_ax_node_wrapper.cc b/ui/accessibility/platform/test_ax_node_wrapper.cc
index d1e33c7..e0b153ef 100644
--- a/ui/accessibility/platform/test_ax_node_wrapper.cc
+++ b/ui/accessibility/platform/test_ax_node_wrapper.cc
@@ -141,7 +141,28 @@
   return nullptr;
 }
 
+// Walk the AXTree and ensure that all wrappers are created
+void TestAXNodeWrapper::BuildAllWrappers(AXTree* tree, AXNode* node) {
+  for (int i = 0; i < node->child_count(); i++) {
+    auto* child = node->children()[i];
+    TestAXNodeWrapper::GetOrCreate(tree, child);
+
+    BuildAllWrappers(tree, child);
+  }
+}
+
 AXPlatformNode* TestAXNodeWrapper::GetFromNodeID(int32_t id) {
+  // Force creating all of the wrappers for this tree.
+  BuildAllWrappers(tree_, node_);
+
+  for (auto it = g_node_to_wrapper_map.begin();
+       it != g_node_to_wrapper_map.end(); ++it) {
+    AXNode* node = it->first;
+    if (node->id() == id) {
+      TestAXNodeWrapper* wrapper = it->second;
+      return wrapper->ax_platform_node();
+    }
+  }
   return nullptr;
 }
 
diff --git a/ui/accessibility/platform/test_ax_node_wrapper.h b/ui/accessibility/platform/test_ax_node_wrapper.h
index 1e6da236..9ce258e3 100644
--- a/ui/accessibility/platform/test_ax_node_wrapper.h
+++ b/ui/accessibility/platform/test_ax_node_wrapper.h
@@ -28,6 +28,8 @@
 
   AXPlatformNode* ax_platform_node() { return platform_node_; }
 
+  void BuildAllWrappers(AXTree* tree, AXNode* node);
+
   // AXPlatformNodeDelegate.
   const AXNodeData& GetData() const override;
   const ui::AXTreeData& GetTreeData() const override;
diff --git a/ui/arc/notification/arc_notification_delegate.cc b/ui/arc/notification/arc_notification_delegate.cc
index fae1302..d13c3446 100644
--- a/ui/arc/notification/arc_notification_delegate.cc
+++ b/ui/arc/notification/arc_notification_delegate.cc
@@ -51,4 +51,9 @@
   return true;
 }
 
+bool ArcNotificationDelegate::ShouldDisplaySettingsButton() {
+  DCHECK(item_);
+  return item_->IsOpeningSettingsSupported();
+}
+
 }  // namespace arc
diff --git a/ui/arc/notification/arc_notification_delegate.h b/ui/arc/notification/arc_notification_delegate.h
index 9241fcec..48df295 100644
--- a/ui/arc/notification/arc_notification_delegate.h
+++ b/ui/arc/notification/arc_notification_delegate.h
@@ -33,6 +33,7 @@
   void Close(bool by_user) override;
   void Click() override;
   bool SettingsClick() override;
+  bool ShouldDisplaySettingsButton() override;
 
  private:
   // The destructor is private since this class is ref-counted.
diff --git a/ui/keyboard/content/keyboard_ui_content_unittest.cc b/ui/keyboard/content/keyboard_ui_content_unittest.cc
index bbac466c..b9f295d 100644
--- a/ui/keyboard/content/keyboard_ui_content_unittest.cc
+++ b/ui/keyboard/content/keyboard_ui_content_unittest.cc
@@ -22,7 +22,6 @@
   ~TestKeyboardUIContent() override {}
 
   ui::InputMethod* GetInputMethod() override { return nullptr; }
-  void SetUpdateInputType(ui::TextInputType type) override {}
   void RequestAudioInput(
       content::WebContents* web_contents,
       const content::MediaStreamRequest& request,
diff --git a/ui/keyboard/keyboard_controller.cc b/ui/keyboard/keyboard_controller.cc
index 3951c2a4..61cd6cb 100644
--- a/ui/keyboard/keyboard_controller.cc
+++ b/ui/keyboard/keyboard_controller.cc
@@ -541,7 +541,6 @@
       keyboard_visible_ = true;
       ChangeState(KeyboardControllerState::SHOWN);
     }
-    ui_->SetUpdateInputType(type);
     // Do not explicitly show the Virtual keyboard unless it is in the process
     // of hiding. Instead, the virtual keyboard is shown in response to a user
     // gesture (mouse or touch) that is received while an element has input
diff --git a/ui/keyboard/keyboard_controller_unittest.cc b/ui/keyboard/keyboard_controller_unittest.cc
index 5822a8f..c3ba4cc 100644
--- a/ui/keyboard/keyboard_controller_unittest.cc
+++ b/ui/keyboard/keyboard_controller_unittest.cc
@@ -135,7 +135,6 @@
     return window_.get();
   }
   ui::InputMethod* GetInputMethod() override { return input_method_; }
-  void SetUpdateInputType(ui::TextInputType type) override {}
   void ReloadKeyboardIfNeeded() override {}
   void InitInsets(const gfx::Rect& keyboard_bounds) override {}
   void ResetInsets() override {}
diff --git a/ui/keyboard/keyboard_ui.h b/ui/keyboard/keyboard_ui.h
index b4fc21c6..8399c85 100644
--- a/ui/keyboard/keyboard_ui.h
+++ b/ui/keyboard/keyboard_ui.h
@@ -55,9 +55,6 @@
   // necesasry animation, or delay the visibility change as it desires.
   virtual void HideKeyboardContainer(aura::Window* container);
 
-  // Updates the type of the focused text input box.
-  virtual void SetUpdateInputType(ui::TextInputType type) = 0;
-
   // Ensures caret in current work area (not occluded by virtual keyboard
   // window).
   virtual void EnsureCaretInWorkArea();
diff --git a/ui/views/controls/button/checkbox.cc b/ui/views/controls/button/checkbox.cc
index a21d406d..d3535f0 100644
--- a/ui/views/controls/button/checkbox.cc
+++ b/ui/views/controls/button/checkbox.cc
@@ -14,9 +14,11 @@
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/color_utils.h"
 #include "ui/gfx/paint_vector_icon.h"
+#include "ui/native_theme/native_theme.h"
 #include "ui/views/animation/ink_drop_impl.h"
 #include "ui/views/animation/ink_drop_ripple.h"
 #include "ui/views/controls/button/label_button_border.h"
+#include "ui/views/controls/focus_ring.h"
 #include "ui/views/layout/layout_provider.h"
 #include "ui/views/painter.h"
 #include "ui/views/resources/grit/views_resources.h"
@@ -25,6 +27,45 @@
 
 namespace views {
 
+// View used to paint the focus ring around the Checkbox icon.
+// The icon is painted separately.
+class IconFocusRing : public View {
+ public:
+  explicit IconFocusRing(Checkbox* checkbox);
+  ~IconFocusRing() override = default;
+
+ private:
+  // View:
+  void Layout() override;
+  void OnPaint(gfx::Canvas* canvas) override;
+
+  Checkbox* checkbox_;
+
+  DISALLOW_COPY_AND_ASSIGN(IconFocusRing);
+};
+
+IconFocusRing::IconFocusRing(Checkbox* checkbox) : checkbox_(checkbox) {
+  FocusRing::InitFocusRing(this);
+}
+
+void IconFocusRing::Layout() {
+  gfx::Rect focus_bounds = checkbox_->image()->bounds();
+  focus_bounds.Inset(gfx::Insets(-2.f));
+  SetBoundsRect(focus_bounds);
+}
+
+void IconFocusRing::OnPaint(gfx::Canvas* canvas) {
+  cc::PaintFlags focus_flags;
+  focus_flags.setAntiAlias(true);
+  focus_flags.setColor(
+      SkColorSetA(GetNativeTheme()->GetSystemColor(
+                      ui::NativeTheme::kColorId_FocusedBorderColor),
+                  0x66));
+  focus_flags.setStyle(cc::PaintFlags::kStroke_Style);
+  focus_flags.setStrokeWidth(2);
+  checkbox_->PaintFocusRing(this, canvas, focus_flags);
+}
+
 // static
 const char Checkbox::kViewClassName[] = "Checkbox";
 
@@ -39,6 +80,9 @@
     set_request_focus_on_press(false);
     SetInkDropMode(InkDropMode::ON);
     set_has_ink_drop_action_on_click(true);
+    focus_ring_ = new IconFocusRing(this);
+    focus_ring_->SetVisible(false);
+    AddChildView(focus_ring_);
   } else {
     std::unique_ptr<LabelButtonBorder> button_border(new LabelButtonBorder());
     // Inset the trailing side by a couple pixels for the focus border.
@@ -128,12 +172,16 @@
   LabelButton::OnFocus();
   if (!UseMd())
     UpdateImage();
+  else
+    focus_ring_->SetVisible(true);
 }
 
 void Checkbox::OnBlur() {
   LabelButton::OnBlur();
   if (!UseMd())
     UpdateImage();
+  else
+    focus_ring_->SetVisible(false);
 }
 
 void Checkbox::OnNativeThemeChanged(const ui::NativeTheme* theme) {
@@ -161,21 +209,6 @@
       ui::NativeTheme::kColorId_LabelEnabledColor);
 }
 
-void Checkbox::PaintButtonContents(gfx::Canvas* canvas) {
-  if (!UseMd() || !HasFocus())
-    return;
-
-  cc::PaintFlags focus_flags;
-  focus_flags.setAntiAlias(true);
-  focus_flags.setColor(
-      SkColorSetA(GetNativeTheme()->GetSystemColor(
-                      ui::NativeTheme::kColorId_FocusedBorderColor),
-                  0x66));
-  focus_flags.setStyle(cc::PaintFlags::kStroke_Style);
-  focus_flags.setStrokeWidth(2);
-  PaintFocusRing(canvas, focus_flags);
-}
-
 gfx::ImageSkia Checkbox::GetImage(ButtonState for_state) const {
   if (UseMd()) {
     return gfx::CreateVectorIcon(
@@ -212,10 +245,10 @@
   UpdateImage();
 }
 
-void Checkbox::PaintFocusRing(gfx::Canvas* canvas,
+void Checkbox::PaintFocusRing(View* view,
+                              gfx::Canvas* canvas,
                               const cc::PaintFlags& flags) {
-  gfx::RectF focus_rect(image()->bounds());
-  canvas->DrawRoundRect(focus_rect, 2.f, flags);
+  canvas->DrawRoundRect(view->GetLocalBounds(), 2.f, flags);
 }
 
 const gfx::VectorIcon& Checkbox::GetVectorIcon() const {
diff --git a/ui/views/controls/button/checkbox.h b/ui/views/controls/button/checkbox.h
index 9622387..deb26a2 100644
--- a/ui/views/controls/button/checkbox.h
+++ b/ui/views/controls/button/checkbox.h
@@ -49,7 +49,6 @@
   std::unique_ptr<InkDrop> CreateInkDrop() override;
   std::unique_ptr<InkDropRipple> CreateInkDropRipple() const override;
   SkColor GetInkDropBaseColor() const override;
-  void PaintButtonContents(gfx::Canvas* canvas) override;
   gfx::ImageSkia GetImage(ButtonState for_state) const override;
   std::unique_ptr<LabelButtonBorder> CreateDefaultBorder() const override;
 
@@ -60,13 +59,17 @@
                       ButtonState for_state,
                       const gfx::ImageSkia& image);
 
-  // Paints a focus indicator for the view.
-  virtual void PaintFocusRing(gfx::Canvas* canvas, const cc::PaintFlags& flags);
+  // Paints a focus indicator for the view. Overridden in RadioButton.
+  virtual void PaintFocusRing(View* view,
+                              gfx::Canvas* canvas,
+                              const cc::PaintFlags& flags);
 
   // Gets the vector icon to use based on the current state of |checked_|.
   virtual const gfx::VectorIcon& GetVectorIcon() const;
 
  private:
+  friend class IconFocusRing;
+
   // Button:
   void NotifyClick(const ui::Event& event) override;
 
@@ -76,6 +79,9 @@
   // True if the checkbox is checked.
   bool checked_;
 
+  // FocusRing used in MD mode
+  View* focus_ring_ = nullptr;
+
   // The images for each button node_data.
   gfx::ImageSkia images_[2][2][STATE_COUNT];
 
diff --git a/ui/views/controls/button/radio_button.cc b/ui/views/controls/button/radio_button.cc
index ff6aaf60..aea68cf 100644
--- a/ui/views/controls/button/radio_button.cc
+++ b/ui/views/controls/button/radio_button.cc
@@ -138,9 +138,10 @@
   Checkbox::SetChecked(checked);
 }
 
-void RadioButton::PaintFocusRing(gfx::Canvas* canvas,
+void RadioButton::PaintFocusRing(View* view,
+                                 gfx::Canvas* canvas,
                                  const cc::PaintFlags& flags) {
-  canvas->DrawCircle(gfx::RectF(image()->bounds()).CenterPoint(),
+  canvas->DrawCircle(gfx::RectF(view->GetLocalBounds()).CenterPoint(),
                      image()->width() / 2, flags);
 }
 
diff --git a/ui/views/controls/button/radio_button.h b/ui/views/controls/button/radio_button.h
index 5117b69..8ce77391 100644
--- a/ui/views/controls/button/radio_button.h
+++ b/ui/views/controls/button/radio_button.h
@@ -35,7 +35,8 @@
 
   // Overridden from Checkbox:
   void SetChecked(bool checked) override;
-  void PaintFocusRing(gfx::Canvas* canvas,
+  void PaintFocusRing(View* view,
+                      gfx::Canvas* canvas,
                       const cc::PaintFlags& flags) override;
   const gfx::VectorIcon& GetVectorIcon() const override;
 
diff --git a/ui/views/controls/focus_ring.cc b/ui/views/controls/focus_ring.cc
index b905823..a5a44ba 100644
--- a/ui/views/controls/focus_ring.cc
+++ b/ui/views/controls/focus_ring.cc
@@ -5,7 +5,6 @@
 #include "ui/views/controls/focus_ring.h"
 
 #include "ui/gfx/canvas.h"
-#include "ui/native_theme/native_theme.h"
 #include "ui/views/controls/focusable_border.h"
 
 namespace views {
@@ -21,7 +20,7 @@
 constexpr float kFocusHaloCornerRadiusDp =
     FocusableBorder::kCornerRadiusDp + kFocusHaloThicknessDp / 2.f;
 
-FocusRing* GetFocusRing(views::View* parent) {
+FocusRing* GetFocusRing(View* parent) {
   for (int i = 0; i < parent->child_count(); ++i) {
     if (parent->child_at(i)->GetClassName() == FocusRing::kViewClassName)
       return static_cast<FocusRing*>(parent->child_at(i));
@@ -34,7 +33,7 @@
 const char FocusRing::kViewClassName[] = "FocusRing";
 
 // static
-views::View* FocusRing::Install(views::View* parent,
+views::View* FocusRing::Install(View* parent,
                                 ui::NativeTheme::ColorId override_color_id) {
   FocusRing* ring = GetFocusRing(parent);
   if (!ring) {
@@ -48,18 +47,23 @@
 }
 
 // static
-void FocusRing::Uninstall(views::View* parent) {
+void FocusRing::Uninstall(View* parent) {
   delete GetFocusRing(parent);
 }
 
+// static
+void FocusRing::InitFocusRing(View* view) {
+  // A layer is necessary to paint beyond the parent's bounds.
+  view->SetPaintToLayer();
+  view->layer()->SetFillsBoundsOpaquely(false);
+  // Don't allow the view to process events.
+  view->set_can_process_events_within_subtree(false);
+}
+
 const char* FocusRing::GetClassName() const {
   return kViewClassName;
 }
 
-bool FocusRing::CanProcessEventsWithinSubtree() const {
-  return false;
-}
-
 void FocusRing::Layout() {
   // The focus ring handles its own sizing, which is simply to fill the parent
   // and extend a little beyond its borders.
@@ -86,9 +90,7 @@
 
 FocusRing::FocusRing()
     : override_color_id_(ui::NativeTheme::kColorId_NumColors) {
-  // A layer is necessary to paint beyond the parent's bounds.
-  SetPaintToLayer();
-  layer()->SetFillsBoundsOpaquely(false);
+  InitFocusRing(this);
 }
 
 FocusRing::~FocusRing() {}
diff --git a/ui/views/controls/focus_ring.h b/ui/views/controls/focus_ring.h
index 53aa19d3..f6ecc2a 100644
--- a/ui/views/controls/focus_ring.h
+++ b/ui/views/controls/focus_ring.h
@@ -5,7 +5,6 @@
 #ifndef UI_VIEWS_CONTROLS_FOCUS_RING_H_
 #define UI_VIEWS_CONTROLS_FOCUS_RING_H_
 
-#include "base/optional.h"
 #include "ui/native_theme/native_theme.h"
 #include "ui/views/view.h"
 
@@ -21,23 +20,26 @@
   // Create a FocusRing and adds it to |parent|, or updates the one that already
   // exists. |override_color_id| will be used in place of the default coloration
   // when provided.
-  static View* Install(views::View* parent,
-                      ui::NativeTheme::ColorId override_color_id =
-                          ui::NativeTheme::kColorId_NumColors);
+  static View* Install(View* parent,
+                       ui::NativeTheme::ColorId override_color_id =
+                           ui::NativeTheme::kColorId_NumColors);
 
   // Removes the FocusRing from |parent|.
-  static void Uninstall(views::View* parent);
+  static void Uninstall(View* parent);
+
+  // Configure |view| for painting focus ring highlights.
+  static void InitFocusRing(View* view);
 
   // View:
   const char* GetClassName() const override;
-  bool CanProcessEventsWithinSubtree() const override;
   void Layout() override;
   void OnPaint(gfx::Canvas* canvas) override;
 
- private:
+ protected:
   FocusRing();
   ~FocusRing() override;
 
+ private:
   ui::NativeTheme::ColorId override_color_id_;
 
   DISALLOW_COPY_AND_ASSIGN(FocusRing);
diff --git a/ui/views/controls/textfield/textfield.cc b/ui/views/controls/textfield/textfield.cc
index ebd9b19..7cb6b9e 100644
--- a/ui/views/controls/textfield/textfield.cc
+++ b/ui/views/controls/textfield/textfield.cc
@@ -975,6 +975,7 @@
   GetRenderText()->SetDisplayRect(bounds);
   OnCaretBoundsChanged();
   UpdateCursorViewPosition();
+  UpdateCursorVisibility();
 }
 
 bool Textfield::GetNeedsNotificationWhenVisibleBoundsChange() const {
diff --git a/ui/views/controls/textfield/textfield_unittest.cc b/ui/views/controls/textfield/textfield_unittest.cc
index 7b49cf5..40eb797 100644
--- a/ui/views/controls/textfield/textfield_unittest.cc
+++ b/ui/views/controls/textfield/textfield_unittest.cc
@@ -3212,6 +3212,7 @@
   int prev_x = GetCursorBounds().x();
   SendKeyEvent('a');
   EXPECT_EQ(prev_x, GetCursorBounds().x());
+  EXPECT_TRUE(test_api_->IsCursorVisible());
 
   // Increase the textfield size and check if the cursor moves to the new end.
   textfield_->SetSize(gfx::Size(40, 100));
@@ -3223,6 +3224,31 @@
   EXPECT_GT(prev_x, GetCursorBounds().x());
 }
 
+// Verify that after creating a new Textfield, the Textfield doesn't
+// automatically receive focus and the text cursor is not visible.
+TEST_F(TextfieldTest, TextfieldInitialization) {
+  TestTextfield* new_textfield = new TestTextfield();
+  new_textfield->set_controller(this);
+  View* container = new View();
+  Widget* widget(new Widget());
+  Widget::InitParams params =
+      CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+  params.bounds = gfx::Rect(100, 100, 100, 100);
+  widget->Init(params);
+  widget->SetContentsView(container);
+  container->AddChildView(new_textfield);
+
+  new_textfield->SetBoundsRect(params.bounds);
+  new_textfield->set_id(1);
+  test_api_.reset(new TextfieldTestApi(new_textfield));
+  widget->Show();
+  EXPECT_FALSE(new_textfield->HasFocus());
+  EXPECT_FALSE(test_api_->IsCursorVisible());
+  new_textfield->RequestFocus();
+  EXPECT_TRUE(test_api_->IsCursorVisible());
+  widget->Close();
+}
+
 // Verify that if a textfield gains focus during key dispatch that an edit
 // command only results when the event is not consumed.
 TEST_F(TextfieldTest, SwitchFocusInKeyDown) {