diff --git a/DEPS b/DEPS
index 85a9eae0..fa00e08 100644
--- a/DEPS
+++ b/DEPS
@@ -40,7 +40,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '746cc0b92290a81a8756a3a75d486cf3c66fcaad',
+  'skia_revision': 'efa9d34ccbdeb541a1fa77a678552df7a08531be',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -96,7 +96,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '4fc181694898980a2b23778d242d6971765d7b37',
+  'catapult_revision': '2c95066149e88b00bea89ac25599cbd8f931de0d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
diff --git a/base/BUILD.gn b/base/BUILD.gn
index a038579d9..7e28bf8 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -2025,6 +2025,7 @@
     "template_util_unittest.cc",
     "test/histogram_tester_unittest.cc",
     "test/scoped_mock_time_message_loop_task_runner_unittest.cc",
+    "test/scoped_task_scheduler_unittest.cc",
     "test/test_pending_task_unittest.cc",
     "test/test_reg_util_win_unittest.cc",
     "test/trace_event_analyzer_unittest.cc",
diff --git a/base/allocator/partition_allocator/partition_alloc_unittest.cc b/base/allocator/partition_allocator/partition_alloc_unittest.cc
index ddbd63e5..30a8922 100644
--- a/base/allocator/partition_allocator/partition_alloc_unittest.cc
+++ b/base/allocator/partition_allocator/partition_alloc_unittest.cc
@@ -11,6 +11,7 @@
 #include <vector>
 
 #include "base/bits.h"
+#include "base/sys_info.h"
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -187,6 +188,10 @@
 #endif
 }
 
+bool IsLargeMemoryDevice() {
+  return base::SysInfo::AmountOfPhysicalMemory() >= 2LL * 1024 * 1024 * 1024;
+}
+
 class MockPartitionStatsDumper : public PartitionStatsDumper {
  public:
   MockPartitionStatsDumper()
@@ -672,9 +677,13 @@
 
   // Can we allocate a massive (512MB) size?
   // Allocate 512MB, but +1, to test for cookie writing alignment issues.
-  ptr = partitionAllocGeneric(genericAllocator.root(), 512 * 1024 * 1024 + 1,
-                              typeName);
-  partitionFreeGeneric(genericAllocator.root(), ptr);
+  // Test this only if the device has enough memory or it might fail due
+  // to OOM.
+  if (IsLargeMemoryDevice()) {
+    ptr = partitionAllocGeneric(genericAllocator.root(), 512 * 1024 * 1024 + 1,
+                                typeName);
+    partitionFreeGeneric(genericAllocator.root(), ptr);
+  }
 
   // Check a more reasonable, but still direct mapped, size.
   // Chop a system page and a byte off to test for rounding errors.
@@ -745,15 +754,18 @@
   partitionFreeGeneric(genericAllocator.root(), ptr);
 
   // Allocate something very large, and uneven.
-  requestedSize = 512 * 1024 * 1024 - 1;
-  predictedSize =
-      partitionAllocActualSize(genericAllocator.root(), requestedSize);
-  ptr = partitionAllocGeneric(genericAllocator.root(), requestedSize, typeName);
-  EXPECT_TRUE(ptr);
-  actualSize = partitionAllocGetSize(ptr);
-  EXPECT_EQ(predictedSize, actualSize);
-  EXPECT_LT(requestedSize, actualSize);
-  partitionFreeGeneric(genericAllocator.root(), ptr);
+  if (IsLargeMemoryDevice()) {
+    requestedSize = 512 * 1024 * 1024 - 1;
+    predictedSize =
+        partitionAllocActualSize(genericAllocator.root(), requestedSize);
+    ptr =
+        partitionAllocGeneric(genericAllocator.root(), requestedSize, typeName);
+    EXPECT_TRUE(ptr);
+    actualSize = partitionAllocGetSize(ptr);
+    EXPECT_EQ(predictedSize, actualSize);
+    EXPECT_LT(requestedSize, actualSize);
+    partitionFreeGeneric(genericAllocator.root(), ptr);
+  }
 
   // Too large allocation.
   requestedSize = INT_MAX;
@@ -1328,7 +1340,7 @@
 #endif
 TEST(PartitionAllocTest, MAYBE_RepeatedReturnNullDirect) {
   // A direct-mapped allocation size.
-  DoReturnNullTest(256 * 1024 * 1024);
+  DoReturnNullTest(32 * 1024 * 1024);
 }
 
 #endif  // !defined(ARCH_CPU_64_BITS) || defined(OS_POSIX)
diff --git a/base/message_loop/message_loop.cc b/base/message_loop/message_loop.cc
index 1581f6c..2212941d 100644
--- a/base/message_loop/message_loop.cc
+++ b/base/message_loop/message_loop.cc
@@ -357,12 +357,20 @@
 void MessageLoop::SetTaskRunner(
     scoped_refptr<SingleThreadTaskRunner> task_runner) {
   DCHECK_EQ(this, current());
+  DCHECK(task_runner);
   DCHECK(task_runner->BelongsToCurrentThread());
   DCHECK(!unbound_task_runner_);
   task_runner_ = std::move(task_runner);
   SetThreadTaskRunnerHandle();
 }
 
+void MessageLoop::ClearTaskRunnerForTesting() {
+  DCHECK_EQ(this, current());
+  DCHECK(!unbound_task_runner_);
+  task_runner_ = nullptr;
+  thread_task_runner_handle_.reset();
+}
+
 void MessageLoop::SetThreadTaskRunnerHandle() {
   DCHECK_EQ(this, current());
   // Clear the previous thread task runner first, because only one can exist at
diff --git a/base/message_loop/message_loop.h b/base/message_loop/message_loop.h
index ac7a303..91a7b1d3 100644
--- a/base/message_loop/message_loop.h
+++ b/base/message_loop/message_loop.h
@@ -234,6 +234,10 @@
   // thread to which the message loop is bound.
   void SetTaskRunner(scoped_refptr<SingleThreadTaskRunner> task_runner);
 
+  // Clears task_runner() and the ThreadTaskRunnerHandle for the target thread.
+  // Must be called on the thread to which the message loop is bound.
+  void ClearTaskRunnerForTesting();
+
   // Enables or disables the recursive task processing. This happens in the case
   // of recursive message loops. Some unwanted message loops may occur when
   // using common controls or printer functions. By default, recursive task
diff --git a/base/numerics/safe_conversions.h b/base/numerics/safe_conversions.h
index 66b278f..99cbef4 100644
--- a/base/numerics/safe_conversions.h
+++ b/base/numerics/safe_conversions.h
@@ -110,35 +110,35 @@
 // that the specified numeric conversion will saturate by default rather than
 // overflow or underflow, and NaN assignment to an integral will return 0.
 // All boundary condition behaviors can be overriden with a custom handler.
+template <template <typename>
+          class SaturationHandler = SaturatedCastDefaultHandler,
+          typename Dst,
+          typename Src>
+constexpr Dst saturated_cast_impl(const Src value,
+                                  const RangeConstraint constraint) {
+  return constraint.IsValid()
+             ? static_cast<Dst>(value)
+             : (constraint.IsOverflow()
+                    ? SaturationHandler<Dst>::HandleOverflow()
+                    // Skip this check for integral Src, which cannot be NaN.
+                    : (std::is_integral<Src>::value || constraint.IsUnderflow()
+                           ? SaturationHandler<Dst>::HandleUnderflow()
+                           : SaturationHandler<Dst>::HandleNaN()));
+}
+
+// saturated_cast<> is analogous to static_cast<> for numeric types, except
+// that the specified numeric conversion will saturate by default rather than
+// overflow or underflow, and NaN assignment to an integral will return 0.
+// All boundary condition behaviors can be overriden with a custom handler.
 template <typename Dst,
           template <typename>
           class SaturationHandler = SaturatedCastDefaultHandler,
           typename Src>
 constexpr Dst saturated_cast(Src value) {
-  static_assert(
-      SaturationHandler<Dst>::lowest() < SaturationHandler<Dst>::max(), "");
-  // While this looks like a lot of code, it's all constexpr and all but
-  // one variable are compile-time constants (enforced by a static_assert).
-  // So, it should evaluate to the minimum number of comparisons required
-  // for the range check, which is 0-3, depending on the exact source and
-  // destination types, and whatever custom range is specified.
   using SrcType = typename UnderlyingType<Src>::type;
-  return IsGreaterOrEqual<SrcType, Dst>::Test(
-             value, NarrowingRange<Dst, SrcType, SaturationHandler>::lowest())
-             ? (IsLessOrEqual<SrcType, Dst>::Test(
-                    value,
-                    NarrowingRange<Dst, SrcType, SaturationHandler>::max())
-                    ? static_cast<Dst>(value)
-                    : SaturationHandler<Dst>::HandleOverflow())
-             // This last branch is a little confusing. It's specifically to
-             // catch NaN when converting from float to integral.
-             : (std::is_integral<SrcType>::value ||
-                        std::is_floating_point<Dst>::value ||
-                        IsLessOrEqual<SrcType, Dst>::Test(
-                            value, NarrowingRange<Dst, SrcType,
-                                                  SaturationHandler>::max())
-                    ? SaturationHandler<Dst>::HandleUnderflow()
-                    : SaturationHandler<Dst>::HandleNaN());
+  return saturated_cast_impl<SaturationHandler, Dst>(
+      static_cast<SrcType>(value),
+      DstRangeRelationToSrcRange<Dst, SaturationHandler, SrcType>(value));
 }
 
 // strict_cast<> is analogous to static_cast<> for numeric types, except that
diff --git a/base/numerics/safe_conversions_impl.h b/base/numerics/safe_conversions_impl.h
index 5ef3321..fd0064dca 100644
--- a/base/numerics/safe_conversions_impl.h
+++ b/base/numerics/safe_conversions_impl.h
@@ -166,29 +166,38 @@
   static const NumericRangeRepresentation value = NUMERIC_RANGE_NOT_CONTAINED;
 };
 
-enum RangeConstraint {
-  RANGE_VALID = 0x0,  // Value can be represented by the destination type.
-  RANGE_UNDERFLOW = 0x1,  // Value would overflow.
-  RANGE_OVERFLOW = 0x2,  // Value would underflow.
+enum RangeConstraintEnum {
+  RANGE_VALID = 0x0,      // Value can be represented by the destination type.
+  RANGE_OVERFLOW = 0x1,   // Value would overflow.
+  RANGE_UNDERFLOW = 0x2,  // Value would underflow.
   RANGE_INVALID = RANGE_UNDERFLOW | RANGE_OVERFLOW  // Invalid (i.e. NaN).
 };
 
-// Helper function for coercing an int back to a RangeContraint.
-constexpr RangeConstraint GetRangeConstraint(int integer_range_constraint) {
-  // TODO(jschuh): Once we get full C++14 support we want this
-  // assert(integer_range_constraint >= RANGE_VALID &&
-  //        integer_range_constraint <= RANGE_INVALID)
-  return static_cast<RangeConstraint>(integer_range_constraint);
-}
+// This class wraps the range constraints as separate booleans so the compiler
+// can identify constants and eliminate unused code paths.
+class RangeConstraint {
+ public:
+  constexpr RangeConstraint(bool is_in_upper_bound, bool is_in_lower_bound)
+      : is_overflow_(!is_in_upper_bound), is_underflow_(!is_in_lower_bound) {}
+  constexpr RangeConstraint() : is_overflow_(0), is_underflow_(0) {}
+  constexpr bool IsValid() const { return !is_overflow_ && !is_underflow_; }
+  constexpr bool IsInvalid() const { return is_overflow_ && is_underflow_; }
+  constexpr bool IsOverflow() const { return is_overflow_ && !is_underflow_; }
+  constexpr bool IsUnderflow() const { return !is_overflow_ && is_underflow_; }
 
-// This function creates a RangeConstraint from an upper and lower bound
-// check by taking advantage of the fact that only NaN can be out of range in
-// both directions at once.
-constexpr inline RangeConstraint GetRangeConstraint(bool is_in_upper_bound,
-                                                    bool is_in_lower_bound) {
-  return GetRangeConstraint((is_in_upper_bound ? 0 : RANGE_OVERFLOW) |
-                            (is_in_lower_bound ? 0 : RANGE_UNDERFLOW));
-}
+  // These are some wrappers to make the tests a bit cleaner.
+  constexpr operator RangeConstraintEnum() const {
+    return static_cast<RangeConstraintEnum>(
+        static_cast<int>(is_overflow_) | static_cast<int>(is_underflow_) << 1);
+  }
+  constexpr bool operator==(const RangeConstraintEnum rhs) const {
+    return rhs == static_cast<RangeConstraintEnum>(*this);
+  }
+
+ private:
+  const bool is_overflow_;
+  const bool is_underflow_;
+};
 
 // The following helper template addresses a corner case in range checks for
 // conversion from a floating-point type to an integral type of smaller range
@@ -211,11 +220,9 @@
 // To fix this bug we manually truncate the maximum value when the destination
 // type is an integral of larger precision than the source floating-point type,
 // such that the resulting maximum is represented exactly as a floating point.
-template <typename Dst,
-          typename Src,
-          template <typename> class Bounds = std::numeric_limits>
+template <typename Dst, typename Src, template <typename> class Bounds>
 struct NarrowingRange {
-  using SrcLimits = typename std::numeric_limits<Src>;
+  using SrcLimits = std::numeric_limits<Src>;
   using DstLimits = typename std::numeric_limits<Dst>;
 
   // Computes the mask required to make an accurate comparison between types.
@@ -253,6 +260,7 @@
 
 template <typename Dst,
           typename Src,
+          template <typename> class Bounds,
           IntegerRepresentation DstSign = std::is_signed<Dst>::value
                                               ? INTEGER_REPRESENTATION_SIGNED
                                               : INTEGER_REPRESENTATION_UNSIGNED,
@@ -267,83 +275,112 @@
 // split it into checks based on signedness to avoid confusing casts and
 // compiler warnings on signed an unsigned comparisons.
 
-// Dst range is statically determined to contain Src: Nothing to check.
+// Same sign narrowing: The range is contained for normal limits.
 template <typename Dst,
           typename Src,
+          template <typename> class Bounds,
           IntegerRepresentation DstSign,
           IntegerRepresentation SrcSign>
 struct DstRangeRelationToSrcRangeImpl<Dst,
                                       Src,
+                                      Bounds,
                                       DstSign,
                                       SrcSign,
                                       NUMERIC_RANGE_CONTAINED> {
-  static constexpr RangeConstraint Check(Src value) { return RANGE_VALID; }
+  static constexpr RangeConstraint Check(Src value) {
+    using SrcLimits = std::numeric_limits<Src>;
+    using DstLimits = NarrowingRange<Dst, Src, Bounds>;
+    return RangeConstraint(
+        static_cast<Dst>(SrcLimits::max()) <= DstLimits::max() ||
+            static_cast<Dst>(value) <= DstLimits::max(),
+        static_cast<Dst>(SrcLimits::lowest()) >= DstLimits::lowest() ||
+            static_cast<Dst>(value) >= DstLimits::lowest());
+  }
 };
 
 // Signed to signed narrowing: Both the upper and lower boundaries may be
-// exceeded.
-template <typename Dst, typename Src>
+// exceeded for standard limits.
+template <typename Dst, typename Src, template <typename> class Bounds>
 struct DstRangeRelationToSrcRangeImpl<Dst,
                                       Src,
+                                      Bounds,
                                       INTEGER_REPRESENTATION_SIGNED,
                                       INTEGER_REPRESENTATION_SIGNED,
                                       NUMERIC_RANGE_NOT_CONTAINED> {
   static constexpr RangeConstraint Check(Src value) {
-    return GetRangeConstraint((value <= NarrowingRange<Dst, Src>::max()),
-                              (value >= NarrowingRange<Dst, Src>::lowest()));
+    using DstLimits = NarrowingRange<Dst, Src, Bounds>;
+    return RangeConstraint(value <= DstLimits::max(),
+                           value >= DstLimits::lowest());
   }
 };
 
-// Unsigned to unsigned narrowing: Only the upper boundary can be exceeded.
-template <typename Dst, typename Src>
+// Unsigned to unsigned narrowing: Only the upper bound can be exceeded for
+// standard limits.
+template <typename Dst, typename Src, template <typename> class Bounds>
 struct DstRangeRelationToSrcRangeImpl<Dst,
                                       Src,
+                                      Bounds,
                                       INTEGER_REPRESENTATION_UNSIGNED,
                                       INTEGER_REPRESENTATION_UNSIGNED,
                                       NUMERIC_RANGE_NOT_CONTAINED> {
   static constexpr RangeConstraint Check(Src value) {
-    return GetRangeConstraint(value <= NarrowingRange<Dst, Src>::max(), true);
+    using DstLimits = NarrowingRange<Dst, Src, Bounds>;
+    return RangeConstraint(
+        value <= DstLimits::max(),
+        DstLimits::lowest() == Dst(0) || value >= DstLimits::lowest());
   }
 };
 
-// Unsigned to signed: The upper boundary may be exceeded.
-template <typename Dst, typename Src>
+// Unsigned to signed: Only the upper bound can be exceeded for standard limits.
+template <typename Dst, typename Src, template <typename> class Bounds>
 struct DstRangeRelationToSrcRangeImpl<Dst,
                                       Src,
+                                      Bounds,
                                       INTEGER_REPRESENTATION_SIGNED,
                                       INTEGER_REPRESENTATION_UNSIGNED,
                                       NUMERIC_RANGE_NOT_CONTAINED> {
   static constexpr RangeConstraint Check(Src value) {
-    return IntegerBitsPlusSign<Dst>::value > IntegerBitsPlusSign<Src>::value
-               ? RANGE_VALID
-               : GetRangeConstraint(
-                     value <= static_cast<Src>(NarrowingRange<Dst, Src>::max()),
-                     true);
+    using DstLimits = NarrowingRange<Dst, Src, Bounds>;
+    using Promotion = decltype(Src() + Dst());
+    return RangeConstraint(static_cast<Promotion>(value) <=
+                               static_cast<Promotion>(DstLimits::max()),
+                           DstLimits::lowest() <= Dst(0) ||
+                               static_cast<Promotion>(value) >=
+                                   static_cast<Promotion>(DstLimits::lowest()));
   }
 };
 
 // Signed to unsigned: The upper boundary may be exceeded for a narrower Dst,
-// and any negative value exceeds the lower boundary.
-template <typename Dst, typename Src>
+// and any negative value exceeds the lower boundary for standard limits.
+template <typename Dst, typename Src, template <typename> class Bounds>
 struct DstRangeRelationToSrcRangeImpl<Dst,
                                       Src,
+                                      Bounds,
                                       INTEGER_REPRESENTATION_UNSIGNED,
                                       INTEGER_REPRESENTATION_SIGNED,
                                       NUMERIC_RANGE_NOT_CONTAINED> {
   static constexpr RangeConstraint Check(Src value) {
-    return (MaxExponent<Dst>::value >= MaxExponent<Src>::value)
-               ? GetRangeConstraint(true, value >= static_cast<Src>(0))
-               : GetRangeConstraint(
-                     value <= static_cast<Src>(NarrowingRange<Dst, Src>::max()),
-                     value >= static_cast<Src>(0));
+    using SrcLimits = std::numeric_limits<Src>;
+    using DstLimits = NarrowingRange<Dst, Src, Bounds>;
+    using Promotion = decltype(Src() + Dst());
+    return RangeConstraint(
+        static_cast<Promotion>(SrcLimits::max()) <=
+                static_cast<Promotion>(DstLimits::max()) ||
+            static_cast<Promotion>(value) <=
+                static_cast<Promotion>(DstLimits::max()),
+        value >= Src(0) && (DstLimits::lowest() == 0 ||
+                            static_cast<Dst>(value) >= DstLimits::lowest()));
   }
 };
 
-template <typename Dst, typename Src>
+template <typename Dst,
+          template <typename> class Bounds = std::numeric_limits,
+          typename Src>
 constexpr RangeConstraint DstRangeRelationToSrcRange(Src value) {
   static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
   static_assert(std::is_arithmetic<Dst>::value, "Result must be numeric.");
-  return DstRangeRelationToSrcRangeImpl<Dst, Src>::Check(value);
+  static_assert(Bounds<Dst>::lowest() < Bounds<Dst>::max(), "");
+  return DstRangeRelationToSrcRangeImpl<Dst, Src, Bounds>::Check(value);
 }
 
 // Integer promotion templates used by the portable checked integer arithmetic.
diff --git a/base/numerics/safe_numerics_unittest.cc b/base/numerics/safe_numerics_unittest.cc
index 0e3d999b..5304593 100644
--- a/base/numerics/safe_numerics_unittest.cc
+++ b/base/numerics/safe_numerics_unittest.cc
@@ -671,7 +671,14 @@
     TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::max());
     TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(1));
     TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, static_cast<Src>(-1));
+
+    // Additional saturation tests.
+    EXPECT_EQ(DstLimits::max(), saturated_cast<Dst>(SrcLimits::max()));
+    EXPECT_EQ(DstLimits::lowest(), saturated_cast<Dst>(SrcLimits::lowest()));
+
     if (SrcLimits::is_iec559) {
+      EXPECT_EQ(Dst(0), saturated_cast<Dst>(SrcLimits::quiet_NaN()));
+
       TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::max() * -1);
       TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::infinity());
       TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::infinity() * -1);
@@ -714,6 +721,10 @@
     TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::lowest());
     TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::max());
     TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(1));
+
+    // Additional saturation tests.
+    EXPECT_EQ(DstLimits::max(), saturated_cast<Dst>(SrcLimits::max()));
+    EXPECT_EQ(Dst(0), saturated_cast<Dst>(SrcLimits::lowest()));
   }
 };
 
diff --git a/base/task_scheduler/task_scheduler.h b/base/task_scheduler/task_scheduler.h
index cbfaae9..d4084c6f 100644
--- a/base/task_scheduler/task_scheduler.h
+++ b/base/task_scheduler/task_scheduler.h
@@ -82,10 +82,6 @@
   // other threads during the call. Returns immediately when shutdown completes.
   virtual void FlushForTesting() = 0;
 
-  // Joins all threads of this scheduler. Tasks that are already running are
-  // allowed to complete their execution. This can only be called once.
-  virtual void JoinForTesting() = 0;
-
   // CreateAndSetSimpleTaskScheduler(), CreateAndSetDefaultTaskScheduler(), and
   // SetInstance() register a TaskScheduler to handle tasks posted through the
   // post_task.h API for this process. The registered TaskScheduler will only be
diff --git a/base/task_scheduler/task_scheduler_impl.h b/base/task_scheduler/task_scheduler_impl.h
index 9a161037..0e03d52 100644
--- a/base/task_scheduler/task_scheduler_impl.h
+++ b/base/task_scheduler/task_scheduler_impl.h
@@ -62,7 +62,10 @@
   std::vector<const HistogramBase*> GetHistograms() const override;
   void Shutdown() override;
   void FlushForTesting() override;
-  void JoinForTesting() override;
+
+  // Joins all threads. Tasks that are already running are allowed to complete
+  // their execution. This can only be called once.
+  void JoinForTesting();
 
  private:
   explicit TaskSchedulerImpl(const WorkerPoolIndexForTraitsCallback&
diff --git a/base/task_scheduler/task_tracker.cc b/base/task_scheduler/task_tracker.cc
index 4c1ee8da..447b35a 100644
--- a/base/task_scheduler/task_tracker.cc
+++ b/base/task_scheduler/task_tracker.cc
@@ -288,6 +288,14 @@
 }
 
 void TaskTracker::SetHasShutdownStartedForTesting() {
+  AutoSchedulerLock auto_lock(shutdown_lock_);
+
+  // Create a dummy |shutdown_event_| to satisfy TaskTracker's expectation of
+  // its existence during shutdown (e.g. in OnBlockingShutdownTasksComplete()).
+  shutdown_event_.reset(
+      new WaitableEvent(WaitableEvent::ResetPolicy::MANUAL,
+                        WaitableEvent::InitialState::NOT_SIGNALED));
+
   state_->StartShutdown();
 }
 
diff --git a/base/test/OWNERS b/base/test/OWNERS
index 34780973..92ca839 100644
--- a/base/test/OWNERS
+++ b/base/test/OWNERS
@@ -1,5 +1,7 @@
 phajdan.jr@chromium.org
 
+per-file scoped_task_scheduler*=file://base/task_scheduler/OWNERS
+
 # For Android-specific changes:
 per-file *android*=file://base/test/android/OWNERS
 per-file BUILD.gn=file://base/test/android/OWNERS
diff --git a/base/test/scoped_task_scheduler.cc b/base/test/scoped_task_scheduler.cc
index 2cf5b33..19ee891 100644
--- a/base/test/scoped_task_scheduler.cc
+++ b/base/test/scoped_task_scheduler.cc
@@ -4,37 +4,250 @@
 
 #include "base/test/scoped_task_scheduler.h"
 
+#include <memory>
+#include <utility>
 #include <vector>
 
 #include "base/bind.h"
-#include "base/task_scheduler/scheduler_worker_pool_params.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram_base.h"
+#include "base/run_loop.h"
+#include "base/sequence_token.h"
+#include "base/sequenced_task_runner.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task_runner.h"
+#include "base/task_scheduler/task.h"
 #include "base/task_scheduler/task_scheduler.h"
-#include "base/threading/platform_thread.h"
-#include "base/time/time.h"
+#include "base/task_scheduler/task_tracker.h"
+#include "base/threading/thread_task_runner_handle.h"
 
 namespace base {
 namespace test {
 
-ScopedTaskScheduler::ScopedTaskScheduler() {
-  DCHECK(!TaskScheduler::GetInstance());
+namespace {
 
-  // Create a TaskScheduler with a single thread to make tests deterministic.
-  constexpr int kMaxThreads = 1;
-  std::vector<SchedulerWorkerPoolParams> worker_pool_params_vector;
-  worker_pool_params_vector.emplace_back(
-      "Simple", ThreadPriority::NORMAL,
-      SchedulerWorkerPoolParams::StandbyThreadPolicy::LAZY, kMaxThreads,
-      TimeDelta::Max());
-  TaskScheduler::CreateAndSetDefaultTaskScheduler(
-      worker_pool_params_vector,
-      Bind([](const TaskTraits&) -> size_t { return 0; }));
+enum class ExecutionMode { PARALLEL, SEQUENCED, SINGLE_THREADED };
+
+class TestTaskScheduler : public TaskScheduler {
+ public:
+  // |external_message_loop| is an externally provided MessageLoop on which to
+  // run tasks. A MessageLoop will be created by TestTaskScheduler if
+  // |external_message_loop| is nullptr.
+  explicit TestTaskScheduler(MessageLoop* external_message_loop);
+  ~TestTaskScheduler() override;
+
+  // TaskScheduler:
+  void PostTaskWithTraits(const tracked_objects::Location& from_here,
+                          const TaskTraits& traits,
+                          const Closure& task) override;
+  scoped_refptr<TaskRunner> CreateTaskRunnerWithTraits(
+      const TaskTraits& traits) override;
+  scoped_refptr<SequencedTaskRunner> CreateSequencedTaskRunnerWithTraits(
+      const TaskTraits& traits) override;
+  scoped_refptr<SingleThreadTaskRunner> CreateSingleThreadTaskRunnerWithTraits(
+      const TaskTraits& traits) override;
+  std::vector<const HistogramBase*> GetHistograms() const override;
+  void Shutdown() override;
+  void FlushForTesting() override;
+
+  // Posts |task| to this TaskScheduler with |sequence_token|. Returns true on
+  // success.
+  bool PostTask(std::unique_ptr<internal::Task> task,
+                const SequenceToken& sequence_token);
+
+  // Runs |task| with |sequence_token| using this TaskScheduler's TaskTracker.
+  void RunTask(std::unique_ptr<internal::Task> task,
+               const SequenceToken& sequence_token);
+
+  // Returns true if this TaskScheduler runs its tasks on the current thread.
+  bool RunsTasksOnCurrentThread() const;
+
+ private:
+  // |message_loop_owned_| will be non-null if this TestTaskScheduler owns the
+  // MessageLoop (wasn't provided an external one at construction).
+  // |message_loop_| will always be set and is used by this TestTaskScheduler to
+  // run tasks.
+  std::unique_ptr<MessageLoop> message_loop_owned_;
+  MessageLoop* message_loop_;
+
+  // The SingleThreadTaskRunner associated with |message_loop_|.
+  const scoped_refptr<SingleThreadTaskRunner> message_loop_task_runner_ =
+      message_loop_->task_runner();
+
+  // Handles shutdown behaviors and sets up the environment to run a task.
+  internal::TaskTracker task_tracker_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestTaskScheduler);
+};
+
+class TestTaskSchedulerTaskRunner : public SingleThreadTaskRunner {
+ public:
+  TestTaskSchedulerTaskRunner(TestTaskScheduler* task_scheduler,
+                              ExecutionMode execution_mode,
+                              TaskTraits traits);
+
+  // SingleThreadTaskRunner:
+  bool PostDelayedTask(const tracked_objects::Location& from_here,
+                       const Closure& closure,
+                       TimeDelta delay) override;
+  bool PostNonNestableDelayedTask(const tracked_objects::Location& from_here,
+                                  const Closure& closure,
+                                  TimeDelta delay) override;
+  bool RunsTasksOnCurrentThread() const override;
+
+ private:
+  ~TestTaskSchedulerTaskRunner() override;
+
+  TestTaskScheduler* const task_scheduler_;
+  const ExecutionMode execution_mode_;
+  const SequenceToken sequence_token_;
+  const TaskTraits traits_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestTaskSchedulerTaskRunner);
+};
+
+TestTaskScheduler::TestTaskScheduler(MessageLoop* external_message_loop)
+    : message_loop_owned_(external_message_loop ? nullptr
+                                                : MakeUnique<MessageLoop>()),
+      message_loop_(message_loop_owned_ ? message_loop_owned_.get()
+                                        : external_message_loop) {}
+
+TestTaskScheduler::~TestTaskScheduler() {
+  // Prevent the RunUntilIdle() call below from running SKIP_ON_SHUTDOWN and
+  // CONTINUE_ON_SHUTDOWN tasks.
+  task_tracker_.SetHasShutdownStartedForTesting();
+
+  // Run pending BLOCK_SHUTDOWN tasks.
+  RunLoop().RunUntilIdle();
+}
+
+void TestTaskScheduler::PostTaskWithTraits(
+    const tracked_objects::Location& from_here,
+    const TaskTraits& traits,
+    const Closure& task) {
+  CreateTaskRunnerWithTraits(traits)->PostTask(from_here, task);
+}
+
+scoped_refptr<TaskRunner> TestTaskScheduler::CreateTaskRunnerWithTraits(
+    const TaskTraits& traits) {
+  return make_scoped_refptr(
+      new TestTaskSchedulerTaskRunner(this, ExecutionMode::PARALLEL, traits));
+}
+
+scoped_refptr<SequencedTaskRunner>
+TestTaskScheduler::CreateSequencedTaskRunnerWithTraits(
+    const TaskTraits& traits) {
+  return make_scoped_refptr(
+      new TestTaskSchedulerTaskRunner(this, ExecutionMode::SEQUENCED, traits));
+}
+
+scoped_refptr<SingleThreadTaskRunner>
+TestTaskScheduler::CreateSingleThreadTaskRunnerWithTraits(
+    const TaskTraits& traits) {
+  return make_scoped_refptr(new TestTaskSchedulerTaskRunner(
+      this, ExecutionMode::SINGLE_THREADED, traits));
+}
+
+std::vector<const HistogramBase*> TestTaskScheduler::GetHistograms() const {
+  NOTREACHED();
+  return std::vector<const HistogramBase*>();
+}
+
+void TestTaskScheduler::Shutdown() {
+  NOTREACHED();
+}
+
+void TestTaskScheduler::FlushForTesting() {
+  NOTREACHED();
+}
+
+bool TestTaskScheduler::PostTask(std::unique_ptr<internal::Task> task,
+                                 const SequenceToken& sequence_token) {
+  DCHECK(task);
+  if (!task_tracker_.WillPostTask(task.get()))
+    return false;
+  internal::Task* const task_ptr = task.get();
+  return message_loop_task_runner_->PostDelayedTask(
+      task_ptr->posted_from, Bind(&TestTaskScheduler::RunTask, Unretained(this),
+                                  Passed(&task), sequence_token),
+      task_ptr->delay);
+}
+
+void TestTaskScheduler::RunTask(std::unique_ptr<internal::Task> task,
+                                const SequenceToken& sequence_token) {
+  // Clear the MessageLoop TaskRunner to allow TaskTracker to register its own
+  // Thread/SequencedTaskRunnerHandle as appropriate.
+  MessageLoop::current()->ClearTaskRunnerForTesting();
+
+  // Run the task.
+  task_tracker_.RunTask(std::move(task), sequence_token.IsValid()
+                                             ? sequence_token
+                                             : SequenceToken::Create());
+
+  // Restore the MessageLoop TaskRunner.
+  MessageLoop::current()->SetTaskRunner(message_loop_task_runner_);
+}
+
+bool TestTaskScheduler::RunsTasksOnCurrentThread() const {
+  return message_loop_task_runner_->RunsTasksOnCurrentThread();
+}
+
+TestTaskSchedulerTaskRunner::TestTaskSchedulerTaskRunner(
+    TestTaskScheduler* task_scheduler,
+    ExecutionMode execution_mode,
+    TaskTraits traits)
+    : task_scheduler_(task_scheduler),
+      execution_mode_(execution_mode),
+      sequence_token_(execution_mode == ExecutionMode::PARALLEL
+                          ? SequenceToken()
+                          : SequenceToken::Create()),
+      traits_(traits) {}
+
+bool TestTaskSchedulerTaskRunner::PostDelayedTask(
+    const tracked_objects::Location& from_here,
+    const Closure& closure,
+    TimeDelta delay) {
+  auto task = MakeUnique<internal::Task>(from_here, closure, traits_, delay);
+  if (execution_mode_ == ExecutionMode::SEQUENCED)
+    task->sequenced_task_runner_ref = make_scoped_refptr(this);
+  else if (execution_mode_ == ExecutionMode::SINGLE_THREADED)
+    task->single_thread_task_runner_ref = make_scoped_refptr(this);
+  return task_scheduler_->PostTask(std::move(task), sequence_token_);
+}
+
+bool TestTaskSchedulerTaskRunner::PostNonNestableDelayedTask(
+    const tracked_objects::Location& from_here,
+    const Closure& closure,
+    TimeDelta delay) {
+  // Tasks are never nested within the task scheduler.
+  return PostDelayedTask(from_here, closure, delay);
+}
+
+bool TestTaskSchedulerTaskRunner::RunsTasksOnCurrentThread() const {
+  if (execution_mode_ == ExecutionMode::PARALLEL)
+    return task_scheduler_->RunsTasksOnCurrentThread();
+  return sequence_token_ == SequenceToken::GetForCurrentThread();
+}
+
+TestTaskSchedulerTaskRunner::~TestTaskSchedulerTaskRunner() = default;
+
+}  // namespace
+
+ScopedTaskScheduler::ScopedTaskScheduler() : ScopedTaskScheduler(nullptr) {}
+
+ScopedTaskScheduler::ScopedTaskScheduler(MessageLoop* external_message_loop) {
+  DCHECK(!TaskScheduler::GetInstance());
+  TaskScheduler::SetInstance(
+      MakeUnique<TestTaskScheduler>(external_message_loop));
   task_scheduler_ = TaskScheduler::GetInstance();
 }
 
 ScopedTaskScheduler::~ScopedTaskScheduler() {
+  DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK_EQ(task_scheduler_, TaskScheduler::GetInstance());
-  TaskScheduler::GetInstance()->Shutdown();
-  TaskScheduler::GetInstance()->JoinForTesting();
   TaskScheduler::SetInstance(nullptr);
 }
 
diff --git a/base/test/scoped_task_scheduler.h b/base/test/scoped_task_scheduler.h
index f2b252b60..bcb98c0 100644
--- a/base/test/scoped_task_scheduler.h
+++ b/base/test/scoped_task_scheduler.h
@@ -6,31 +6,58 @@
 #define BASE_TEST_SCOPED_TASK_SCHEDULER_H_
 
 #include "base/macros.h"
+#include "base/threading/thread_checker.h"
 
 namespace base {
 
+class MessageLoop;
 class TaskScheduler;
 
 namespace test {
 
-// Initializes a TaskScheduler and allows usage of the
-// base/task_scheduler/post_task.h API within its scope.
+// Allows usage of the base/task_scheduler/post_task.h API within its scope.
+//
+// To run pending tasks synchronously, call RunLoop::Run/RunUntilIdle() on the
+// thread where the ScopedTaskScheduler lives. The destructor runs remaining
+// BLOCK_SHUTDOWN tasks synchronously.
+//
+// Example usage:
+//
+// In this snippet, RunUntilIdle() returns after "A" is run.
+// base::test::ScopedTaskScheduler scoped_task_scheduler;
+// base::PostTask(FROM_HERE, base::Bind(&A));
+// base::RunLoop::RunUntilIdle(); // Returns after running A.
+//
+// In this snippet, run_loop.Run() returns after running "B" and
+// "RunLoop::Quit".
+// base::RunLoop run_loop;
+// base::PostTask(FROM_HERE, base::Bind(&B));
+// base::PostTask(FROM_HERE, base::Bind(&RunLoop::Quit, &run_loop));
+// base::PostTask(FROM_HERE, base::Bind(&C));
+// base::PostTaskWithTraits(
+//     base::TaskTraits().WithShutdownBehavior(
+//         base::TaskShutdownBehavior::BLOCK_SHUTDOWN),
+//     base::Bind(&D));
+// run_loop.Run();  // Returns after running B and RunLoop::Quit.
+//
+// At this point, |scoped_task_scheduler| will be destroyed. The destructor
+// runs "D" because it's BLOCK_SHUTDOWN. "C" is skipped.
 class ScopedTaskScheduler {
  public:
-  // Initializes a TaskScheduler with default arguments.
+  // Registers a TaskScheduler that instantiates a MessageLoop on the current
+  // thread and runs its tasks on it.
   ScopedTaskScheduler();
 
-  // Waits until all TaskScheduler tasks blocking shutdown complete their
-  // execution (see TaskShutdownBehavior). Then, joins all TaskScheduler threads
-  // and deletes the TaskScheduler.
-  //
-  // Note that joining TaskScheduler threads may involve waiting for
-  // CONTINUE_ON_SHUTDOWN tasks to complete their execution. Normally, in
-  // production, the process exits without joining TaskScheduler threads.
+  // Registers a TaskScheduler that runs its tasks on |external_message_loop|.
+  // |external_message_loop| must be bound to the current thread.
+  explicit ScopedTaskScheduler(MessageLoop* external_message_loop);
+
+  // Runs all pending BLOCK_SHUTDOWN tasks and unregisters the TaskScheduler.
   ~ScopedTaskScheduler();
 
  private:
   const TaskScheduler* task_scheduler_ = nullptr;
+  ThreadChecker thread_checker_;
 
   DISALLOW_COPY_AND_ASSIGN(ScopedTaskScheduler);
 };
diff --git a/base/test/scoped_task_scheduler_unittest.cc b/base/test/scoped_task_scheduler_unittest.cc
new file mode 100644
index 0000000..057dff7
--- /dev/null
+++ b/base/test/scoped_task_scheduler_unittest.cc
@@ -0,0 +1,252 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/scoped_task_scheduler.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/sequence_checker.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/task_scheduler/test_utils.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/threading/thread_checker.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace test {
+
+TEST(ScopedTaskSchedulerTest, PostTask) {
+  ScopedTaskScheduler scoped_task_scheduler;
+
+  bool first_task_ran = false;
+  bool second_task_ran = false;
+
+  SequenceCheckerImpl sequence_checker;
+  ThreadCheckerImpl thread_checker;
+
+  // Detach |sequence_checker| and |thread_checker|. Otherwise, they are bound
+  // to the current thread without a SequenceToken or TaskToken (i.e.
+  // CalledOnValidSequence/Thread() will always return true on the current
+  // thread, even when the SequenceToken or TaskToken changes).
+  sequence_checker.DetachFromSequence();
+  thread_checker.DetachFromThread();
+
+  PostTask(FROM_HERE,
+           Bind(
+               [](SequenceCheckerImpl* sequence_checker,
+                  ThreadCheckerImpl* thread_checker, bool* first_task_ran) {
+                 EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet());
+                 EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
+                 EXPECT_TRUE(sequence_checker->CalledOnValidSequence());
+                 EXPECT_TRUE(thread_checker->CalledOnValidThread());
+                 *first_task_ran = true;
+               },
+               Unretained(&sequence_checker), Unretained(&thread_checker),
+               Unretained(&first_task_ran)));
+
+  PostTask(FROM_HERE,
+           Bind(
+               [](SequenceCheckerImpl* sequence_checker,
+                  ThreadCheckerImpl* thread_checker, bool* second_task_ran) {
+                 EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet());
+                 EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
+                 EXPECT_FALSE(sequence_checker->CalledOnValidSequence());
+                 EXPECT_FALSE(thread_checker->CalledOnValidThread());
+                 *second_task_ran = true;
+               },
+               Unretained(&sequence_checker), Unretained(&thread_checker),
+               Unretained(&second_task_ran)));
+
+  RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(first_task_ran);
+  EXPECT_TRUE(second_task_ran);
+}
+
+TEST(ScopedTaskSchedulerTest, CreateTaskRunnerAndPostTask) {
+  ScopedTaskScheduler scoped_task_scheduler;
+  auto task_runner = CreateTaskRunnerWithTraits(TaskTraits());
+
+  bool first_task_ran = false;
+  bool second_task_ran = false;
+
+  SequenceCheckerImpl sequence_checker;
+  ThreadCheckerImpl thread_checker;
+
+  // Detach |sequence_checker| and |thread_checker|. Otherwise, they are bound
+  // to the current thread without a SequenceToken or TaskToken (i.e.
+  // CalledOnValidSequence/Thread() will always return true on the current
+  // thread, even when the SequenceToken or TaskToken changes).
+  sequence_checker.DetachFromSequence();
+  thread_checker.DetachFromThread();
+
+  task_runner->PostTask(
+      FROM_HERE,
+      Bind(
+          [](SequenceCheckerImpl* sequence_checker,
+             ThreadCheckerImpl* thread_checker, bool* first_task_ran) {
+            EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet());
+            EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
+            EXPECT_TRUE(sequence_checker->CalledOnValidSequence());
+            EXPECT_TRUE(thread_checker->CalledOnValidThread());
+            *first_task_ran = true;
+          },
+          Unretained(&sequence_checker), Unretained(&thread_checker),
+          Unretained(&first_task_ran)));
+
+  task_runner->PostTask(
+      FROM_HERE,
+      Bind(
+          [](SequenceCheckerImpl* sequence_checker,
+             ThreadCheckerImpl* thread_checker, bool* second_task_ran) {
+            EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet());
+            EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
+            EXPECT_FALSE(sequence_checker->CalledOnValidSequence());
+            EXPECT_FALSE(thread_checker->CalledOnValidThread());
+            *second_task_ran = true;
+          },
+          Unretained(&sequence_checker), Unretained(&thread_checker),
+          Unretained(&second_task_ran)));
+
+  RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(first_task_ran);
+  EXPECT_TRUE(second_task_ran);
+}
+
+TEST(ScopedTaskSchedulerTest, CreateSequencedTaskRunnerAndPostTask) {
+  ScopedTaskScheduler scoped_task_scheduler;
+  auto task_runner = CreateSequencedTaskRunnerWithTraits(TaskTraits());
+
+  bool first_task_ran = false;
+  bool second_task_ran = false;
+
+  SequenceCheckerImpl sequence_checker;
+  ThreadCheckerImpl thread_checker;
+
+  // Detach |sequence_checker| and |thread_checker|. Otherwise, they are bound
+  // to the current thread without a SequenceToken or TaskToken (i.e.
+  // CalledOnValidSequence/Thread() will always return true on the current
+  // thread, even when the SequenceToken or TaskToken changes).
+  sequence_checker.DetachFromSequence();
+  thread_checker.DetachFromThread();
+
+  task_runner->PostTask(
+      FROM_HERE,
+      Bind(
+          [](SequenceCheckerImpl* sequence_checker,
+             ThreadCheckerImpl* thread_checker, bool* first_task_ran) {
+            EXPECT_TRUE(SequencedTaskRunnerHandle::IsSet());
+            EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
+            EXPECT_TRUE(sequence_checker->CalledOnValidSequence());
+            EXPECT_TRUE(thread_checker->CalledOnValidThread());
+            *first_task_ran = true;
+          },
+          Unretained(&sequence_checker), Unretained(&thread_checker),
+          Unretained(&first_task_ran)));
+
+  task_runner->PostTask(
+      FROM_HERE,
+      Bind(
+          [](SequenceCheckerImpl* sequence_checker,
+             ThreadCheckerImpl* thread_checker, bool* second_task_ran) {
+            EXPECT_TRUE(SequencedTaskRunnerHandle::IsSet());
+            EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
+            EXPECT_TRUE(sequence_checker->CalledOnValidSequence());
+            EXPECT_FALSE(thread_checker->CalledOnValidThread());
+            *second_task_ran = true;
+          },
+          Unretained(&sequence_checker), Unretained(&thread_checker),
+          Unretained(&second_task_ran)));
+
+  RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(first_task_ran);
+  EXPECT_TRUE(second_task_ran);
+}
+
+TEST(ScopedTaskSchedulerTest, CreateSingleThreadTaskRunnerAndPostTask) {
+  ScopedTaskScheduler scoped_task_scheduler;
+  auto task_runner = CreateSingleThreadTaskRunnerWithTraits(TaskTraits());
+
+  bool first_task_ran = false;
+  bool second_task_ran = false;
+
+  SequenceCheckerImpl sequence_checker;
+  ThreadCheckerImpl thread_checker;
+
+  // Detach |sequence_checker| and |thread_checker|. Otherwise, they are bound
+  // to the current thread without a SequenceToken or TaskToken (i.e.
+  // CalledOnValidSequence/Thread() will always return true on the current
+  // thread, even when the SequenceToken or TaskToken changes).
+  sequence_checker.DetachFromSequence();
+  thread_checker.DetachFromThread();
+
+  task_runner->PostTask(
+      FROM_HERE,
+      Bind(
+          [](SequenceCheckerImpl* sequence_checker,
+             ThreadCheckerImpl* thread_checker, bool* first_task_ran) {
+            EXPECT_TRUE(SequencedTaskRunnerHandle::IsSet());
+            EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
+            EXPECT_TRUE(sequence_checker->CalledOnValidSequence());
+            EXPECT_TRUE(thread_checker->CalledOnValidThread());
+            *first_task_ran = true;
+          },
+          Unretained(&sequence_checker), Unretained(&thread_checker),
+          Unretained(&first_task_ran)));
+
+  task_runner->PostTask(
+      FROM_HERE,
+      Bind(
+          [](SequenceCheckerImpl* sequence_checker,
+             ThreadCheckerImpl* thread_checker, bool* second_task_ran) {
+            EXPECT_TRUE(SequencedTaskRunnerHandle::IsSet());
+            EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
+            EXPECT_TRUE(sequence_checker->CalledOnValidSequence());
+            EXPECT_TRUE(thread_checker->CalledOnValidThread());
+            *second_task_ran = true;
+          },
+          Unretained(&sequence_checker), Unretained(&thread_checker),
+          Unretained(&second_task_ran)));
+
+  RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(first_task_ran);
+  EXPECT_TRUE(second_task_ran);
+}
+
+TEST(ScopedTaskSchedulerTest, ShutdownBehavior) {
+  bool block_shutdown_task_ran = false;
+  {
+    ScopedTaskScheduler scoped_task_scheduler;
+    PostTaskWithTraits(
+        FROM_HERE, TaskTraits().WithShutdownBehavior(
+                       TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN),
+        Bind([]() {
+          ADD_FAILURE() << "CONTINUE_ON_SHUTDOWN task should not run";
+        }));
+    PostTaskWithTraits(FROM_HERE, TaskTraits().WithShutdownBehavior(
+                                      TaskShutdownBehavior::SKIP_ON_SHUTDOWN),
+                       Bind([]() {
+                         ADD_FAILURE()
+                             << "SKIP_ON_SHUTDOWN task should not run";
+                       }));
+    PostTaskWithTraits(FROM_HERE, TaskTraits().WithShutdownBehavior(
+                                      TaskShutdownBehavior::BLOCK_SHUTDOWN),
+                       Bind(
+                           [](bool* block_shutdown_task_ran) {
+                             *block_shutdown_task_ran = true;
+                           },
+                           Unretained(&block_shutdown_task_ran)));
+  }
+  EXPECT_TRUE(block_shutdown_task_ran);
+}
+
+}  // namespace test
+}  // namespace base
diff --git a/base/threading/sequenced_worker_pool_unittest.cc b/base/threading/sequenced_worker_pool_unittest.cc
index 496c567..6e20339 100644
--- a/base/threading/sequenced_worker_pool_unittest.cc
+++ b/base/threading/sequenced_worker_pool_unittest.cc
@@ -296,7 +296,8 @@
   // Destroys and unregisters the registered TaskScheduler, if any.
   void DeleteTaskScheduler() {
     if (TaskScheduler::GetInstance()) {
-      TaskScheduler::GetInstance()->JoinForTesting();
+      static_cast<internal::TaskSchedulerImpl*>(TaskScheduler::GetInstance())
+          ->JoinForTesting();
       TaskScheduler::SetInstance(nullptr);
     }
   }
diff --git a/build/android/gyp/aar.py b/build/android/gyp/aar.py
index 566cab3..6c0bc89 100755
--- a/build/android/gyp/aar.py
+++ b/build/android/gyp/aar.py
@@ -77,6 +77,7 @@
     data['has_classes_jar'] = False
     data['has_proguard_flags'] = False
     data['has_native_libraries'] = False
+    data['has_r_text_file'] = False
     with zipfile.ZipFile(aar_file) as z:
       data['is_manifest_empty'] = (
           _IsManifestEmpty(z.read('AndroidManifest.xml')))
@@ -101,6 +102,10 @@
           data['has_classes_jar'] = True
         elif name == 'proguard.txt':
           data['has_proguard_flags'] = True
+        elif name == 'R.txt':
+          # Some AARs, e.g. gvr_controller_java, have empty R.txt. Such AARs
+          # have no resources as well. We treat empty R.txt as having no R.txt.
+          data['has_r_text_file'] = (z.read('R.txt').strip() != '')
 
     print gn_helpers.ToGNString(data)
 
diff --git a/build/android/gyp/process_resources.py b/build/android/gyp/process_resources.py
index 8cac142..1166788e 100755
--- a/build/android/gyp/process_resources.py
+++ b/build/android/gyp/process_resources.py
@@ -74,7 +74,11 @@
   parser.add_option('--srcjar-out',
                     help='Path to srcjar to contain generated R.java.')
   parser.add_option('--r-text-out',
-                    help='Path to store the R.txt file generated by appt.')
+                    help='Path to store the generated R.txt file.')
+  parser.add_option('--r-text-in',
+                    help='Path to pre-existing R.txt for these resources. '
+                    'Resource names from it will be used to generate R.java '
+                    'instead of aapt-generated R.txt.')
 
   parser.add_option('--proguard-file',
                     help='Path to proguard.txt generated file')
@@ -169,8 +173,24 @@
     # figure out which entries belong to them, but use the values from the
     # main R.txt file.
     for entry in _ParseTextSymbolsFile(r_txt_file):
-      entry = all_resources[(entry.resource_type, entry.name)]
-      resources_by_type[entry.resource_type].append(entry)
+      entry = all_resources.get((entry.resource_type, entry.name))
+      # For most cases missing entry here is an error. It means that some
+      # library claims to have or depend on a resource that isn't included into
+      # the APK. There is one notable exception: Google Play Services (GMS).
+      # GMS is shipped as a bunch of AARs. One of them - basement - contains
+      # R.txt with ids of all resources, but most of the resources are in the
+      # other AARs. However, all other AARs reference their resources via
+      # basement's R.java so the latter must contain all ids that are in its
+      # R.txt. Most targets depend on only a subset of GMS AARs so some
+      # resources are missing, which is okay because the code that references
+      # them is missing too. We can't get an id for a resource that isn't here
+      # so the only solution is to skip the resource entry entirely.
+      #
+      # We can verify that all entries referenced in the code were generated
+      # correctly by running Proguard on the APK: it will report missing
+      # fields.
+      if entry:
+        resources_by_type[entry.resource_type].append(entry)
 
   for package, resources_by_type in resources_by_package.iteritems():
     package_r_java_dir = os.path.join(srcjar_dir, *package.split('.'))
@@ -434,6 +454,12 @@
       build_utils.Touch(r_txt_path)
 
     if not options.include_all_resources:
+      # --include-all-resources can only be specified for generating final R
+      # classes for APK. It makes no sense for APK to have pre-generated R.txt
+      # though, because aapt-generated already lists all available resources.
+      if options.r_text_in:
+        r_txt_path = options.r_text_in
+
       packages = list(options.extra_res_packages)
       r_txt_files = list(options.extra_r_text_files)
 
diff --git a/build/android/pylib/gtest/gtest_test_instance.py b/build/android/pylib/gtest/gtest_test_instance.py
index 3cdf586..394b9e8 100644
--- a/build/android/pylib/gtest/gtest_test_instance.py
+++ b/build/android/pylib/gtest/gtest_test_instance.py
@@ -99,13 +99,15 @@
   for test in raw_list:
     if not test:
       continue
-    if test[0] != ' ':
+    if not test.startswith(' '):
       test_case = test.split()[0]
       if test_case.endswith('.'):
         current = test_case
-    elif not 'YOU HAVE' in test:
-      test_name = test.split()[0]
-      ret += [current + test_name]
+    else:
+      test = test.strip()
+      if test and not 'YOU HAVE' in test:
+        test_name = test.split()[0]
+        ret += [current + test_name]
   return ret
 
 
diff --git a/build/android/pylib/gtest/gtest_test_instance_test.py b/build/android/pylib/gtest/gtest_test_instance_test.py
index 8103108..a7b591a 100755
--- a/build/android/pylib/gtest/gtest_test_instance_test.py
+++ b/build/android/pylib/gtest/gtest_test_instance_test.py
@@ -81,6 +81,18 @@
     ]
     self.assertEqual(expected, actual)
 
+  def testParseGTestListTests_emptyTestName(self):
+    raw_output = [
+      'TestCase.',
+      '  ',
+      '  nonEmptyTestName',
+    ]
+    actual = gtest_test_instance.ParseGTestListTests(raw_output)
+    expected = [
+      'TestCase.nonEmptyTestName',
+    ]
+    self.assertEqual(expected, actual)
+
   def testParseGTestOutput_pass(self):
     raw_output = [
       '[ RUN      ] FooTest.Bar',
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index 53ef628..f6052ae 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -2538,7 +2538,7 @@
 
     zip_path = invoker.zip_path
     srcjar_path = invoker.srcjar_path
-    r_text_path = invoker.r_text_path
+    r_text_out_path = invoker.r_text_out_path
     build_config = invoker.build_config
     android_manifest = invoker.android_manifest
 
@@ -2560,7 +2560,7 @@
       outputs = [
         zip_path,
         srcjar_path,
-        r_text_path,
+        r_text_out_path,
       ]
 
       _all_resource_dirs = []
@@ -2623,12 +2623,21 @@
         "--resource-zip-out",
         rebase_path(zip_path, root_build_dir),
         "--r-text-out",
-        rebase_path(r_text_path, root_build_dir),
+        rebase_path(r_text_out_path, root_build_dir),
         "--dependencies-res-zips=@FileArg($rebase_build_config:resources:dependency_zips)",
         "--extra-res-packages=@FileArg($rebase_build_config:resources:extra_package_names)",
         "--extra-r-text-files=@FileArg($rebase_build_config:resources:extra_r_text_files)",
       ]
 
+      if (defined(invoker.r_text_in_path)) {
+        _r_text_in_path = invoker.r_text_in_path
+        inputs += [ _r_text_in_path ]
+        args += [
+          "--r-text-in",
+          rebase_path(_r_text_in_path, root_build_dir),
+        ]
+      }
+
       if (non_constant_id) {
         args += [ "--non-constant-id" ]
       }
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index 5186640..2a45db4 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -646,6 +646,8 @@
   #     different application at runtime to access the package's resources.
   #   app_as_shared_lib: If true make a resource package that can be loaded as
   #     both shared_resources and normal application.
+  #   r_text_file: (optional) path to pre-generated R.txt to be used when
+  #     generating R.java instead of resource-based aapt-generated one.
 
   # Example:
   #   android_resources("foo_resources") {
@@ -667,7 +669,7 @@
     base_path = "$target_gen_dir/$target_name"
     zip_path = base_path + ".resources.zip"
     srcjar_path = base_path + ".srcjar"
-    r_text_path = base_path + "_R.txt"
+    r_text_out_path = base_path + "_R.txt"
     build_config = base_path + ".build_config"
 
     build_config_target_name = "${target_name}__build_config"
@@ -700,7 +702,7 @@
 
       # No package means resources override their deps.
       if (defined(custom_package) || defined(android_manifest)) {
-        r_text = r_text_path
+        r_text = r_text_out_path
       } else {
         assert(defined(invoker.deps),
                "Must specify deps when custom_package is omitted.")
@@ -731,6 +733,10 @@
         deps += [ invoker.android_manifest_dep ]
       }
 
+      if (defined(invoker.r_text_file)) {
+        r_text_in_path = invoker.r_text_file
+      }
+
       # Always generate R.onResourcesLoaded() method, it is required for
       # compiling ResourceRewriter, there is no side effect because the
       # generated R.class isn't used in final apk.
@@ -1675,7 +1681,7 @@
                                "shared_resources",
                              ])
       srcjar_path = "${target_gen_dir}/${target_name}.srcjar"
-      r_text_path = "${target_gen_dir}/${target_name}_R.txt"
+      r_text_out_path = "${target_gen_dir}/${target_name}_R.txt"
       android_manifest = _android_manifest
       resource_dirs = [ "//build/android/ant/empty/res" ]
       zip_path = resources_zip_path
@@ -2710,8 +2716,14 @@
         "${_output_path}/AndroidManifest.xml",
       ]
 
-      if (_scanned_files.resources != []) {
+      if (_scanned_files.has_r_text_file) {
+        # Certain packages, in particular Play Services have no R.txt even
+        # though its presence is mandated by AAR spec. Such packages cause
+        # spurious rebuilds if this output is specified unconditionally.
         outputs += [ "${_output_path}/R.txt" ]
+      }
+
+      if (_scanned_files.resources != []) {
         outputs += get_path_info(
                 rebase_path(_scanned_files.resources, "", _output_path),
                 "abspath")
@@ -2728,7 +2740,7 @@
     }
 
     # Create the android_resources target for resources.
-    if (_scanned_files.resources != []) {
+    if (_scanned_files.resources != [] || _scanned_files.has_r_text_file) {
       _res_target_name = "${target_name}__res"
       android_resources(_res_target_name) {
         forward_variables_from(invoker, [ "deps" ])
@@ -2742,6 +2754,9 @@
             rebase_path(_scanned_files.resources, "", _output_path)
         android_manifest_dep = ":$_unpack_target_name"
         android_manifest = "${_output_path}/AndroidManifest.xml"
+        if (_scanned_files.has_r_text_file) {
+          r_text_file = "${_output_path}/R.txt"
+        }
         v14_skip = true
       }
     }
diff --git a/chrome/android/java/proguard.flags b/chrome/android/java/proguard.flags
index 3a301b7..cedca4c 100644
--- a/chrome/android/java/proguard.flags
+++ b/chrome/android/java/proguard.flags
@@ -27,9 +27,6 @@
   public <init>(...);
 }
 
-# Google Play Services warnings are about its resources.
--dontwarn com.google.android.gms.R**
-
 # The Google Play services library depends on the legacy Apache HTTP library,
 # and just adding it as proguard time dependency causes the following warnings:
 # `library class org.apache.http.params.HttpConnectionParams depends on program
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 b5509909..b024736 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
@@ -41,7 +41,6 @@
     private boolean mImpressionTracked;
 
     public ActionItem(SuggestionsSection section) {
-        super(section);
         mCategoryInfo = section.getCategoryInfo();
         mParentSection = section;
     }
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 e03acc5..a5f11e0 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
@@ -22,12 +22,6 @@
  * to restore the dismissed sections and load new suggestions from the server.
  */
 public class AllDismissedItem extends OptionalLeaf {
-    /**
-     * @param parent The item's parent node.
-     */
-    public AllDismissedItem(NodeParent parent) {
-        super(parent);
-    }
 
     @Override
     @ItemViewType
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ChildNode.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ChildNode.java
index 87151a0..0aaf26f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ChildNode.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ChildNode.java
@@ -4,31 +4,34 @@
 
 package org.chromium.chrome.browser.ntp.cards;
 
+import android.support.annotation.CallSuper;
+
 /**
  * A node in the tree that has a parent and can notify it about changes.
  *
  * This class mostly serves as a convenience base class for implementations of {@link TreeNode}.
  */
 public abstract class ChildNode implements TreeNode {
-    private final NodeParent mParent;
+    private NodeParent mParent;
 
-    protected ChildNode(NodeParent parent) {
+    @Override
+    @CallSuper
+    public void setParent(NodeParent parent) {
+        assert mParent == null;
+        assert parent != null;
         mParent = parent;
     }
 
-    @Override
-    public void init() {}
-
     protected void notifyItemRangeChanged(int index, int count) {
-        mParent.onItemRangeChanged(this, index, count);
+        if (mParent != null) mParent.onItemRangeChanged(this, index, count);
     }
 
     protected void notifyItemRangeInserted(int index, int count) {
-        mParent.onItemRangeInserted(this, index, count);
+        if (mParent != null) mParent.onItemRangeInserted(this, index, count);
     }
 
     protected void notifyItemRangeRemoved(int index, int count) {
-        mParent.onItemRangeRemoved(this, index, count);
+        if (mParent != null) mParent.onItemRangeRemoved(this, index, count);
     }
 
     protected void notifyItemChanged(int index) {
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 563ccfe2..9fca3e1 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
@@ -19,12 +19,6 @@
  * A footer to show some text and a link to learn more.
  */
 public class Footer extends OptionalLeaf {
-    /**
-     * @param parent The footer's parent node.
-     */
-    public Footer(NodeParent parent) {
-        super(parent);
-    }
 
     @Override
     @ItemViewType
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/InnerNode.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/InnerNode.java
index 3d6c70b..0c57425 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/InnerNode.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/InnerNode.java
@@ -4,62 +4,58 @@
 
 package org.chromium.chrome.browser.ntp.cards;
 
+import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
  * An inner node in the tree: the root of a subtree, with a list of child nodes.
  */
-public abstract class InnerNode extends ChildNode implements NodeParent {
-    public InnerNode(NodeParent parent) {
-        super(parent);
-    }
-
-    protected abstract List<TreeNode> getChildren();
+public class InnerNode extends ChildNode implements NodeParent {
+    private final List<TreeNode> mChildren = new ArrayList<>();
 
     private int getChildIndexForPosition(int position) {
         if (position < 0) {
             throw new IndexOutOfBoundsException(Integer.toString(position));
         }
-        List<TreeNode> children = getChildren();
         int numItems = 0;
-        int numChildren = children.size();
+        int numChildren = mChildren.size();
         for (int i = 0; i < numChildren; i++) {
-            numItems += children.get(i).getItemCount();
+            numItems += mChildren.get(i).getItemCount();
             if (position < numItems) return i;
         }
         throw new IndexOutOfBoundsException(position + "/" + numItems);
     }
 
     private int getStartingOffsetForChildIndex(int childIndex) {
-        List<TreeNode> children = getChildren();
-        if (childIndex < 0 || childIndex >= children.size()) {
-            throw new IndexOutOfBoundsException(childIndex + "/" + children.size());
+        if (childIndex < 0 || childIndex >= mChildren.size()) {
+            throw new IndexOutOfBoundsException(childIndex + "/" + mChildren.size());
         }
 
         int offset = 0;
         for (int i = 0; i < childIndex; i++) {
-            offset += children.get(i).getItemCount();
+            offset += mChildren.get(i).getItemCount();
         }
         return offset;
     }
 
     int getStartingOffsetForChild(TreeNode child) {
-        return getStartingOffsetForChildIndex(getChildren().indexOf(child));
+        return getStartingOffsetForChildIndex(mChildren.indexOf(child));
     }
 
     /**
      * Returns the child whose subtree contains the item at the given position.
      */
     TreeNode getChildForPosition(int position) {
-        return getChildren().get(getChildIndexForPosition(position));
+        return mChildren.get(getChildIndexForPosition(position));
     }
 
     @Override
     public int getItemCount() {
         int numItems = 0;
-        for (TreeNode child : getChildren()) {
+        for (TreeNode child : mChildren) {
             numItems += child.getItemCount();
         }
         return numItems;
@@ -69,28 +65,28 @@
     @ItemViewType
     public int getItemViewType(int position) {
         int index = getChildIndexForPosition(position);
-        return getChildren().get(index).getItemViewType(
+        return mChildren.get(index).getItemViewType(
                 position - getStartingOffsetForChildIndex(index));
     }
 
     @Override
     public void onBindViewHolder(NewTabPageViewHolder holder, int position) {
         int index = getChildIndexForPosition(position);
-        getChildren().get(index).onBindViewHolder(
+        mChildren.get(index).onBindViewHolder(
                 holder, position - getStartingOffsetForChildIndex(index));
     }
 
     @Override
     public SnippetArticle getSuggestionAt(int position) {
         int index = getChildIndexForPosition(position);
-        return getChildren().get(index).getSuggestionAt(
+        return mChildren.get(index).getSuggestionAt(
                 position - getStartingOffsetForChildIndex(index));
     }
 
     @Override
     public int getDismissSiblingPosDelta(int position) {
         int index = getChildIndexForPosition(position);
-        return getChildren().get(index).getDismissSiblingPosDelta(
+        return mChildren.get(index).getDismissSiblingPosDelta(
                 position - getStartingOffsetForChildIndex(index));
     }
 
@@ -109,33 +105,63 @@
         notifyItemRangeRemoved(getStartingOffsetForChild(child) + index, count);
     }
 
-    @Override
-    public void init() {
-        super.init();
-        for (TreeNode child : getChildren()) {
-            child.init();
-        }
-    }
-
     /**
-     * Helper method for adding a new child node. Notifies about the inserted items and initializes
-     * the child.
+     * Helper method that adds a new child node and notifies about its insertion.
      *
      * @param child The child node to be added.
      */
-    protected void didAddChild(TreeNode child) {
+    protected void addChild(TreeNode child) {
+        int insertedIndex = getItemCount();
+        mChildren.add(child);
+        child.setParent(this);
+
         int count = child.getItemCount();
-        if (count > 0) onItemRangeInserted(child, 0, count);
-        child.init();
+        if (count > 0) notifyItemRangeInserted(insertedIndex, count);
     }
 
     /**
-     * Helper method for removing a child node. Notifies about the removed items.
+     * Helper method that adds all the children and notifies about the inserted items.
+     */
+    protected void addChildren(TreeNode... children) {
+        int initialCount = getItemCount();
+        for (TreeNode child : children) {
+            mChildren.add(child);
+            child.setParent(this);
+        }
+
+        int addedCount = getItemCount() - initialCount;
+        if (addedCount > 0) notifyItemRangeInserted(initialCount, addedCount);
+    }
+
+    /**
+     * Helper method that removes a child node and notifies about the removed items.
      *
      * @param child The child node to be removed.
      */
-    protected void willRemoveChild(TreeNode child) {
+    protected void removeChild(TreeNode child) {
+        int removedIndex = mChildren.indexOf(child);
+        if (removedIndex == -1) throw new IndexOutOfBoundsException();
+
         int count = child.getItemCount();
-        if (count > 0) onItemRangeRemoved(child, 0, count);
+        int childStartingOffset = getStartingOffsetForChildIndex(removedIndex);
+
+        mChildren.remove(removedIndex);
+        if (count > 0) notifyItemRangeRemoved(childStartingOffset, count);
+    }
+
+    /**
+     * Helper method that removes all the children and notifies about the removed items.
+     */
+    protected void removeChildren() {
+        int itemCount = getItemCount();
+        if (itemCount == 0) return;
+
+        mChildren.clear();
+        notifyItemRangeRemoved(0, itemCount);
+    }
+
+    @VisibleForTesting
+    final List<TreeNode> getChildren() {
+        return mChildren;
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/Leaf.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/Leaf.java
index a2282a0..ade8c74 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/Leaf.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/Leaf.java
@@ -11,7 +11,7 @@
  * If the leaf is not to be a permanent member of the tree, see {@link OptionalLeaf} for an
  * implementation that will take care of hiding or showing the item.
  */
-public abstract class Leaf implements TreeNode {
+public abstract class Leaf extends ChildNode {
     @Override
     public int getItemCount() {
         return 1;
@@ -42,9 +42,6 @@
         return 0;
     }
 
-    @Override
-    public void init() {}
-
     /**
      * Display the data for this item.
      * @param holder The view holder that should be updated.
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 ebb7b2d..8b24a1a3 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
@@ -25,9 +25,6 @@
 import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource;
 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
 
-import java.util.Arrays;
-import java.util.List;
-
 /**
  * A class that handles merging above the fold elements and below the fold cards into an adapter
  * that will be used to back the NTP RecyclerView. The first element in the adapter should always be
@@ -43,10 +40,6 @@
     private final ItemTouchCallbacks mItemTouchCallbacks = new ItemTouchCallbacks();
     private NewTabPageRecyclerView mRecyclerView;
 
-    /**
-     * List of all child nodes (which can themselves contain multiple child nodes).
-     */
-    private final List<TreeNode> mChildren;
     private final InnerNode mRoot;
 
     private final AboveTheFoldItem mAboveTheFold = new AboveTheFoldItem();
@@ -132,23 +125,18 @@
         mNewTabPageManager = manager;
         mAboveTheFoldView = aboveTheFoldView;
         mUiConfig = uiConfig;
-        mRoot = new InnerNode(this) {
-            @Override
-            protected List<TreeNode> getChildren() {
-                return mChildren;
-            }
-        };
+        mRoot = new InnerNode();
 
-        mSections = new SectionList(mRoot, mNewTabPageManager, offlinePageBridge);
-        mSigninPromo = new SignInPromo(mRoot, mNewTabPageManager);
-        mAllDismissed = new AllDismissedItem(mRoot);
-        mFooter = new Footer(mRoot);
+        mSections = new SectionList(mNewTabPageManager, offlinePageBridge);
+        mSigninPromo = new SignInPromo(mNewTabPageManager);
+        mAllDismissed = new AllDismissedItem();
+        mFooter = new Footer();
 
-        mChildren = Arrays.asList(
+        mRoot.addChildren(
                 mAboveTheFold, mSections, mSigninPromo, mAllDismissed, mFooter, mBottomSpacer);
-        mRoot.init();
 
         updateAllDismissedVisibility();
+        mRoot.setParent(this);
     }
 
     /** Returns callbacks to configure the interactions with the RecyclerView's items. */
@@ -251,7 +239,7 @@
     public void onItemRangeInserted(TreeNode child, int itemPosition, int itemCount) {
         assert child == mRoot;
         notifyItemRangeInserted(itemPosition, itemCount);
-        notifyItemChanged(getItemCount() - 1); // Refresh the spacer too.
+        mBottomSpacer.refresh();
 
         updateAllDismissedVisibility();
     }
@@ -260,7 +248,7 @@
     public void onItemRangeRemoved(TreeNode child, int itemPosition, int itemCount) {
         assert child == mRoot;
         notifyItemRangeRemoved(itemPosition, itemCount);
-        notifyItemChanged(getItemCount() - 1); // Refresh the spacer too.
+        mBottomSpacer.refresh();
 
         updateAllDismissedVisibility();
     }
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 af1836a..1a3b7ff 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
@@ -19,14 +19,6 @@
 public abstract class OptionalLeaf extends ChildNode {
     private boolean mVisible;
 
-    /**
-     * Constructor for {@link OptionalLeaf}.
-     * By default it is not visible. See {@link #setVisible(boolean)} to update the visibility.
-     */
-    public OptionalLeaf(NodeParent parent) {
-        super(parent);
-    }
-
     @Override
     public int getItemCount() {
         return isVisible() ? 1 : 0;
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 a031edf5..425e3ee 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
@@ -11,9 +11,6 @@
  * @see ProgressViewHolder
  */
 class ProgressItem extends OptionalLeaf {
-    protected ProgressItem(NodeParent parent) {
-        super(parent);
-    }
 
     @Override
     @ItemViewType
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 2143c590..5ac8614db 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
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.ntp.cards;
 
 import org.chromium.base.Log;
+import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.browser.ntp.NewTabPageView.NewTabPageManager;
 import org.chromium.chrome.browser.ntp.snippets.CategoryInt;
 import org.chromium.chrome.browser.ntp.snippets.CategoryStatus;
@@ -15,7 +16,6 @@
 import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource;
 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
 
-import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -29,37 +29,23 @@
 
     /** Maps suggestion categories to sections, with stable iteration ordering. */
     private final Map<Integer, SuggestionsSection> mSections = new LinkedHashMap<>();
-    private final List<TreeNode> mChildren = new ArrayList<>();
     private final NewTabPageManager mNewTabPageManager;
     private final OfflinePageBridge mOfflinePageBridge;
 
-    public SectionList(NodeParent parent, NewTabPageManager newTabPageManager,
-            OfflinePageBridge offlinePageBridge) {
-        super(parent);
+    public SectionList(NewTabPageManager newTabPageManager, OfflinePageBridge offlinePageBridge) {
         mNewTabPageManager = newTabPageManager;
         mNewTabPageManager.getSuggestionsSource().setObserver(this);
         mOfflinePageBridge = offlinePageBridge;
-    }
-
-    @Override
-    public void init() {
-        super.init();
         resetSections(/* alwaysAllowEmptySections = */ false);
     }
 
-    @Override
-    protected List<TreeNode> getChildren() {
-        return mChildren;
-    }
-
     /**
      * Resets the sections, reloading the whole new tab page content.
      * @param alwaysAllowEmptySections Whether sections are always allowed to be displayed when
      *     they are empty, even when they are normally not.
      */
     public void resetSections(boolean alwaysAllowEmptySections) {
-        mSections.clear();
-        mChildren.clear();
+        removeAllSections();
 
         SuggestionsSource suggestionsSource = mNewTabPageManager.getSuggestionsSource();
         int[] categories = suggestionsSource.getCategories();
@@ -105,10 +91,9 @@
 
         // Create the section if needed.
         if (section == null) {
-            section = new SuggestionsSection(this, mNewTabPageManager, mOfflinePageBridge, info);
+            section = new SuggestionsSection(mNewTabPageManager, mOfflinePageBridge, info);
             mSections.put(category, section);
-            mChildren.add(section);
-            didAddChild(section);
+            addChild(section);
         }
 
         // Add the new suggestions.
@@ -229,10 +214,15 @@
         removeSection(section);
     }
 
-    private void removeSection(SuggestionsSection section) {
+    @VisibleForTesting
+    void removeSection(SuggestionsSection section) {
         mSections.remove(section.getCategory());
-        willRemoveChild(section);
-        mChildren.remove(section);
+        removeChild(section);
+    }
+
+    private void removeAllSections() {
+        mSections.clear();
+        removeChildren();
     }
 
     /**
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 1ad2c93..a8c1ca1 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
@@ -42,8 +42,7 @@
     @Nullable
     private final SigninObserver mObserver;
 
-    public SignInPromo(NodeParent parent, NewTabPageManager newTabPageManager) {
-        super(parent);
+    public SignInPromo(NewTabPageManager newTabPageManager) {
         mDismissed = ChromePreferenceManager.getInstance(ContextUtils.getApplicationContext())
                              .getNewTabPageSigninPromoDismissed();
 
@@ -54,12 +53,7 @@
             mObserver = new SigninObserver(signinManager);
             newTabPageManager.addDestructionObserver(mObserver);
         }
-    }
 
-    @Override
-    public void init() {
-        super.init();
-        SigninManager signinManager = SigninManager.get(ContextUtils.getApplicationContext());
         setVisible(signinManager.isSignInAllowed() && !signinManager.isSignedInOnNative());
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SpacingItem.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SpacingItem.java
index c4c045d..0409f88 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SpacingItem.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SpacingItem.java
@@ -41,4 +41,9 @@
     protected void onBindViewHolder(NewTabPageViewHolder holder) {
         // Nothing to do.
     }
+
+    /** Schedules a recalculation of the space occupied by the item. */
+    public void refresh() {
+        notifyItemChanged(0);
+    }
 }
\ No newline at end of file
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 1c684de8..c68445a 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
@@ -14,20 +14,14 @@
  * configuration that affects the NTP suggestions.
  */
 public abstract class StatusItem extends OptionalLeaf implements StatusCardViewHolder.DataSource {
-
-    protected StatusItem(NodeParent parent) {
-        super(parent);
-    }
-
-    public static StatusItem createNoSuggestionsItem(SuggestionsSection parentSection) {
-        return new NoSuggestionsItem(parentSection);
+    public static StatusItem createNoSuggestionsItem(SuggestionsCategoryInfo categoryInfo) {
+        return new NoSuggestionsItem(categoryInfo);
     }
 
     private static class NoSuggestionsItem extends StatusItem {
         private final String mDescription;
-        public NoSuggestionsItem(SuggestionsSection parentSection) {
-            super(parentSection);
-            mDescription = parentSection.getCategoryInfo().getNoSuggestionsMessage();
+        public NoSuggestionsItem(SuggestionsCategoryInfo categoryInfo) {
+            mDescription = categoryInfo.getNoSuggestionsMessage();
         }
 
         @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSection.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSection.java
index f237cfb9..cac3245b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSection.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSection.java
@@ -20,7 +20,6 @@
 import org.chromium.chrome.browser.offlinepages.OfflinePageItem;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 
@@ -41,35 +40,21 @@
     private final ActionItem mMoreButton;
     private final ProgressItem mProgressIndicator;
 
-    private final List<TreeNode> mChildren;
-
     private boolean mIsNtpDestroyed;
 
-    public SuggestionsSection(NodeParent parent, NewTabPageManager manager,
-            OfflinePageBridge offlinePageBridge, SuggestionsCategoryInfo info) {
-        super(parent);
+    public SuggestionsSection(NewTabPageManager manager, OfflinePageBridge offlinePageBridge,
+            SuggestionsCategoryInfo info) {
         mCategoryInfo = info;
         mOfflinePageBridge = offlinePageBridge;
 
         mHeader = new SectionHeader(info.getTitle());
-        mSuggestionsList = new SuggestionsList(this, info);
-        mStatus = StatusItem.createNoSuggestionsItem(this);
+        mSuggestionsList = new SuggestionsList(info);
+        mStatus = StatusItem.createNoSuggestionsItem(info);
         mMoreButton = new ActionItem(this);
-        mProgressIndicator = new ProgressItem(this);
-
-        mChildren = Arrays.asList(
-                mHeader,
-                mSuggestionsList,
-                mStatus,
-                mMoreButton,
-                mProgressIndicator);
+        mProgressIndicator = new ProgressItem();
+        addChildren(mHeader, mSuggestionsList, mStatus, mMoreButton, mProgressIndicator);
 
         setupOfflinePageBridgeObserver(manager);
-    }
-
-    @Override
-    public void init() {
-        super.init();
         refreshChildrenVisibility();
     }
 
@@ -77,8 +62,7 @@
         private final List<SnippetArticle> mSuggestions = new ArrayList<>();
         private final SuggestionsCategoryInfo mCategoryInfo;
 
-        public SuggestionsList(NodeParent parent, SuggestionsCategoryInfo categoryInfo) {
-            super(parent);
+        public SuggestionsList(SuggestionsCategoryInfo categoryInfo) {
             mCategoryInfo = categoryInfo;
         }
 
@@ -140,11 +124,6 @@
         }
     }
 
-    @Override
-    protected List<TreeNode> getChildren() {
-        return mChildren;
-    }
-
     private void setupOfflinePageBridgeObserver(NewTabPageManager manager) {
         final OfflinePageBridge.OfflinePageModelObserver observer =
                 new OfflinePageBridge.OfflinePageModelObserver() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/TreeNode.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/TreeNode.java
index 75997a4..6816a1a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/TreeNode.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/TreeNode.java
@@ -4,8 +4,6 @@
 
 package org.chromium.chrome.browser.ntp.cards;
 
-import android.support.annotation.CallSuper;
-
 import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
 
 /**
@@ -17,8 +15,7 @@
      * node has been added to the tree, i.e. when it is in the list of its parent's children.
      * The node may notify its parent about changes that happen during initialization.
      */
-    @CallSuper
-    void init();
+    void setParent(NodeParent parent);
 
     /**
      * Returns the number of items under this subtree. This method may be called
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/AutofillPaymentInstrument.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/AutofillPaymentInstrument.java
index 2069212..3aee75e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/AutofillPaymentInstrument.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/AutofillPaymentInstrument.java
@@ -42,6 +42,7 @@
     @Nullable private InstrumentDetailsCallback mCallback;
     private boolean mIsWaitingForBillingNormalization;
     private boolean mIsWaitingForFullCardDetails;
+    private boolean mHasValidNumberAndName;
 
     /**
      * Builds a payment instrument for the given credit card.
@@ -80,7 +81,10 @@
             InstrumentDetailsCallback callback) {
         // The billing address should never be null for a credit card at this point.
         assert mBillingAddress != null;
+        assert AutofillAddress.checkAddressCompletionStatus(mBillingAddress)
+                == AutofillAddress.COMPLETE;
         assert mIsComplete;
+        assert mHasValidNumberAndName;
         assert mCallback == null;
         mCallback = callback;
 
@@ -210,12 +214,23 @@
     @Override
     public void dismissInstrument() {}
 
-    /** @return Whether the card is complete and ready to be sent to the merchant as-is. */
+    /**
+     * @return Whether the card is complete and ready to be sent to the merchant as-is. If true,
+     * this card has a valid card number, a non-empty name on card, and a complete billing address.
+     */
     public boolean isComplete() {
         return mIsComplete;
     }
 
     /**
+     * @return Whether the card number is valid and name on card is non-empty. Billing address is
+     * not taken into consideration.
+     */
+    public boolean isValid() {
+        return mHasValidNumberAndName;
+    }
+
+    /**
      * Updates the instrument and marks it "complete." Called after the user has edited this
      * instrument.
      *
@@ -239,15 +254,19 @@
                               mContext.getResources(), card.getIssuerIconDrawableId()));
         checkAndUpateCardCompleteness();
         assert mIsComplete;
+        assert mHasValidNumberAndName;
     }
 
     /**
      * Checks whether card is complete, i.e., can be sent to the merchant as-is without editing
      * first. And updates edit message, edit title and complete status.
      *
-     * For both local and server cards, verifies that the billing address is complete. For local
+     * For both local and server cards, verifies that the billing address is present. For local
      * cards also verifies that the card number is valid and the name on card is not empty.
      *
+     * Does not check that the billing address has all of the required fields. This is done
+     * elsewhere to filter out such billing addresses entirely.
+     *
      * Does not check the expiration date. If the card is expired, the user has the opportunity
      * update the expiration date when providing their CVC in the card unmask dialog.
      *
@@ -265,8 +284,10 @@
             invalidFieldsCount++;
         }
 
+        mHasValidNumberAndName = true;
         if (mCard.getIsLocal()) {
             if (TextUtils.isEmpty(mCard.getName())) {
+                mHasValidNumberAndName = false;
                 editMessageResId = R.string.payments_name_on_card_required;
                 editTitleResId = R.string.payments_add_name_on_card;
                 invalidFieldsCount++;
@@ -275,6 +296,7 @@
             if (PersonalDataManager.getInstance().getBasicCardPaymentType(
                         mCard.getNumber().toString(), true)
                     == null) {
+                mHasValidNumberAndName = false;
                 editMessageResId = R.string.payments_card_number_invalid;
                 editTitleResId = R.string.payments_add_valid_card_number;
                 invalidFieldsCount++;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
index 90a8c8e..e1c98792 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
@@ -183,7 +183,7 @@
 
     /**
      * In-memory mapping of the origins of websites that have recently called canMakePayment()
-     * to the list of the payment methods that were been queried. Used for throttling the usage of
+     * to the list of the payment methods that were being queried. Used for throttling the usage of
      * this call. The mapping is shared among all instances of PaymentRequestImpl in the browser
      * process on UI thread. The user can reset the throttling mechanism by restarting the browser.
      */
@@ -268,6 +268,12 @@
     /** True if any of the requested payment methods are supported. */
     private boolean mArePaymentMethodsSupported;
 
+    /**
+     * True after at least one usable payment instrument has been found. Should be read only after
+     * all payment apps have been queried.
+     */
+    private boolean mCanMakePayment;
+
     /** The helper to create and fill the response to send to the merchant. */
     private PaymentResponseHelper mPaymentResponseHelper;
 
@@ -1174,10 +1180,7 @@
         }
 
         query.addObserver(this);
-        if (mPendingApps.isEmpty() && mPendingInstruments.isEmpty()) {
-            query.setResponse(mPaymentMethodsSection != null
-                    && mPaymentMethodsSection.getSelectedItem() != null);
-        }
+        if (isFinishedQueryingPaymentApps()) query.setResponse(mCanMakePayment);
     }
 
     private void respondCanMakePaymentQuery(boolean response) {
@@ -1240,18 +1243,22 @@
         if (disconnectIfNoPaymentMethodsSupported()) return;
 
         // Load the validation rules for each unique region code in the credit card billing
-        // addresses.
+        // addresses and check for validity.
         Set<String> uniqueCountryCodes = new HashSet<>();
         for (int i = 0; i < mPendingAutofillInstruments.size(); ++i) {
             assert mPendingAutofillInstruments.get(i) instanceof AutofillPaymentInstrument;
+            AutofillPaymentInstrument creditCard =
+                    (AutofillPaymentInstrument) mPendingAutofillInstruments.get(i);
 
-            String countryCode = AutofillAddress
-                    .getCountryCode(((AutofillPaymentInstrument) mPendingAutofillInstruments.get(
-                            i)).getBillingAddress());
+            String countryCode = AutofillAddress.getCountryCode(creditCard.getBillingAddress());
             if (!uniqueCountryCodes.contains(countryCode)) {
                 uniqueCountryCodes.add(countryCode);
                 PersonalDataManager.getInstance().loadRulesForRegion(countryCode);
             }
+
+            // If there's a card on file with a valid number and a name, then
+            // PaymentRequest.canMakePayment() returns true.
+            mCanMakePayment |= creditCard.isValid();
         }
 
         // List order:
@@ -1267,18 +1274,22 @@
 
         mPendingAutofillInstruments.clear();
 
-        // Pre-select the first instrument on the list, if it is complete.
+        // Possibly pre-select the first instrument on the list.
         int selection = SectionInformation.NO_SELECTION;
         if (!mPendingInstruments.isEmpty()) {
             PaymentInstrument first = mPendingInstruments.get(0);
-            if (!(first instanceof AutofillPaymentInstrument)
-                    || ((AutofillPaymentInstrument) first).isComplete()) {
+            if (first instanceof AutofillPaymentInstrument) {
+                AutofillPaymentInstrument creditCard = (AutofillPaymentInstrument) first;
+                if (creditCard.isComplete()) selection = 0;
+            } else {
+                // If a payment app is available, then PaymentRequest.canMakePayment() returns true.
+                mCanMakePayment = true;
                 selection = 0;
             }
         }
 
         CanMakePaymentQuery query = sCanMakePaymentQueries.get(mOrigin);
-        if (query != null) query.setResponse(selection == 0);
+        if (query != null) query.setResponse(mCanMakePayment);
 
         // The list of payment instruments is ready to display.
         mPaymentMethodsSection = new SectionInformation(PaymentRequestUI.TYPE_PAYMENT_METHODS,
@@ -1298,10 +1309,7 @@
      * @return True if no payment methods are supported
      */
     private boolean disconnectIfNoPaymentMethodsSupported() {
-        if (mPendingApps == null || !mPendingApps.isEmpty() || !mPendingInstruments.isEmpty()) {
-            // Waiting for pending apps and instruments.
-            return false;
-        }
+        if (!isFinishedQueryingPaymentApps()) return false;
 
         boolean foundPaymentMethods = mPaymentMethodsSection != null
                 && !mPaymentMethodsSection.isEmpty();
@@ -1325,6 +1333,11 @@
         return false;
     }
 
+    /** @return True after payment apps have been queried. */
+    private boolean isFinishedQueryingPaymentApps() {
+        return mPendingApps != null && mPendingApps.isEmpty() && mPendingInstruments.isEmpty();
+    }
+
     /**
      * Saves the given instrument in either "autofill" or "non-autofill" list. The separation
      * enables placing autofill instruments on the bottom of the list.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentQueryNoCardTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentQueryNoCardTest.java
index fd842d55..5d923ac 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentQueryNoCardTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentQueryNoCardTest.java
@@ -29,8 +29,8 @@
         // The user has an incomplete credit card on file. This is not sufficient for
         // canMakePayment() to return true.
         new AutofillTestHelper().setCreditCard(new CreditCard("", "https://example.com", true, true,
-                "Jon Doe", "4111111111111111", "1111", "12", "2050", "visa", R.drawable.pr_visa,
-                "" /* billingAddressId */, "" /* serverId */));
+                "" /* nameOnCard */, "4111111111111111", "1111", "12", "2050", "visa",
+                R.drawable.pr_visa, "" /* billingAddressId */, "" /* serverId */));
     }
 
     @MediumTest
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentQueryTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentQueryTest.java
index b0443bd..ec5494a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentQueryTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCanMakePaymentQueryTest.java
@@ -9,15 +9,14 @@
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
-import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
 
 /**
- * A payment integration test for checking whether user can make a payment via either payment
- * app or a credit card. This user has a complete credit card on file.
+ * A payment integration test for checking whether user can make a payment via either payment app or
+ * a credit card. This user has a valid credit card without a billing address on file.
  */
 public class PaymentRequestCanMakePaymentQueryTest extends PaymentRequestTestBase {
     public PaymentRequestCanMakePaymentQueryTest() {
@@ -27,15 +26,11 @@
     @Override
     public void onMainActivityStarted() throws InterruptedException, ExecutionException,
             TimeoutException {
-        // The user has a complete credit card on file. This is sufficient for
-        // canMakePayment() to return true.
-        AutofillTestHelper helper = new AutofillTestHelper();
-        String billingAddressId = helper.setProfile(new AutofillProfile("", "https://example.com",
-                true, "Jon Doe", "Google", "340 Main St", "CA", "Los Angeles", "", "90291", "",
-                "US", "555-555-5555", "", "en-US"));
-        helper.setCreditCard(new CreditCard("", "https://example.com", true, true, "Jon Doe",
-                "4111111111111111", "1111", "12", "2050", "visa", R.drawable.pr_visa,
-                billingAddressId, "" /* serverId */));
+        // The user has a valid credit card without a billing address on file. This is sufficient
+        // for canMakePayment() to return true.
+        new AutofillTestHelper().setCreditCard(new CreditCard("", "https://example.com", true, true,
+                "Jon Doe", "4111111111111111", "1111", "12", "2050", "visa", R.drawable.pr_visa,
+                "" /* billingAddressId */, "" /* serverId */));
     }
 
     @MediumTest
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCcCanMakePaymentQueryNoCardTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCcCanMakePaymentQueryNoCardTest.java
index 6f1d3ce..9413daa 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCcCanMakePaymentQueryNoCardTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCcCanMakePaymentQueryNoCardTest.java
@@ -29,8 +29,8 @@
         // The user has an incomplete credit card on file. This is not sufficient for
         // canMakePayment() to return true.
         new AutofillTestHelper().setCreditCard(new CreditCard("", "https://example.com", true, true,
-                "Jon Doe", "4111111111111111", "1111", "12", "2050", "visa", R.drawable.pr_visa,
-                "" /* billingAddressId */, "" /* serverId */));
+                "" /* nameOnCard */, "4111111111111111", "1111", "12", "2050", "visa",
+                R.drawable.pr_visa, "" /* billingAddressId */, "" /* serverId */));
     }
 
     @MediumTest
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCcCanMakePaymentQueryTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCcCanMakePaymentQueryTest.java
index 56dc5b06..c87d1777 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCcCanMakePaymentQueryTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestCcCanMakePaymentQueryTest.java
@@ -9,15 +9,14 @@
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.autofill.AutofillTestHelper;
-import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
 
 /**
- * A payment integration test for checking whether user can make a payment via a credit card.
- * This user has a complete credit card on file.
+ * A payment integration test for checking whether user can make a payment via a credit card. This
+ * user has a valid  credit card without a billing address on file.
  */
 public class PaymentRequestCcCanMakePaymentQueryTest extends PaymentRequestTestBase {
     public PaymentRequestCcCanMakePaymentQueryTest() {
@@ -27,15 +26,11 @@
     @Override
     public void onMainActivityStarted() throws InterruptedException, ExecutionException,
             TimeoutException {
-        // The user has a complete credit card on file. This is sufficient for
-        // canMakePayment() to return true.
-        AutofillTestHelper helper = new AutofillTestHelper();
-        String billingAddressId = helper.setProfile(new AutofillProfile("", "https://example.com",
-                true, "Jon Doe", "Google", "340 Main St", "CA", "Los Angeles", "", "90291", "",
-                "US", "555-555-5555", "", "en-US"));
-        helper.setCreditCard(new CreditCard("", "https://example.com", true, true, "Jon Doe",
-                "4111111111111111", "1111", "12", "2050", "visa", R.drawable.pr_visa,
-                billingAddressId, "" /* serverId */));
+        // The user has a valid credit card without a billing address on file. This is sufficient
+        // for canMakePayment() to return true.
+        new AutofillTestHelper().setCreditCard(new CreditCard("", "https://example.com", true, true,
+                "Jon Doe", "4111111111111111", "1111", "12", "2050", "visa", R.drawable.pr_visa,
+                "" /* billingAddressId */, "" /* serverId */));
     }
 
     @MediumTest
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/InnerNodeTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/InnerNodeTest.java
index 07e9b5e..2ba0adc 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/InnerNodeTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/InnerNodeTest.java
@@ -6,11 +6,19 @@
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import android.support.annotation.Nullable;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -39,26 +47,15 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mInnerNode = spy(new InnerNode());
 
-        for (int i = 0; i < ITEM_COUNTS.length; i++) {
-            TreeNode child = mock(TreeNode.class);
-            when(child.getItemCount()).thenReturn(ITEM_COUNTS[i]);
+        for (int childItemCount : ITEM_COUNTS) {
+            TreeNode child = makeDummyNode(childItemCount);
             mChildren.add(child);
+            mInnerNode.addChild(child);
         }
-        mInnerNode = new InnerNode(mParent) {
-            @Override
-            protected List<TreeNode> getChildren() {
-                return mChildren;
-            }
-        };
-    }
 
-    @Test
-    public void testInit() {
-        mInnerNode.init();
-        for (TreeNode child : mChildren) {
-            verify(child).init();
-        }
+        mInnerNode.setParent(mParent);
     }
 
     @Test
@@ -112,37 +109,36 @@
     }
 
     @Test
-    public void testDidAddChild() {
-        TreeNode child = mock(TreeNode.class);
-        when(child.getItemCount()).thenReturn(23);
-        mChildren.add(3, child);
-        mInnerNode.didAddChild(child);
+    public void testAddChild() {
+        final int itemCountBefore = mInnerNode.getItemCount();
 
-        // The child should have been initialized and the parent notified about the added items.
-        verify(child).init();
-        verify(mParent).onItemRangeInserted(mInnerNode, 6, 23);
+        TreeNode child = makeDummyNode(23);
+        mInnerNode.addChild(child);
 
-        TreeNode child2 = mock(TreeNode.class);
-        when(child2.getItemCount()).thenReturn(0);
-        mChildren.add(4, child2);
-        mInnerNode.didAddChild(child2);
-        verify(child2).init();
+        // The child should have been initialized and the parent hierarchy notified about it.
+        verify(child).setParent(eq(mInnerNode));
+        verify(mParent).onItemRangeInserted(mInnerNode, itemCountBefore, 23);
+
+        TreeNode child2 = makeDummyNode(0);
+        mInnerNode.addChild(child2);
 
         // The empty child should have been initialized, but there should be no change
         // notifications.
+        verify(child2).setParent(eq(mInnerNode));
         verifyNoMoreInteractions(mParent);
     }
 
     @Test
-    public void testWillRemoveChild() {
-        mInnerNode.willRemoveChild(mChildren.get(4));
-        mChildren.remove(4);
+    public void testRemoveChild() {
+        TreeNode child = mChildren.get(4);
+        mInnerNode.removeChild(child);
 
         // The parent should have been notified about the removed items.
         verify(mParent).onItemRangeRemoved(mInnerNode, 6, 3);
 
-        mInnerNode.willRemoveChild(mChildren.get(3));
-        mChildren.remove(3);
+        reset(mParent); // Prepare for the #verifyNoMoreInteractions() call below.
+        TreeNode child2 = mChildren.get(3);
+        mInnerNode.removeChild(child2);
 
         // There should be no change notifications about the empty child.
         verifyNoMoreInteractions(mParent);
@@ -160,5 +156,78 @@
         verify(mParent).onItemRangeChanged(mInnerNode, 6, 6502);
         verify(mParent).onItemRangeRemoved(mInnerNode, 11, 8086);
     }
+
+    /**
+     * Tests that {@link ChildNode} sends the change notifications AFTER its child list is modified.
+     */
+    @Test
+    public void testChangeNotificationsTiming() {
+        // The MockModeParent will enforce a given number of items in the child when notified.
+        MockNodeParent parent = spy(new MockNodeParent());
+        InnerNode rootNode = new InnerNode();
+
+        TreeNode[] children = {makeDummyNode(3), makeDummyNode(5)};
+        rootNode.addChildren(children);
+        rootNode.setParent(parent);
+
+        assertThat(rootNode.getItemCount(), is(8));
+        verifyZeroInteractions(parent); // Before the parent is set, no notifications.
+
+        parent.expectItemCount(24);
+        rootNode.addChildren(makeDummyNode(7), makeDummyNode(9)); // Should bundle the insertions.
+        verify(parent).onItemRangeInserted(eq(rootNode), eq(8), eq(16));
+
+        parent.expectItemCount(28);
+        rootNode.addChild(makeDummyNode(4));
+        verify(parent).onItemRangeInserted(eq(rootNode), eq(24), eq(4));
+
+        parent.expectItemCount(23);
+        rootNode.removeChild(children[1]);
+        verify(parent).onItemRangeRemoved(eq(rootNode), eq(3), eq(5));
+
+        parent.expectItemCount(0);
+        rootNode.removeChildren(); // Bundles the removals in a single change notification
+        verify(parent).onItemRangeRemoved(eq(rootNode), eq(0), eq(23));
+    }
+
+    /**
+     * Implementation of {@link NodeParent} that checks the item count from the node that
+     * sends notifications against defined expectations. Fails on unexpected calls.
+     */
+    private static class MockNodeParent implements NodeParent {
+        @Nullable
+        private Integer mNextExpectedItemCount;
+
+        public void expectItemCount(int count) {
+            mNextExpectedItemCount = count;
+        }
+
+        @Override
+        public void onItemRangeChanged(TreeNode child, int index, int count) {
+            checkCount(child);
+        }
+
+        @Override
+        public void onItemRangeInserted(TreeNode child, int index, int count) {
+            checkCount(child);
+        }
+
+        @Override
+        public void onItemRangeRemoved(TreeNode child, int index, int count) {
+            checkCount(child);
+        }
+
+        private void checkCount(TreeNode child) {
+            if (mNextExpectedItemCount == null) fail("Unexpected call");
+            assertThat(child.getItemCount(), is(mNextExpectedItemCount));
+            mNextExpectedItemCount = null;
+        }
+    }
+
+    private static TreeNode makeDummyNode(int itemCount) {
+        TreeNode node = mock(TreeNode.class);
+        doReturn(itemCount).when(node).getItemCount();
+        return node;
+    }
 }
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
index ad5f339..4ae471a 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
@@ -347,8 +347,8 @@
     }
 
     private SuggestionsSection createSection(SuggestionsCategoryInfo info) {
-        SuggestionsSection section = new SuggestionsSection(mParent, mManager, mBridge, info);
-        section.init();
+        SuggestionsSection section = new SuggestionsSection(mManager, mBridge, info);
+        section.setParent(mParent);
         return section;
     }
 
diff --git a/chrome/browser/android/offline_pages/evaluation/offline_page_evaluation_bridge.cc b/chrome/browser/android/offline_pages/evaluation/offline_page_evaluation_bridge.cc
index 4964b41..eb9b876e 100644
--- a/chrome/browser/android/offline_pages/evaluation/offline_page_evaluation_bridge.cc
+++ b/chrome/browser/android/offline_pages/evaluation/offline_page_evaluation_bridge.cc
@@ -190,7 +190,7 @@
       base::MakeUnique<RequestCoordinator>(
           std::move(policy), std::move(prerenderer_offliner), std::move(queue),
           std::move(scheduler), network_quality_estimator);
-  request_coordinator->SetImmediateScheduleCallbackForTest(
+  request_coordinator->SetInternalStartProcessingCallbackForTest(
       base::Bind(&android::EvaluationTestScheduler::ImmediateScheduleCallback,
                  base::Unretained(scheduler.get())));
 
diff --git a/chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_service.cc b/chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_service.cc
index 59611d3a..3e54f436 100644
--- a/chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_service.cc
+++ b/chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_service.cc
@@ -28,23 +28,19 @@
 void ArcKioskAppService::OnAppRegistered(
     const std::string& app_id,
     const ArcAppListPrefs::AppInfo& app_info) {
-  if (app_id == app_id_)
-    PreconditionsChanged();
+  PreconditionsChanged();
 }
 
 void ArcKioskAppService::OnAppReadyChanged(const std::string& id, bool ready) {
-  if (id == app_id_)
-    PreconditionsChanged();
+  PreconditionsChanged();
 }
 
 void ArcKioskAppService::OnPackageListInitialRefreshed() {
   // The app could already be registered.
-  app_id_ = GetAppId();
   PreconditionsChanged();
 }
 
 void ArcKioskAppService::OnArcKioskAppsChanged() {
-  app_id_ = GetAppId();
   PreconditionsChanged();
 }
 
@@ -73,7 +69,6 @@
   app_manager_ = ArcKioskAppManager::Get();
   if (app_manager_) {
     app_manager_->AddObserver(this);
-    app_id_ = GetAppId();
   }
   pref_change_registrar_.reset(new PrefChangeRegistrar());
   pref_change_registrar_->Init(profile_->GetPrefs());
@@ -93,6 +88,9 @@
 }
 
 void ArcKioskAppService::PreconditionsChanged() {
+  app_id_ = GetAppId();
+  if (app_id_.empty())
+    return;
   app_info_ = prefs_->GetApp(app_id_);
   if (app_info_ && app_info_->ready &&
       profile_->GetPrefs()->GetBoolean(prefs::kArcPolicyCompliant)) {
diff --git a/chrome/browser/chromeos/login/easy_unlock/easy_unlock_tpm_key_manager_unittest.cc b/chrome/browser/chromeos/login/easy_unlock/easy_unlock_tpm_key_manager_unittest.cc
index c682c2e..76aae0c 100644
--- a/chrome/browser/chromeos/login/easy_unlock/easy_unlock_tpm_key_manager_unittest.cc
+++ b/chrome/browser/chromeos/login/easy_unlock/easy_unlock_tpm_key_manager_unittest.cc
@@ -9,7 +9,6 @@
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
-#include "base/test/scoped_task_scheduler.h"
 #include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_tpm_key_manager.h"
 #include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_tpm_key_manager_factory.h"
 #include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
@@ -337,7 +336,6 @@
   const AccountId test_account_id_ = AccountId::FromUserEmail(kTestUserId);
 
  private:
-  base::test::ScopedTaskScheduler scoped_task_scheduler_;
   content::TestBrowserThreadBundle thread_bundle_;
 
   // The NSS system slot used by EasyUnlockTPMKeyManagers in tests.
diff --git a/chrome/browser/chromeos/net/client_cert_store_chromeos_unittest.cc b/chrome/browser/chromeos/net/client_cert_store_chromeos_unittest.cc
index bbd7973..3621e9e 100644
--- a/chrome/browser/chromeos/net/client_cert_store_chromeos_unittest.cc
+++ b/chrome/browser/chromeos/net/client_cert_store_chromeos_unittest.cc
@@ -11,7 +11,6 @@
 #include "base/location.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/ref_counted.h"
-#include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
 #include "base/test/scoped_task_scheduler.h"
@@ -90,7 +89,6 @@
 
  private:
   base::test::ScopedTaskScheduler scoped_task_scheduler_;
-  base::MessageLoopForIO message_loop_;
 };
 
 // Ensure that cert requests, that are started before the filter is initialized,
diff --git a/chrome/browser/extensions/api/messaging/message_service.cc b/chrome/browser/extensions/api/messaging/message_service.cc
index 2dda966..b77fbdee 100644
--- a/chrome/browser/extensions/api/messaging/message_service.cc
+++ b/chrome/browser/extensions/api/messaging/message_service.cc
@@ -566,11 +566,6 @@
   channel->receiver.reset(params->receiver.release());
   AddChannel(std::move(channel_ptr), params->receiver_port_id);
 
-  // TODO(robwu): Could |guest_process_id| and |guest_render_frame_routing_id|
-  // be removed? In the past extension message routing was process-based, but
-  // now that extensions are routed from a specific RFH, the special casing for
-  // guest views seems no longer necessary, because the ExtensionMessagePort can
-  // simply obtain the source process & frame ID directly from the RFH.
   int guest_process_id = content::ChildProcessHost::kInvalidUniqueID;
   int guest_render_frame_routing_id = MSG_ROUTING_NONE;
   if (params->include_guest_process_info) {
diff --git a/chrome/browser/predictors/resource_prefetch_predictor.cc b/chrome/browser/predictors/resource_prefetch_predictor.cc
index 85b7d80..310aee3 100644
--- a/chrome/browser/predictors/resource_prefetch_predictor.cc
+++ b/chrome/browser/predictors/resource_prefetch_predictor.cc
@@ -497,6 +497,12 @@
                  prefetch_manager_, url));
 }
 
+void ResourcePrefetchPredictor::OnPrefetchingFinished(
+    const GURL& main_frame_url) {
+  if (observer_)
+    observer_->OnPrefetchingFinished(main_frame_url);
+}
+
 void ResourcePrefetchPredictor::SetObserverForTesting(TestObserver* observer) {
   observer_ = observer;
 }
diff --git a/chrome/browser/predictors/resource_prefetch_predictor.h b/chrome/browser/predictors/resource_prefetch_predictor.h
index d2bbee3..a7ce8bf 100644
--- a/chrome/browser/predictors/resource_prefetch_predictor.h
+++ b/chrome/browser/predictors/resource_prefetch_predictor.h
@@ -160,6 +160,10 @@
   // |main_frame_url|.
   void StopPrefetching(const GURL& main_frame_url);
 
+  // Called when ResourcePrefetcher is finished, i.e. there is nothing pending
+  // in flight.
+  void OnPrefetchingFinished(const GURL& main_frame_url);
+
   // Sets the |observer| to be notified when the resource prefetch predictor
   // data changes. Previously registered observer will be discarded. Call
   // this with nullptr parameter to de-register observer.
@@ -367,6 +371,8 @@
       size_t url_visit_count,
       const ResourcePrefetchPredictor::PageRequestSummary& summary) {}
 
+  virtual void OnPrefetchingFinished(const GURL& main_frame_url) {}
+
   virtual void OnPredictorInitialized() {}
 
  protected:
diff --git a/chrome/browser/predictors/resource_prefetch_predictor_browsertest.cc b/chrome/browser/predictors/resource_prefetch_predictor_browsertest.cc
index c9ab1bf..70c3131 100644
--- a/chrome/browser/predictors/resource_prefetch_predictor_browsertest.cc
+++ b/chrome/browser/predictors/resource_prefetch_predictor_browsertest.cc
@@ -8,6 +8,7 @@
 #include "chrome/browser/predictors/resource_prefetch_predictor_test_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -73,6 +74,9 @@
   GURL url;
 };
 
+// Helper class to track and allow waiting for ResourcePrefetchPredictor
+// initialization. WARNING: OnPredictorInitialized event will not be fired if
+// ResourcePrefetchPredictor is initialized before the observer creation.
 class InitializationObserver : public TestObserver {
  public:
   explicit InitializationObserver(ResourcePrefetchPredictor* predictor)
@@ -145,18 +149,17 @@
 
 }  // namespace
 
-// Helper class to track and allow waiting for ResourcePrefetchPredictor events.
-// These events are also used to verify that ResourcePrefetchPredictor works as
-// expected.
-class ResourcePrefetchPredictorTestObserver : public TestObserver {
+// Helper class to track and allow waiting for a single OnNavigationLearned
+// event. The information provided by this event is also used to verify that
+// ResourcePrefetchPredictor works as expected.
+class LearningObserver : public TestObserver {
  public:
   using PageRequestSummary = ResourcePrefetchPredictor::PageRequestSummary;
 
-  explicit ResourcePrefetchPredictorTestObserver(
-      ResourcePrefetchPredictor* predictor,
-      const size_t expected_url_visit_count,
-      const PageRequestSummary& expected_summary,
-      bool match_navigation_id)
+  LearningObserver(ResourcePrefetchPredictor* predictor,
+                   const size_t expected_url_visit_count,
+                   const PageRequestSummary& expected_summary,
+                   bool match_navigation_id)
       : TestObserver(predictor),
         url_visit_count_(expected_url_visit_count),
         summary_(expected_summary),
@@ -181,7 +184,35 @@
   PageRequestSummary summary_;
   bool match_navigation_id_;
 
-  DISALLOW_COPY_AND_ASSIGN(ResourcePrefetchPredictorTestObserver);
+  DISALLOW_COPY_AND_ASSIGN(LearningObserver);
+};
+
+// Helper class to track and allow waiting for a single OnPrefetchingFinished
+// event. No learning events should be fired while this observer is active.
+class PrefetchingObserver : public TestObserver {
+ public:
+  PrefetchingObserver(ResourcePrefetchPredictor* predictor,
+                      const GURL& expected_main_frame_url)
+      : TestObserver(predictor), main_frame_url_(expected_main_frame_url) {}
+
+  // TestObserver:
+  void OnNavigationLearned(size_t url_visit_count,
+                           const PageRequestSummary& summary) override {
+    ADD_FAILURE() << "Prefetching shouldn't activate learning";
+  }
+
+  void OnPrefetchingFinished(const GURL& main_frame_url) override {
+    EXPECT_EQ(main_frame_url_, main_frame_url);
+    run_loop_.Quit();
+  }
+
+  void Wait() { run_loop_.Run(); }
+
+ private:
+  base::RunLoop run_loop_;
+  GURL main_frame_url_;
+
+  DISALLOW_COPY_AND_ASSIGN(PrefetchingObserver);
 };
 
 class ResourcePrefetchPredictorBrowserTest : public InProcessBrowserTest {
@@ -208,6 +239,19 @@
     EnsurePredictorInitialized();
   }
 
+  void TestLearningAndPrefetching(const GURL& main_frame_url) {
+    // Navigate to |main_frame_url| and check all the expectations.
+    NavigateToURLAndCheckSubresources(main_frame_url);
+    ClearCache();
+    // It is needed to have at least two resource hits to trigger prefetch.
+    NavigateToURLAndCheckSubresources(main_frame_url);
+    ClearCache();
+    // Prefetch all needed resources and change expectations so that all
+    // cacheable resources should be served from cache next navigation.
+    PrefetchURL(main_frame_url);
+    NavigateToURLAndCheckSubresources(main_frame_url);
+  }
+
   void NavigateToURLAndCheckSubresources(
       const GURL& main_frame_url,
       WindowOpenDisposition disposition = WindowOpenDisposition::CURRENT_TAB) {
@@ -219,7 +263,7 @@
       url_request_summaries.push_back(
           GetURLRequestSummaryForResource(endpoint_url, kv.second));
     }
-    ResourcePrefetchPredictorTestObserver observer(
+    LearningObserver observer(
         predictor_, UpdateAndGetVisitCount(main_frame_url),
         CreatePageRequestSummary(endpoint_url.spec(), main_frame_url.spec(),
                                  url_request_summaries),
@@ -228,6 +272,20 @@
         browser(), main_frame_url, disposition,
         ui_test_utils::BROWSER_TEST_NONE);
     observer.Wait();
+    for (auto& kv : resources_) {
+      if (!kv.second.is_no_store && kv.second.should_be_recorded)
+        kv.second.request.was_cached = true;
+    }
+  }
+
+  void PrefetchURL(const GURL& main_frame_url) {
+    PrefetchingObserver observer(predictor_, main_frame_url);
+    predictor_->StartPrefetching(main_frame_url, PrefetchOrigin::EXTERNAL);
+    observer.Wait();
+    for (auto& kv : resources_) {
+      if (!kv.second.is_no_store && kv.second.should_be_recorded)
+        kv.second.request.was_cached = true;
+    }
   }
 
   ResourceSummary* AddResource(const GURL& resource_url,
@@ -273,6 +331,12 @@
     }
   }
 
+  void ClearCache() {
+    chrome::ClearCache(browser());
+    for (auto& kv : resources_)
+      kv.second.request.was_cached = false;
+  }
+
   // Shortcut for convenience.
   GURL GetURL(const std::string& path) const {
     return embedded_test_server()->GetURL(path);
@@ -397,7 +461,7 @@
   std::map<GURL, size_t> visit_count_;
 };
 
-IN_PROC_BROWSER_TEST_F(ResourcePrefetchPredictorBrowserTest, LearningSimple) {
+IN_PROC_BROWSER_TEST_F(ResourcePrefetchPredictorBrowserTest, Simple) {
   // These resources have default priorities that correspond to
   // blink::typeToPriority function.
   AddResource(GetURL(kImagePath), content::RESOURCE_TYPE_IMAGE, net::LOWEST);
@@ -406,11 +470,10 @@
   AddResource(GetURL(kScriptPath), content::RESOURCE_TYPE_SCRIPT, net::MEDIUM);
   AddResource(GetURL(kFontPath), content::RESOURCE_TYPE_FONT_RESOURCE,
               net::HIGHEST);
-  NavigateToURLAndCheckSubresources(GetURL(kHtmlSubresourcesPath));
+  TestLearningAndPrefetching(GetURL(kHtmlSubresourcesPath));
 }
 
-IN_PROC_BROWSER_TEST_F(ResourcePrefetchPredictorBrowserTest,
-                       LearningAfterRedirect) {
+IN_PROC_BROWSER_TEST_F(ResourcePrefetchPredictorBrowserTest, Redirect) {
   AddRedirectChain(GetURL(kRedirectPath), {{net::HTTP_MOVED_PERMANENTLY,
                                             GetURL(kHtmlSubresourcesPath)}});
   AddResource(GetURL(kImagePath), content::RESOURCE_TYPE_IMAGE, net::LOWEST);
@@ -419,11 +482,10 @@
   AddResource(GetURL(kScriptPath), content::RESOURCE_TYPE_SCRIPT, net::MEDIUM);
   AddResource(GetURL(kFontPath), content::RESOURCE_TYPE_FONT_RESOURCE,
               net::HIGHEST);
-  NavigateToURLAndCheckSubresources(GetURL(kRedirectPath));
+  TestLearningAndPrefetching(GetURL(kRedirectPath));
 }
 
-IN_PROC_BROWSER_TEST_F(ResourcePrefetchPredictorBrowserTest,
-                       LearningAfterRedirectChain) {
+IN_PROC_BROWSER_TEST_F(ResourcePrefetchPredictorBrowserTest, RedirectChain) {
   AddRedirectChain(GetURL(kRedirectPath),
                    {{net::HTTP_FOUND, GetURL(kRedirectPath2)},
                     {net::HTTP_MOVED_PERMANENTLY, GetURL(kRedirectPath3)},
@@ -434,15 +496,14 @@
   AddResource(GetURL(kScriptPath), content::RESOURCE_TYPE_SCRIPT, net::MEDIUM);
   AddResource(GetURL(kFontPath), content::RESOURCE_TYPE_FONT_RESOURCE,
               net::HIGHEST);
-  NavigateToURLAndCheckSubresources(GetURL(kRedirectPath));
+  TestLearningAndPrefetching(GetURL(kRedirectPath));
 }
 
 IN_PROC_BROWSER_TEST_F(ResourcePrefetchPredictorBrowserTest,
                        LearningAfterHttpToHttpsRedirect) {
   EnableHttpsServer();
   AddRedirectChain(GetURL(kRedirectPath),
-                   {{net::HTTP_FOUND, https_server()->GetURL(kRedirectPath2)},
-                    {net::HTTP_MOVED_PERMANENTLY,
+                   {{net::HTTP_MOVED_PERMANENTLY,
                      https_server()->GetURL(kHtmlSubresourcesPath)}});
   AddResource(https_server()->GetURL(kImagePath), content::RESOURCE_TYPE_IMAGE,
               net::LOWEST);
@@ -453,10 +514,12 @@
   AddResource(https_server()->GetURL(kFontPath),
               content::RESOURCE_TYPE_FONT_RESOURCE, net::HIGHEST);
   NavigateToURLAndCheckSubresources(GetURL(kRedirectPath));
+  // TODO(alexilin): Test learning and prefetching once crbug.com/650246 is
+  // fixed.
 }
 
 IN_PROC_BROWSER_TEST_F(ResourcePrefetchPredictorBrowserTest,
-                       LearningJavascriptDocumentWrite) {
+                       JavascriptDocumentWrite) {
   auto externalScript =
       AddExternalResource(GetURL(kScriptDocumentWritePath),
                           content::RESOURCE_TYPE_SCRIPT, net::MEDIUM);
@@ -465,11 +528,11 @@
   AddResource(GetURL(kStylePath), content::RESOURCE_TYPE_STYLESHEET,
               net::HIGHEST);
   AddResource(GetURL(kScriptPath), content::RESOURCE_TYPE_SCRIPT, net::MEDIUM);
-  NavigateToURLAndCheckSubresources(GetURL(kHtmlDocumentWritePath));
+  TestLearningAndPrefetching(GetURL(kHtmlDocumentWritePath));
 }
 
 IN_PROC_BROWSER_TEST_F(ResourcePrefetchPredictorBrowserTest,
-                       LearningJavascriptAppendChild) {
+                       JavascriptAppendChild) {
   auto externalScript =
       AddExternalResource(GetURL(kScriptAppendChildPath),
                           content::RESOURCE_TYPE_SCRIPT, net::MEDIUM);
@@ -479,11 +542,11 @@
               net::HIGHEST);
   // This script has net::LOWEST priority because it's executed asynchronously.
   AddResource(GetURL(kScriptPath), content::RESOURCE_TYPE_SCRIPT, net::LOWEST);
-  NavigateToURLAndCheckSubresources(GetURL(kHtmlAppendChildPath));
+  TestLearningAndPrefetching(GetURL(kHtmlAppendChildPath));
 }
 
 IN_PROC_BROWSER_TEST_F(ResourcePrefetchPredictorBrowserTest,
-                       LearningJavascriptInnerHtml) {
+                       JavascriptInnerHtml) {
   auto externalScript = AddExternalResource(
       GetURL(kScriptInnerHtmlPath), content::RESOURCE_TYPE_SCRIPT, net::MEDIUM);
   externalScript->request.mime_type = kJavascriptMime;
@@ -493,13 +556,12 @@
   // https://www.w3.org/TR/2014/REC-html5-20141028/scripting-1.html#the-script-element
   // Script elements don't execute when inserted using innerHTML attribute.
   AddUnrecordedResources({GetURL(kScriptPath)});
-  NavigateToURLAndCheckSubresources(GetURL(kHtmlInnerHtmlPath));
+  TestLearningAndPrefetching(GetURL(kHtmlInnerHtmlPath));
 }
 
 // Requests originated by XMLHttpRequest have content::RESOURCE_TYPE_XHR.
 // Actual resource type is inferred from the mime-type.
-IN_PROC_BROWSER_TEST_F(ResourcePrefetchPredictorBrowserTest,
-                       LearningJavascriptXHR) {
+IN_PROC_BROWSER_TEST_F(ResourcePrefetchPredictorBrowserTest, JavascriptXHR) {
   auto externalScript = AddExternalResource(
       GetURL(kScriptXHRPath), content::RESOURCE_TYPE_SCRIPT, net::MEDIUM);
   externalScript->request.mime_type = kJavascriptMime;
@@ -512,12 +574,12 @@
   auto script = AddResource(GetURL(kScriptPath), content::RESOURCE_TYPE_SCRIPT,
                             net::HIGHEST);
   script->request.mime_type = kJavascriptMime;
-  NavigateToURLAndCheckSubresources(GetURL(kHtmlXHRPath));
+  TestLearningAndPrefetching(GetURL(kHtmlXHRPath));
 }
 
 // ResourcePrefetchPredictor ignores all resources requested from subframes.
 IN_PROC_BROWSER_TEST_F(ResourcePrefetchPredictorBrowserTest,
-                       LearningWithIframe) {
+                       IframeShouldBeIgnored) {
   // Included from html_iframe.html.
   AddResource(GetURL(kImagePath2), content::RESOURCE_TYPE_IMAGE, net::LOWEST);
   AddResource(GetURL(kStylePath2), content::RESOURCE_TYPE_STYLESHEET,
@@ -526,7 +588,7 @@
   // Included from <iframe src="html_subresources.html"> and not recored.
   AddUnrecordedResources({GetURL(kImagePath), GetURL(kStylePath),
                           GetURL(kScriptPath), GetURL(kFontPath)});
-  NavigateToURLAndCheckSubresources(GetURL(kHtmlIframePath));
+  TestLearningAndPrefetching(GetURL(kHtmlIframePath));
 }
 
 }  // namespace predictors
diff --git a/chrome/browser/predictors/resource_prefetcher_manager.cc b/chrome/browser/predictors/resource_prefetcher_manager.cc
index 476ba79..7130038 100644
--- a/chrome/browser/predictors/resource_prefetcher_manager.cc
+++ b/chrome/browser/predictors/resource_prefetcher_manager.cc
@@ -82,7 +82,13 @@
     ResourcePrefetcher* resource_prefetcher) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
-  const std::string key = resource_prefetcher->main_frame_url().host();
+  const GURL& main_frame_url = resource_prefetcher->main_frame_url();
+  BrowserThread::PostTask(
+      BrowserThread::UI, FROM_HERE,
+      base::Bind(&ResourcePrefetchPredictor::OnPrefetchingFinished,
+                 base::Unretained(predictor_), main_frame_url));
+
+  const std::string key = main_frame_url.host();
   auto it = prefetcher_map_.find(key);
   DCHECK(it != prefetcher_map_.end());
   prefetcher_map_.erase(it);
diff --git a/components/BUILD.gn b/components/BUILD.gn
index bfe31f2..1c1eecf 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -138,7 +138,7 @@
     "//components/sync_bookmarks:unit_tests",
     "//components/sync_preferences:unit_tests",
     "//components/sync_sessions:unit_tests",
-    "//components/task_scheduler_util/initialization:unit_tests",
+    "//components/task_scheduler_util/common:unit_tests",
     "//components/task_scheduler_util/variations:unit_tests",
     "//components/test:test_support",
     "//components/translate/core/browser:unit_tests",
diff --git a/components/cronet/OWNERS b/components/cronet/OWNERS
index cc03ecf..078e99e 100644
--- a/components/cronet/OWNERS
+++ b/components/cronet/OWNERS
@@ -1,5 +1,6 @@
 kapishnikov@chromium.org
 mef@chromium.org
 mmenke@chromium.org
+mgersh@chromium.org
 pauljensen@chromium.org
 xunjieli@chromium.org
diff --git a/components/offline_pages/core/background/request_coordinator.cc b/components/offline_pages/core/background/request_coordinator.cc
index e25b4b09..f098edf 100644
--- a/components/offline_pages/core/background/request_coordinator.cc
+++ b/components/offline_pages/core/background/request_coordinator.cc
@@ -178,7 +178,7 @@
       active_request_(nullptr),
       last_offlining_status_(Offliner::RequestStatus::UNKNOWN),
       scheduler_callback_(base::Bind(&EmptySchedulerCallback)),
-      immediate_schedule_callback_(base::Bind(&EmptySchedulerCallback)),
+      internal_start_processing_callback_(base::Bind(&EmptySchedulerCallback)),
       weak_ptr_factory_(this) {
   DCHECK(policy_ != nullptr);
   std::unique_ptr<CleanupTaskFactory> cleanup_factory(
@@ -529,6 +529,18 @@
                                  device_conditions, callback);
 }
 
+// Returns true if the caller should expect a callback, false otherwise.
+bool RequestCoordinator::StartImmediateProcessing(
+    const DeviceConditions& device_conditions,
+    const base::Callback<void(bool)>& callback) {
+  OfflinerImmediateStartStatus immediate_start_status =
+      TryImmediateStart(device_conditions, callback);
+  UMA_HISTOGRAM_ENUMERATION(
+      "OfflinePages.Background.ImmediateStartStatus", immediate_start_status,
+      RequestCoordinator::OfflinerImmediateStartStatus::STATUS_COUNT);
+  return immediate_start_status == OfflinerImmediateStartStatus::STARTED;
+}
+
 bool RequestCoordinator::StartProcessingInternal(
     const ProcessingWindowState processing_state,
     const DeviceConditions& device_conditions,
@@ -549,14 +561,18 @@
 }
 
 void RequestCoordinator::StartImmediatelyIfConnected() {
-  OfflinerImmediateStartStatus immediate_start_status = TryImmediateStart();
-  UMA_HISTOGRAM_ENUMERATION(
-      "OfflinePages.Background.ImmediateStartStatus", immediate_start_status,
-      RequestCoordinator::OfflinerImmediateStartStatus::STATUS_COUNT);
+  // Start processing with manufactured conservative battery conditions
+  // (i.e., assume no battery).
+  // TODO(dougarnett): Obtain actual battery conditions (from Android/Java).
+  DeviceConditions device_conditions(false, 0, GetConnectionType());
+  StartImmediateProcessing(device_conditions,
+                           internal_start_processing_callback_);
 }
 
 RequestCoordinator::OfflinerImmediateStartStatus
-RequestCoordinator::TryImmediateStart() {
+RequestCoordinator::TryImmediateStart(
+    const DeviceConditions& device_conditions,
+    const base::Callback<void(bool)>& callback) {
   DVLOG(2) << "Immediate " << __func__;
   // Make sure not already busy processing.
   if (is_busy_)
@@ -567,11 +583,11 @@
       !offline_pages::IsOfflinePagesSvelteConcurrentLoadingEnabled()) {
     DVLOG(2) << "low end device, returning";
     // Let the scheduler know we are done processing and failed due to svelte.
-    immediate_schedule_callback_.Run(false);
+    callback.Run(false);
     return OfflinerImmediateStartStatus::NOT_STARTED_ON_SVELTE;
   }
 
-  if (GetConnectionType() ==
+  if (device_conditions.GetNetConnectionType() ==
       net::NetworkChangeNotifier::ConnectionType::CONNECTION_NONE) {
     RequestConnectedEventForStarting();
     return OfflinerImmediateStartStatus::NO_CONNECTION;
@@ -581,13 +597,8 @@
     ClearConnectedEventRequest();
   }
 
-  // Start processing with manufactured conservative battery conditions
-  // (i.e., assume no battery).
-  // TODO(dougarnett): Obtain actual battery conditions (from Android/Java).
-
-  DeviceConditions device_conditions(false, 0, GetConnectionType());
   if (StartProcessingInternal(ProcessingWindowState::IMMEDIATE_WINDOW,
-                              device_conditions, immediate_schedule_callback_))
+                              device_conditions, callback))
     return OfflinerImmediateStartStatus::STARTED;
   else
     return OfflinerImmediateStartStatus::NOT_ACCEPTED;
diff --git a/components/offline_pages/core/background/request_coordinator.h b/components/offline_pages/core/background/request_coordinator.h
index 1875759d..854a313 100644
--- a/components/offline_pages/core/background/request_coordinator.h
+++ b/components/offline_pages/core/background/request_coordinator.h
@@ -110,6 +110,19 @@
   bool StartScheduledProcessing(const DeviceConditions& device_conditions,
                                 const base::Callback<void(bool)>& callback);
 
+  // Attempts to starts processing of one or more queued save page later
+  // requests (if device conditions are suitable) in immediate mode
+  // (opposed to scheduled background mode). This method is suitable to call
+  // when there is some user action that suggests the user wants to do this
+  // operation now, if possible, vs. trying to do it in the background when
+  // idle.
+  // Returns whether processing was started and that caller should expect
+  // a callback. If processing was already active or some condition was
+  // not suitable for immediate processing (e.g., network or low-end device),
+  // returns false.
+  bool StartImmediateProcessing(const DeviceConditions& device_conditions,
+                                const base::Callback<void(bool)>& callback);
+
   // Stops the current request processing if active. This is a way for
   // caller to abort processing; otherwise, processing will complete on
   // its own. In either case, the callback will be called when processing
@@ -133,12 +146,12 @@
     scheduler_callback_ = callback;
   }
 
-  // A way to set the callback which would be called if the request will be
-  // scheduled immediately. Used by testing harness to determine if a request
-  // has been processed.
-  void SetImmediateScheduleCallbackForTest(
+  // A way to set the callback which would be called if processing will be
+  // triggered immediately internally by the coordinator. Used by testing
+  // harness to determine if a request has been processed.
+  void SetInternalStartProcessingCallbackForTest(
       const base::Callback<void(bool)> callback) {
-    immediate_schedule_callback_ = callback;
+    internal_start_processing_callback_ = callback;
   }
 
   void StartImmediatelyForTest() { StartImmediatelyIfConnected(); }
@@ -255,7 +268,9 @@
   // as to other device conditions).
   void StartImmediatelyIfConnected();
 
-  OfflinerImmediateStartStatus TryImmediateStart();
+  OfflinerImmediateStartStatus TryImmediateStart(
+      const DeviceConditions& device_conditions,
+      const base::Callback<void(bool)>& callback);
 
   // Requests a callback upon the next network connection to start processing.
   void RequestConnectedEventForStarting();
@@ -408,14 +423,20 @@
   // A set of request_ids that we are holding off until the download manager is
   // done with them.
   std::set<int64_t> disabled_requests_;
-  // Calling this returns to the scheduler across the JNI bridge.
+  // The processing callback to call when processing the current processing
+  // window stops. It is set from the Start*Processing() call that triggered
+  // the processing or it may be the |internal_start_processing_callback_| if
+  // processing was triggered internally.
+  // For StartScheduledProcessing() processing, calling its callback returns
+  // to the scheduler across the JNI bridge.
   base::Callback<void(bool)> scheduler_callback_;
+  // Callback invoked when internally triggered processing is done. It is
+  // kept as a class member so that it may be overridden for test visibility.
+  base::Callback<void(bool)> internal_start_processing_callback_;
   // Logger to record events.
   RequestCoordinatorEventLogger event_logger_;
   // Timer to watch for pre-render attempts running too long.
   base::OneShotTimer watchdog_timer_;
-  // Callback invoked when an immediate request is done (default empty).
-  base::Callback<void(bool)> immediate_schedule_callback_;
   // Used for potential immediate processing when we get network connection.
   std::unique_ptr<ConnectionNotifier> connection_notifier_;
   // Allows us to pass a weak pointer to callbacks.
diff --git a/components/offline_pages/core/background/request_coordinator_unittest.cc b/components/offline_pages/core/background/request_coordinator_unittest.cc
index 1c26bef..1114bf6 100644
--- a/components/offline_pages/core/background/request_coordinator_unittest.cc
+++ b/components/offline_pages/core/background/request_coordinator_unittest.cc
@@ -119,10 +119,10 @@
 
   bool is_starting() { return coordinator_->is_starting(); }
 
-  // Empty callback function.
-  void ImmediateScheduleCallbackFunction(bool result) {
-    immediate_schedule_callback_called_ = true;
-    immediate_schedule_callback_result_ = result;
+  // Test processing callback function.
+  void ProcessingCallbackFunction(bool result) {
+    processing_callback_called_ = true;
+    processing_callback_result_ = result;
   }
 
   // Callback function which releases a wait for it.
@@ -249,17 +249,17 @@
 
   DeviceConditions device_conditions() { return device_conditions_; }
 
-  base::Callback<void(bool)> immediate_callback() {
-    return immediate_callback_;
+  base::Callback<void(bool)> processing_callback() {
+    return processing_callback_;
   }
 
   base::Callback<void(bool)> waiting_callback() { return waiting_callback_; }
-  bool immediate_schedule_callback_called() const {
-    return immediate_schedule_callback_called_;
+  bool processing_callback_called() const {
+    return processing_callback_called_;
   }
 
-  bool immediate_schedule_callback_result() const {
-    return immediate_schedule_callback_result_;
+  bool processing_callback_result() const {
+    return processing_callback_result_;
   }
 
   const base::HistogramTester& histograms() const { return histogram_tester_; }
@@ -275,10 +275,10 @@
   OfflinerStub* offliner_;
   base::WaitableEvent waiter_;
   ObserverStub observer_;
-  bool immediate_schedule_callback_called_;
-  bool immediate_schedule_callback_result_;
+  bool processing_callback_called_;
+  bool processing_callback_result_;
   DeviceConditions device_conditions_;
-  base::Callback<void(bool)> immediate_callback_;
+  base::Callback<void(bool)> processing_callback_;
   base::Callback<void(bool)> waiting_callback_;
   base::HistogramTester histogram_tester_;
 };
@@ -290,8 +290,8 @@
       offliner_(nullptr),
       waiter_(base::WaitableEvent::ResetPolicy::MANUAL,
               base::WaitableEvent::InitialState::NOT_SIGNALED),
-      immediate_schedule_callback_called_(false),
-      immediate_schedule_callback_result_(false),
+      processing_callback_called_(false),
+      processing_callback_result_(false),
       device_conditions_(!kPowerRequired,
                          kBatteryPercentageHigh,
                          net::NetworkChangeNotifier::CONNECTION_3G) {}
@@ -314,8 +314,8 @@
       std::move(scheduler_stub), network_quality_estimator_.get()));
   coordinator_->AddObserver(&observer_);
   SetNetworkConnected(true);
-  immediate_callback_ =
-      base::Bind(&RequestCoordinatorTest::ImmediateScheduleCallbackFunction,
+  processing_callback_ =
+      base::Bind(&RequestCoordinatorTest::ProcessingCallbackFunction,
                  base::Unretained(this));
   // Override the normal immediate callback with a wait releasing callback.
   waiting_callback_ = base::Bind(
@@ -362,7 +362,7 @@
 
   // Override the processing callback for test visiblity.
   base::Callback<void(bool)> callback =
-      base::Bind(&RequestCoordinatorTest::ImmediateScheduleCallbackFunction,
+      base::Bind(&RequestCoordinatorTest::ProcessingCallbackFunction,
                  base::Unretained(this));
   coordinator()->SetProcessingCallbackForTest(callback);
 
@@ -399,10 +399,10 @@
 
 TEST_F(RequestCoordinatorTest, StartScheduledProcessingWithNoRequests) {
   EXPECT_TRUE(coordinator()->StartScheduledProcessing(device_conditions(),
-                                                      immediate_callback()));
+                                                      processing_callback()));
   PumpLoop();
 
-  EXPECT_TRUE(immediate_schedule_callback_called());
+  EXPECT_TRUE(processing_callback_called());
 
   // Verify queue depth UMA for starting scheduled processing on empty queue.
   if (base::SysInfo::IsLowEndDevice()) {
@@ -428,22 +428,90 @@
 
   // Sending the request to the offliner should make it busy.
   EXPECT_TRUE(coordinator()->StartScheduledProcessing(device_conditions(),
-                                                      immediate_callback()));
+                                                      processing_callback()));
   PumpLoop();
 
   EXPECT_TRUE(is_busy());
   // Since the offliner is disabled, this callback should not be called.
-  EXPECT_FALSE(immediate_schedule_callback_called());
+  EXPECT_FALSE(processing_callback_called());
 
-  // Now trying to start processing on another request should return false.
+  // Now trying to start processing should return false since already busy.
   EXPECT_FALSE(coordinator()->StartScheduledProcessing(device_conditions(),
-                                                       immediate_callback()));
+                                                       processing_callback()));
+}
+
+TEST_F(RequestCoordinatorTest, StartImmediateProcessingWithNoRequests) {
+  // Ensure not low-end device so immediate start can happen.
+  SetIsLowEndDeviceForTest(false);
+
+  EXPECT_TRUE(coordinator()->StartImmediateProcessing(device_conditions(),
+                                                      processing_callback()));
+  PumpLoop();
+
+  EXPECT_TRUE(processing_callback_called());
+
+  histograms().ExpectBucketCount("OfflinePages.Background.ImmediateStartStatus",
+                                 0 /* STARTED */, 1);
+}
+
+TEST_F(RequestCoordinatorTest, StartImmediateProcessingOnSvelte) {
+  // Set as low-end device to verfiy immediate processing will not start.
+  SetIsLowEndDeviceForTest(true);
+
+  EXPECT_FALSE(coordinator()->StartImmediateProcessing(device_conditions(),
+                                                       processing_callback()));
+  histograms().ExpectBucketCount("OfflinePages.Background.ImmediateStartStatus",
+                                 5 /* NOT_STARTED_ON_SVELTE */, 1);
+}
+
+TEST_F(RequestCoordinatorTest, StartImmediateProcessingWhenDisconnected) {
+  // Ensure not low-end device so immediate start can happen.
+  SetIsLowEndDeviceForTest(false);
+
+  DeviceConditions disconnected_conditions(
+      !kPowerRequired, kBatteryPercentageHigh,
+      net::NetworkChangeNotifier::CONNECTION_NONE);
+  EXPECT_FALSE(coordinator()->StartImmediateProcessing(disconnected_conditions,
+                                                       processing_callback()));
+  histograms().ExpectBucketCount("OfflinePages.Background.ImmediateStartStatus",
+                                 3 /* NO_CONNECTION */, 1);
+}
+
+TEST_F(RequestCoordinatorTest, StartImmediateProcessingWithRequestInProgress) {
+  // Ensure not low-end device so immediate start can happen.
+  SetIsLowEndDeviceForTest(false);
+
+  // Start processing for this request.
+  EXPECT_NE(coordinator()->SavePageLater(
+                kUrl1, kClientId1, kUserRequested,
+                RequestCoordinator::RequestAvailability::ENABLED_FOR_OFFLINER),
+            0);
+
+  // Disable the automatic offliner callback.
+  EnableOfflinerCallback(false);
+
+  // Sending the request to the offliner should make it busy.
+  EXPECT_TRUE(coordinator()->StartImmediateProcessing(device_conditions(),
+                                                      processing_callback()));
+  PumpLoop();
+
+  EXPECT_TRUE(is_busy());
+  // Since the offliner is disabled, this callback should not be called.
+  EXPECT_FALSE(processing_callback_called());
+
+  // Now trying to start processing should return false since already busy.
+  EXPECT_FALSE(coordinator()->StartImmediateProcessing(device_conditions(),
+                                                       processing_callback()));
+
+  histograms().ExpectBucketCount("OfflinePages.Background.ImmediateStartStatus",
+                                 1 /* BUSY */, 1);
 }
 
 TEST_F(RequestCoordinatorTest, SavePageLater) {
   // The user-requested request which gets processed by SavePageLater
   // would invoke user request callback.
-  coordinator()->SetImmediateScheduleCallbackForTest(immediate_callback());
+  coordinator()->SetInternalStartProcessingCallbackForTest(
+      processing_callback());
 
   EXPECT_NE(coordinator()->SavePageLater(
                 kUrl1, kClientId1, kUserRequested,
@@ -456,7 +524,7 @@
 
   // Wait for callbacks to finish, both request queue and offliner.
   PumpLoop();
-  EXPECT_TRUE(immediate_schedule_callback_called());
+  EXPECT_TRUE(processing_callback_called());
 
   // Check the request queue is as expected.
   EXPECT_EQ(1UL, last_requests().size());
@@ -489,7 +557,8 @@
 TEST_F(RequestCoordinatorTest, SavePageLaterFailed) {
   // The user-requested request which gets processed by SavePageLater
   // would invoke user request callback.
-  coordinator()->SetImmediateScheduleCallbackForTest(immediate_callback());
+  coordinator()->SetInternalStartProcessingCallbackForTest(
+      processing_callback());
 
   EXPECT_TRUE(
       coordinator()->SavePageLater(
@@ -505,11 +574,11 @@
 
   // On low-end devices the callback will be called with false since the
   // processing started but failed due to svelte devices.
-  EXPECT_TRUE(immediate_schedule_callback_called());
+  EXPECT_TRUE(processing_callback_called());
   if (base::SysInfo::IsLowEndDevice()) {
-    EXPECT_FALSE(immediate_schedule_callback_result());
+    EXPECT_FALSE(processing_callback_result());
   } else {
-    EXPECT_TRUE(immediate_schedule_callback_result());
+    EXPECT_TRUE(processing_callback_result());
   }
 
   // Check the request queue is as expected.
@@ -540,7 +609,7 @@
   // for callbacks.
   SendOfflinerDoneCallback(request, Offliner::RequestStatus::SAVED);
   PumpLoop();
-  EXPECT_TRUE(immediate_schedule_callback_called());
+  EXPECT_TRUE(processing_callback_called());
 
   // Verify the request gets removed from the queue, and wait for callbacks.
   coordinator()->queue()->GetRequests(base::Bind(
@@ -575,7 +644,7 @@
   // for callbacks.
   SendOfflinerDoneCallback(request, Offliner::RequestStatus::SAVED);
   PumpLoop();
-  EXPECT_TRUE(immediate_schedule_callback_called());
+  EXPECT_TRUE(processing_callback_called());
 
   // Verify not busy with 2nd request (since no connection).
   EXPECT_FALSE(is_busy());
@@ -607,7 +676,7 @@
 
   // For retriable failure, processing should continue to 2nd request so
   // no scheduler callback yet.
-  EXPECT_FALSE(immediate_schedule_callback_called());
+  EXPECT_FALSE(processing_callback_called());
 
   // Busy processing 2nd request.
   EXPECT_TRUE(is_busy());
@@ -646,7 +715,7 @@
 
   // For no retry failure, processing should continue to 2nd request so
   // no scheduler callback yet.
-  EXPECT_FALSE(immediate_schedule_callback_called());
+  EXPECT_FALSE(processing_callback_called());
 
   // Busy processing 2nd request.
   EXPECT_TRUE(is_busy());
@@ -683,7 +752,7 @@
 
   // For no next failure, processing should not continue to 2nd request so
   // expect scheduler callback.
-  EXPECT_TRUE(immediate_schedule_callback_called());
+  EXPECT_TRUE(processing_callback_called());
 
   // Not busy for NO_NEXT failure.
   EXPECT_FALSE(is_busy());
@@ -707,7 +776,7 @@
   SendOfflinerDoneCallback(request,
                            Offliner::RequestStatus::FOREGROUND_CANCELED);
   PumpLoop();
-  EXPECT_TRUE(immediate_schedule_callback_called());
+  EXPECT_TRUE(processing_callback_called());
 
   // Verify the request is not removed from the queue, and wait for callbacks.
   coordinator()->queue()->GetRequests(base::Bind(
@@ -730,7 +799,7 @@
   // for callbacks.
   SendOfflinerDoneCallback(request, Offliner::RequestStatus::LOADING_CANCELED);
   PumpLoop();
-  EXPECT_TRUE(immediate_schedule_callback_called());
+  EXPECT_TRUE(processing_callback_called());
 
   // Verify the request is not removed from the queue, and wait for callbacks.
   coordinator()->queue()->GetRequests(base::Bind(
@@ -749,7 +818,7 @@
 // we should make a scheduler entry for a non-user requested item.
 TEST_F(RequestCoordinatorTest, RequestNotPickedDisabledItemsRemain) {
   coordinator()->StartScheduledProcessing(device_conditions(),
-                                          immediate_callback());
+                                          processing_callback());
   EXPECT_TRUE(is_starting());
 
   // Call RequestNotPicked, simulating a request on the disabled list.
@@ -770,7 +839,7 @@
 // we should make a scheduler entry for a non-user requested item.
 TEST_F(RequestCoordinatorTest, RequestNotPickedNonUserRequestedItemsRemain) {
   coordinator()->StartScheduledProcessing(device_conditions(),
-                                          immediate_callback());
+                                          processing_callback());
   EXPECT_TRUE(is_starting());
 
   // Call RequestNotPicked, and make sure we pick schedule a task for non user
@@ -779,7 +848,7 @@
   PumpLoop();
 
   EXPECT_FALSE(is_starting());
-  EXPECT_TRUE(immediate_schedule_callback_called());
+  EXPECT_TRUE(processing_callback_called());
 
   // The scheduler should have been called to schedule the non-user requested
   // task.
@@ -833,11 +902,11 @@
 
   DisableLoading();
   EXPECT_TRUE(coordinator()->StartScheduledProcessing(device_conditions(),
-                                                      immediate_callback()));
+                                                      processing_callback()));
 
   // Let the async callbacks in the request coordinator run.
   PumpLoop();
-  EXPECT_TRUE(immediate_schedule_callback_called());
+  EXPECT_TRUE(processing_callback_called());
 
   EXPECT_FALSE(is_starting());
   EXPECT_EQ(Offliner::LOADING_NOT_STARTED, last_offlining_status());
@@ -852,7 +921,7 @@
   PumpLoop();
 
   EXPECT_TRUE(coordinator()->StartScheduledProcessing(device_conditions(),
-                                                      immediate_callback()));
+                                                      processing_callback()));
   EXPECT_TRUE(is_starting());
 
   // Now, quick, before it can do much (we haven't called PumpLoop), cancel it.
@@ -860,7 +929,7 @@
 
   // Let the async callbacks in the request coordinator run.
   PumpLoop();
-  EXPECT_TRUE(immediate_schedule_callback_called());
+  EXPECT_TRUE(processing_callback_called());
 
   EXPECT_FALSE(is_starting());
 
@@ -884,7 +953,7 @@
   EnableOfflinerCallback(false);
 
   EXPECT_TRUE(coordinator()->StartScheduledProcessing(device_conditions(),
-                                                      immediate_callback()));
+                                                      processing_callback()));
   EXPECT_TRUE(is_starting());
 
   // Let all the async parts of the start processing pipeline run to completion.
@@ -896,7 +965,7 @@
   observer().Clear();
 
   // Since the offliner is disabled, this callback should not be called.
-  EXPECT_FALSE(immediate_schedule_callback_called());
+  EXPECT_FALSE(processing_callback_called());
 
   // Coordinator should now be busy.
   EXPECT_TRUE(is_busy());
@@ -935,12 +1004,12 @@
   EnableOfflinerCallback(false);
 
   EXPECT_TRUE(coordinator()->StartScheduledProcessing(device_conditions(),
-                                                      immediate_callback()));
+                                                      processing_callback()));
 
   // Let all the async parts of the start processing pipeline run to completion.
   PumpLoop();
   // Since the offliner is disabled, this callback should not be called.
-  EXPECT_FALSE(immediate_schedule_callback_called());
+  EXPECT_FALSE(processing_callback_called());
 
   // Remove the request while it is processing.
   std::vector<int64_t> request_ids{kRequestId1};
diff --git a/components/task_scheduler_util/browser/BUILD.gn b/components/task_scheduler_util/browser/BUILD.gn
new file mode 100644
index 0000000..502d832
--- /dev/null
+++ b/components/task_scheduler_util/browser/BUILD.gn
@@ -0,0 +1,17 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+static_library("browser") {
+  sources = [
+    "initialization.cc",
+    "initialization.h",
+  ]
+
+  deps = [
+    "//base",
+    "//base:base_static",
+    "//components/task_scheduler_util/common",
+    "//components/variations",
+  ]
+}
diff --git a/components/task_scheduler_util/browser/DEPS b/components/task_scheduler_util/browser/DEPS
new file mode 100644
index 0000000..80f6f90
--- /dev/null
+++ b/components/task_scheduler_util/browser/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+components/variations",
+]
diff --git a/components/task_scheduler_util/browser/initialization.cc b/components/task_scheduler_util/browser/initialization.cc
new file mode 100644
index 0000000..3f1624b9
--- /dev/null
+++ b/components/task_scheduler_util/browser/initialization.cc
@@ -0,0 +1,84 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/task_scheduler_util/browser/initialization.h"
+
+#include <map>
+#include <string>
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/task_scheduler/switches.h"
+#include "base/task_scheduler/task_traits.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "components/task_scheduler_util/common/variations_util.h"
+#include "components/variations/variations_associated_data.h"
+
+namespace task_scheduler_util {
+
+namespace {
+
+constexpr char kFieldTrialName[] = "BrowserScheduler";
+
+enum WorkerPoolType : size_t {
+  BACKGROUND = 0,
+  BACKGROUND_FILE_IO,
+  FOREGROUND,
+  FOREGROUND_FILE_IO,
+  WORKER_POOL_COUNT  // Always last.
+};
+
+}  // namespace
+
+std::vector<base::SchedulerWorkerPoolParams>
+GetBrowserWorkerPoolParamsFromVariations() {
+  using ThreadPriority = base::ThreadPriority;
+
+  std::map<std::string, std::string> variation_params;
+  if (!::variations::GetVariationParams(kFieldTrialName, &variation_params))
+    return std::vector<base::SchedulerWorkerPoolParams>();
+
+  std::vector<SchedulerImmutableWorkerPoolParams> constant_worker_pool_params;
+  DCHECK_EQ(BACKGROUND, constant_worker_pool_params.size());
+  constant_worker_pool_params.emplace_back("Background",
+                                           ThreadPriority::BACKGROUND);
+  DCHECK_EQ(BACKGROUND_FILE_IO, constant_worker_pool_params.size());
+  constant_worker_pool_params.emplace_back("BackgroundFileIO",
+                                           ThreadPriority::BACKGROUND);
+  DCHECK_EQ(FOREGROUND, constant_worker_pool_params.size());
+  constant_worker_pool_params.emplace_back("Foreground",
+                                           ThreadPriority::NORMAL);
+  DCHECK_EQ(FOREGROUND_FILE_IO, constant_worker_pool_params.size());
+  constant_worker_pool_params.emplace_back("ForegroundFileIO",
+                                           ThreadPriority::NORMAL);
+
+  return GetWorkerPoolParams(constant_worker_pool_params, variation_params);
+}
+
+size_t BrowserWorkerPoolIndexForTraits(const base::TaskTraits& traits) {
+  const bool is_background =
+      traits.priority() == base::TaskPriority::BACKGROUND;
+  if (traits.with_file_io())
+    return is_background ? BACKGROUND_FILE_IO : FOREGROUND_FILE_IO;
+  return is_background ? BACKGROUND : FOREGROUND;
+}
+
+void MaybePerformBrowserTaskSchedulerRedirection() {
+  // TODO(gab): Remove this when http://crbug.com/622400 concludes.
+  if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kDisableBrowserTaskScheduler) &&
+      variations::GetVariationParamValue(
+          kFieldTrialName, "RedirectSequencedWorkerPools") == "true") {
+    const base::TaskPriority max_task_priority =
+        variations::GetVariationParamValue(
+            kFieldTrialName, "CapSequencedWorkerPoolsAtUserVisible") == "true"
+            ? base::TaskPriority::USER_VISIBLE
+            : base::TaskPriority::HIGHEST;
+    base::SequencedWorkerPool::EnableWithRedirectionToTaskSchedulerForProcess(
+        max_task_priority);
+  }
+}
+
+}  // namespace task_scheduler_util
diff --git a/components/task_scheduler_util/browser/initialization.h b/components/task_scheduler_util/browser/initialization.h
new file mode 100644
index 0000000..d4584ee
--- /dev/null
+++ b/components/task_scheduler_util/browser/initialization.h
@@ -0,0 +1,35 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_TASK_SCHEDULER_UTIL_BROWSER_INITIALIZATION_H_
+#define COMPONENTS_TASK_SCHEDULER_UTIL_BROWSER_INITIALIZATION_H_
+
+#include <stddef.h>
+
+#include <vector>
+
+#include "base/task_scheduler/scheduler_worker_pool_params.h"
+
+namespace base {
+class TaskTraits;
+}
+
+namespace task_scheduler_util {
+
+// Gets a vector of SchedulerWorkerPoolParams to initialize TaskScheduler in the
+// browser based off variations. Returns an empty vector on failure.
+std::vector<base::SchedulerWorkerPoolParams>
+GetBrowserWorkerPoolParamsFromVariations();
+
+// Maps |traits| to the index of a browser worker pool vector provided by
+// GetBrowserWorkerPoolParamsFromVariations().
+size_t BrowserWorkerPoolIndexForTraits(const base::TaskTraits& traits);
+
+// Redirects zero-to-many PostTask APIs to the browser task scheduler based off
+// variations.
+void MaybePerformBrowserTaskSchedulerRedirection();
+
+}  // namespace task_scheduler_util
+
+#endif  // COMPONENTS_TASK_SCHEDULER_UTIL_BROWSER_INITIALIZATION_H_
diff --git a/components/task_scheduler_util/common/BUILD.gn b/components/task_scheduler_util/common/BUILD.gn
new file mode 100644
index 0000000..d0e0566
--- /dev/null
+++ b/components/task_scheduler_util/common/BUILD.gn
@@ -0,0 +1,26 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+static_library("common") {
+  sources = [
+    "variations_util.cc",
+    "variations_util.h",
+  ]
+
+  deps = [
+    "//base",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [
+    "variations_util_unittest.cc",
+  ]
+  deps = [
+    ":common",
+    "//base",
+    "//testing/gtest",
+  ]
+}
diff --git a/components/task_scheduler_util/common/variations_util.cc b/components/task_scheduler_util/common/variations_util.cc
new file mode 100644
index 0000000..ad823597
--- /dev/null
+++ b/components/task_scheduler_util/common/variations_util.cc
@@ -0,0 +1,108 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/task_scheduler_util/common/variations_util.h"
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/task_scheduler/initialization_util.h"
+#include "base/time/time.h"
+
+namespace task_scheduler_util {
+
+namespace {
+
+struct SchedulerCustomizableWorkerPoolParams {
+  base::SchedulerWorkerPoolParams::StandbyThreadPolicy standby_thread_policy;
+  int max_threads = 0;
+  base::TimeDelta detach_period;
+};
+
+// Converts |pool_descriptor| to a SchedulerWorkerPoolVariableParams. Returns a
+// default SchedulerWorkerPoolVariableParams on failure.
+//
+// |pool_descriptor| is a semi-colon separated value string with the following
+// items:
+// 0. Minimum Thread Count (int)
+// 1. Maximum Thread Count (int)
+// 2. Thread Count Multiplier (double)
+// 3. Thread Count Offset (int)
+// 4. Detach Time in Milliseconds (int)
+// 5. Standby Thread Policy (string)
+// Additional values may appear as necessary and will be ignored.
+SchedulerCustomizableWorkerPoolParams StringToVariableWorkerPoolParams(
+    const base::StringPiece pool_descriptor) {
+  using StandbyThreadPolicy =
+      base::SchedulerWorkerPoolParams::StandbyThreadPolicy;
+  const std::vector<base::StringPiece> tokens = SplitStringPiece(
+      pool_descriptor, ";", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+  // Normally, we wouldn't initialize the values below because we don't read
+  // from them before we write to them. However, some compilers (like MSVC)
+  // complain about uninitialized variables due to the as_string() call below.
+  int min = 0;
+  int max = 0;
+  double cores_multiplier = 0.0;
+  int offset = 0;
+  int detach_milliseconds = 0;
+  // Checking for a size greater than the expected amount allows us to be
+  // forward compatible if we add more variation values.
+  if (tokens.size() >= 5 && base::StringToInt(tokens[0], &min) &&
+      base::StringToInt(tokens[1], &max) &&
+      base::StringToDouble(tokens[2].as_string(), &cores_multiplier) &&
+      base::StringToInt(tokens[3], &offset) &&
+      base::StringToInt(tokens[4], &detach_milliseconds)) {
+    SchedulerCustomizableWorkerPoolParams params;
+    params.max_threads = base::RecommendedMaxNumberOfThreadsInPool(
+        min, max, cores_multiplier, offset);
+    params.detach_period =
+        base::TimeDelta::FromMilliseconds(detach_milliseconds);
+    params.standby_thread_policy = (tokens.size() >= 6 && tokens[5] == "lazy")
+                                       ? StandbyThreadPolicy::LAZY
+                                       : StandbyThreadPolicy::ONE;
+    return params;
+  }
+  DLOG(ERROR) << "Invalid Worker Pool Descriptor: " << pool_descriptor;
+  return SchedulerCustomizableWorkerPoolParams();
+}
+
+}  // namespace
+
+SchedulerImmutableWorkerPoolParams::SchedulerImmutableWorkerPoolParams(
+    const char* name,
+    base::ThreadPriority priority_hint)
+    : name_(name), priority_hint_(priority_hint) {}
+
+std::vector<base::SchedulerWorkerPoolParams> GetWorkerPoolParams(
+    const std::vector<SchedulerImmutableWorkerPoolParams>&
+        constant_worker_pool_params_vector,
+    const std::map<std::string, std::string>& variation_params) {
+  std::vector<base::SchedulerWorkerPoolParams> worker_pool_params_vector;
+  for (const auto& constant_worker_pool_params :
+       constant_worker_pool_params_vector) {
+    const char* const worker_pool_name = constant_worker_pool_params.name();
+    auto it = variation_params.find(worker_pool_name);
+    if (it == variation_params.end()) {
+      DLOG(ERROR) << "Missing Worker Pool Configuration: " << worker_pool_name;
+      return std::vector<base::SchedulerWorkerPoolParams>();
+    }
+    const auto variable_worker_pool_params =
+        StringToVariableWorkerPoolParams(it->second);
+    if (variable_worker_pool_params.max_threads <= 0 ||
+        variable_worker_pool_params.detach_period <= base::TimeDelta()) {
+      DLOG(ERROR) << "Invalid Worker Pool Configuration: " << worker_pool_name
+                  << " [" << it->second << "]";
+      return std::vector<base::SchedulerWorkerPoolParams>();
+    }
+    worker_pool_params_vector.emplace_back(
+        worker_pool_name, constant_worker_pool_params.priority_hint(),
+        variable_worker_pool_params.standby_thread_policy,
+        variable_worker_pool_params.max_threads,
+        variable_worker_pool_params.detach_period);
+  }
+  return worker_pool_params_vector;
+}
+
+}  // namespace task_scheduler_util
diff --git a/components/task_scheduler_util/common/variations_util.h b/components/task_scheduler_util/common/variations_util.h
new file mode 100644
index 0000000..aa37d25b
--- /dev/null
+++ b/components/task_scheduler_util/common/variations_util.h
@@ -0,0 +1,41 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_TASK_SCHEDULER_UTIL_COMMON_VARIATIONS_UTIL_H_
+#define COMPONENTS_TASK_SCHEDULER_UTIL_COMMON_VARIATIONS_UTIL_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/task_scheduler/scheduler_worker_pool_params.h"
+#include "base/threading/platform_thread.h"
+
+namespace task_scheduler_util {
+
+class SchedulerImmutableWorkerPoolParams {
+ public:
+  SchedulerImmutableWorkerPoolParams(const char* name,
+                                     base::ThreadPriority priority_hint);
+
+  const char* name() const { return name_; }
+  base::ThreadPriority priority_hint() const { return priority_hint_; }
+
+ private:
+  const char* name_;
+  base::ThreadPriority priority_hint_;
+};
+
+// Returns a SchedulerWorkerPoolParams vector to initialize pools specified in
+// |constant_worker_pool_params_vector|. SchedulerWorkerPoolParams members
+// without a counterpart in SchedulerImmutableWorkerPoolParams are initialized
+// based of |variation_params|. Returns an empty vector on failure.
+std::vector<base::SchedulerWorkerPoolParams> GetWorkerPoolParams(
+    const std::vector<SchedulerImmutableWorkerPoolParams>&
+        constant_worker_pool_params_vector,
+    const std::map<std::string, std::string>& variation_params);
+
+}  // namespace task_scheduler_util
+
+#endif  // COMPONENTS_TASK_SCHEDULER_UTIL_COMMON_VARIATIONS_UTIL_H_
diff --git a/components/task_scheduler_util/common/variations_util_unittest.cc b/components/task_scheduler_util/common/variations_util_unittest.cc
new file mode 100644
index 0000000..386d0b0
--- /dev/null
+++ b/components/task_scheduler_util/common/variations_util_unittest.cc
@@ -0,0 +1,148 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/task_scheduler_util/common/variations_util.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/task_scheduler/scheduler_worker_pool_params.h"
+#include "base/threading/platform_thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace task_scheduler_util {
+
+namespace {
+
+using StandbyThreadPolicy =
+    base::SchedulerWorkerPoolParams::StandbyThreadPolicy;
+using ThreadPriority = base::ThreadPriority;
+
+std::vector<SchedulerImmutableWorkerPoolParams> GetImmutableWorkerPoolParams() {
+  std::vector<SchedulerImmutableWorkerPoolParams> constant_worker_pool_params;
+  constant_worker_pool_params.emplace_back("Background",
+                                           ThreadPriority::BACKGROUND);
+  constant_worker_pool_params.emplace_back("BackgroundFileIO",
+                                           ThreadPriority::BACKGROUND);
+  constant_worker_pool_params.emplace_back("Foreground",
+                                           ThreadPriority::NORMAL);
+  constant_worker_pool_params.emplace_back("ForegroundFileIO",
+                                           ThreadPriority::NORMAL);
+  return constant_worker_pool_params;
+}
+
+}  // namespace
+
+TEST(TaskSchedulerUtilVariationsUtilTest, OrderingParams5) {
+  std::map<std::string, std::string> variation_params;
+  variation_params["Background"] = "1;1;1;0;42";
+  variation_params["BackgroundFileIO"] = "2;2;1;0;52";
+  variation_params["Foreground"] = "4;4;1;0;62";
+  variation_params["ForegroundFileIO"] = "8;8;1;0;72";
+
+  auto params_vector =
+      GetWorkerPoolParams(GetImmutableWorkerPoolParams(), variation_params);
+  ASSERT_EQ(4U, params_vector.size());
+
+  EXPECT_EQ("Background", params_vector[0].name());
+  EXPECT_EQ(ThreadPriority::BACKGROUND, params_vector[0].priority_hint());
+  EXPECT_EQ(StandbyThreadPolicy::ONE, params_vector[0].standby_thread_policy());
+  EXPECT_EQ(1U, params_vector[0].max_threads());
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(42),
+            params_vector[0].suggested_reclaim_time());
+
+  EXPECT_EQ("BackgroundFileIO", params_vector[1].name());
+  EXPECT_EQ(ThreadPriority::BACKGROUND, params_vector[1].priority_hint());
+  EXPECT_EQ(StandbyThreadPolicy::ONE, params_vector[1].standby_thread_policy());
+  EXPECT_EQ(2U, params_vector[1].max_threads());
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(52),
+            params_vector[1].suggested_reclaim_time());
+
+  EXPECT_EQ("Foreground", params_vector[2].name());
+  EXPECT_EQ(ThreadPriority::NORMAL, params_vector[2].priority_hint());
+  EXPECT_EQ(StandbyThreadPolicy::ONE, params_vector[2].standby_thread_policy());
+  EXPECT_EQ(4U, params_vector[2].max_threads());
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(62),
+            params_vector[2].suggested_reclaim_time());
+
+  EXPECT_EQ("ForegroundFileIO", params_vector[3].name());
+  EXPECT_EQ(ThreadPriority::NORMAL, params_vector[3].priority_hint());
+  EXPECT_EQ(StandbyThreadPolicy::ONE, params_vector[3].standby_thread_policy());
+  EXPECT_EQ(8U, params_vector[3].max_threads());
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(72),
+            params_vector[3].suggested_reclaim_time());
+}
+
+TEST(TaskSchedulerUtilVariationsUtilTest, OrderingParams6) {
+  std::map<std::string, std::string> variation_params;
+  variation_params["Background"] = "1;1;1;0;42;lazy";
+  variation_params["BackgroundFileIO"] = "2;2;1;0;52;one";
+  variation_params["Foreground"] = "4;4;1;0;62;lazy";
+  variation_params["ForegroundFileIO"] = "8;8;1;0;72;one";
+
+  auto params_vector =
+      GetWorkerPoolParams(GetImmutableWorkerPoolParams(), variation_params);
+  ASSERT_EQ(4U, params_vector.size());
+
+  EXPECT_EQ("Background", params_vector[0].name());
+  EXPECT_EQ(ThreadPriority::BACKGROUND, params_vector[0].priority_hint());
+  EXPECT_EQ(StandbyThreadPolicy::LAZY,
+            params_vector[0].standby_thread_policy());
+  EXPECT_EQ(1U, params_vector[0].max_threads());
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(42),
+            params_vector[0].suggested_reclaim_time());
+
+  EXPECT_EQ("BackgroundFileIO", params_vector[1].name());
+  EXPECT_EQ(ThreadPriority::BACKGROUND, params_vector[1].priority_hint());
+  EXPECT_EQ(StandbyThreadPolicy::ONE, params_vector[1].standby_thread_policy());
+  EXPECT_EQ(2U, params_vector[1].max_threads());
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(52),
+            params_vector[1].suggested_reclaim_time());
+
+  EXPECT_EQ("Foreground", params_vector[2].name());
+  EXPECT_EQ(ThreadPriority::NORMAL, params_vector[2].priority_hint());
+  EXPECT_EQ(StandbyThreadPolicy::LAZY,
+            params_vector[2].standby_thread_policy());
+  EXPECT_EQ(4U, params_vector[2].max_threads());
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(62),
+            params_vector[2].suggested_reclaim_time());
+
+  EXPECT_EQ("ForegroundFileIO", params_vector[3].name());
+  EXPECT_EQ(ThreadPriority::NORMAL, params_vector[3].priority_hint());
+  EXPECT_EQ(StandbyThreadPolicy::ONE, params_vector[3].standby_thread_policy());
+  EXPECT_EQ(8U, params_vector[3].max_threads());
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(72),
+            params_vector[3].suggested_reclaim_time());
+}
+
+TEST(TaskSchedulerUtilVariationsUtilTest, NoData) {
+  EXPECT_TRUE(GetWorkerPoolParams(GetImmutableWorkerPoolParams(),
+                                  std::map<std::string, std::string>())
+                  .empty());
+}
+
+TEST(TaskSchedulerUtilVariationsUtilTest, IncompleteParameters) {
+  std::map<std::string, std::string> variation_params;
+  variation_params["Background"] = "1;1;1;0";
+  variation_params["BackgroundFileIO"] = "2;2;1;0";
+  variation_params["Foreground"] = "4;4;1;0";
+  variation_params["ForegroundFileIO"] = "8;8;1;0";
+  EXPECT_TRUE(
+      GetWorkerPoolParams(GetImmutableWorkerPoolParams(), variation_params)
+          .empty());
+}
+
+TEST(TaskSchedulerUtilVariationsUtilTest, InvalidParameters) {
+  std::map<std::string, std::string> variation_params;
+  variation_params["Background"] = "a;b;c;d;e";
+  variation_params["BackgroundFileIO"] = "a;b;c;d;e";
+  variation_params["Foreground"] = "a;b;c;d;e";
+  variation_params["ForegroundFileIO"] = "a;b;c;d;e";
+  EXPECT_TRUE(
+      GetWorkerPoolParams(GetImmutableWorkerPoolParams(), variation_params)
+          .empty());
+}
+
+}  // namespace task_scheduler_util
diff --git a/components/task_scheduler_util/initialization/BUILD.gn b/components/task_scheduler_util/initialization/BUILD.gn
index 213326b..26c6ba24 100644
--- a/components/task_scheduler_util/initialization/BUILD.gn
+++ b/components/task_scheduler_util/initialization/BUILD.gn
@@ -9,18 +9,6 @@
   ]
 
   deps = [
-    "//base",
-  ]
-}
-
-source_set("unit_tests") {
-  testonly = true
-  sources = [
-    "browser_util_unittest.cc",
-  ]
-  deps = [
-    ":initialization",
-    "//base",
-    "//testing/gtest",
+    "//components/task_scheduler_util/browser",
   ]
 }
diff --git a/components/task_scheduler_util/initialization/browser_util.cc b/components/task_scheduler_util/initialization/browser_util.cc
index ff9bbf9..5dccd998 100644
--- a/components/task_scheduler_util/initialization/browser_util.cc
+++ b/components/task_scheduler_util/initialization/browser_util.cc
@@ -2,125 +2,22 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// TODO(fdoray): Remove this file once TaskScheduler initialization in the
+// browser process uses the components/task_scheduler_util/browser/ API on all
+// platforms.
+
 #include "components/task_scheduler_util/initialization/browser_util.h"
 
-#include <map>
-#include <string>
-
-#include "base/task_scheduler/initialization_util.h"
-#include "base/task_scheduler/switches.h"
-#include "base/task_scheduler/task_traits.h"
-#include "base/threading/sequenced_worker_pool.h"
+#include "components/task_scheduler_util/browser/initialization.h"
 
 namespace task_scheduler_util {
 namespace initialization {
 
-namespace {
-
-using StandbyThreadPolicy =
-    base::SchedulerWorkerPoolParams::StandbyThreadPolicy;
-using ThreadPriority = base::ThreadPriority;
-
-struct SchedulerWorkerPoolCustomizableConfiguration {
-  SchedulerWorkerPoolCustomizableConfiguration(
-      const char* name_in,
-      ThreadPriority priority_hint_in,
-      const SingleWorkerPoolConfiguration& single_worker_pool_config_in)
-      : name(name_in),
-        priority_hint(priority_hint_in),
-        single_worker_pool_config(single_worker_pool_config_in) {}
-
-  const char* name;
-  ThreadPriority priority_hint;
-  const SingleWorkerPoolConfiguration& single_worker_pool_config;
-};
-
-}  // namespace
-
-std::vector<base::SchedulerWorkerPoolParams>
-BrowserWorkerPoolConfigurationToSchedulerWorkerPoolParams(
-    const BrowserWorkerPoolsConfiguration& config) {
-  const SchedulerWorkerPoolCustomizableConfiguration worker_pool_config[] = {
-      SchedulerWorkerPoolCustomizableConfiguration("Background",
-                                                   ThreadPriority::BACKGROUND,
-                                                   config.background),
-      SchedulerWorkerPoolCustomizableConfiguration("BackgroundFileIO",
-                                                   ThreadPriority::BACKGROUND,
-                                                   config.background_file_io),
-      SchedulerWorkerPoolCustomizableConfiguration("Foreground",
-                                                   ThreadPriority::NORMAL,
-                                                   config.foreground),
-      SchedulerWorkerPoolCustomizableConfiguration("ForegroundFileIO",
-                                                   ThreadPriority::NORMAL,
-                                                   config.foreground_file_io),
-
-  };
-  static_assert(arraysize(worker_pool_config) == WORKER_POOL_COUNT,
-                "Mismatched Worker Pool Types and Predefined Parameters");
-  constexpr size_t kNumWorkerPoolsDefined = sizeof(config) /
-                                            sizeof(config.background);
-  static_assert(arraysize(worker_pool_config) == kNumWorkerPoolsDefined,
-                "Mismatch in predefined parameters and worker pools.");
-  std::vector<base::SchedulerWorkerPoolParams> params_vector;
-  for (const auto& config : worker_pool_config) {
-    params_vector.emplace_back(
-        config.name, config.priority_hint,
-        config.single_worker_pool_config.standby_thread_policy,
-        config.single_worker_pool_config.threads,
-        config.single_worker_pool_config.detach_period);
-  }
-  DCHECK_EQ(WORKER_POOL_COUNT, params_vector.size());
-  return params_vector;
-}
-
 // Returns the worker pool index for |traits| defaulting to FOREGROUND or
 // FOREGROUND_FILE_IO on any priorities other than background.
 size_t BrowserWorkerPoolIndexForTraits(const base::TaskTraits& traits) {
-  const bool is_background =
-      traits.priority() == base::TaskPriority::BACKGROUND;
-  if (traits.with_file_io())
-    return is_background ? BACKGROUND_FILE_IO : FOREGROUND_FILE_IO;
-
-  return is_background ? BACKGROUND : FOREGROUND;
+  return ::task_scheduler_util::BrowserWorkerPoolIndexForTraits(traits);
 }
 
-#if defined(OS_IOS)
-std::vector<base::SchedulerWorkerPoolParams>
-GetDefaultBrowserSchedulerWorkerPoolParams() {
-  constexpr size_t kNumWorkerPoolsDefined =
-      sizeof(BrowserWorkerPoolsConfiguration) /
-      sizeof(SingleWorkerPoolConfiguration);
-  static_assert(kNumWorkerPoolsDefined == 4,
-                "Expected 4 worker pools in BrowserWorkerPoolsConfiguration");
-  BrowserWorkerPoolsConfiguration config;
-  constexpr size_t kSizeAssignedFields =
-      sizeof(config.background.threads) +
-      sizeof(config.background.detach_period) +
-      sizeof(config.background.standby_thread_policy);
-  static_assert(kSizeAssignedFields == sizeof(config.background),
-                "Not all fields were assigned");
-  config.background.standby_thread_policy = StandbyThreadPolicy::ONE;
-  config.background.threads =
-      base::RecommendedMaxNumberOfThreadsInPool(2, 8, 0.1, 0);
-  config.background.detach_period = base::TimeDelta::FromSeconds(30);
-
-  config.background_file_io.standby_thread_policy = StandbyThreadPolicy::ONE;
-  config.background_file_io.threads =
-      base::RecommendedMaxNumberOfThreadsInPool(2, 8, 0.1, 0);
-  config.background_file_io.detach_period = base::TimeDelta::FromSeconds(30);
-
-  config.foreground.standby_thread_policy = StandbyThreadPolicy::ONE;
-  config.foreground.threads =
-      base::RecommendedMaxNumberOfThreadsInPool(3, 8, 0.3, 0);
-  config.foreground.detach_period = base::TimeDelta::FromSeconds(30);
-
-  config.foreground_file_io.standby_thread_policy = StandbyThreadPolicy::ONE;
-  config.foreground_file_io.threads =
-      base::RecommendedMaxNumberOfThreadsInPool(3, 8, 0.3, 0);
-  config.foreground_file_io.detach_period = base::TimeDelta::FromSeconds(30);
-  return BrowserWorkerPoolConfigurationToSchedulerWorkerPoolParams(config);
-}
-#endif  // defined(OS_IOS)
-
 }  // namespace initialization
 }  // namespace task_scheduler_util
diff --git a/components/task_scheduler_util/initialization/browser_util.h b/components/task_scheduler_util/initialization/browser_util.h
index 8d95d889..873ca7bb 100644
--- a/components/task_scheduler_util/initialization/browser_util.h
+++ b/components/task_scheduler_util/initialization/browser_util.h
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// TODO(fdoray): Remove this file once TaskScheduler initialization in the
+// browser process uses the components/task_scheduler_util/browser/ API on all
+// platforms.
+
 #ifndef COMPONENTS_TASK_SCHEDULER_UTIL_INITIALIZATION_BROWSER_UTIL_H_
 #define COMPONENTS_TASK_SCHEDULER_UTIL_INITIALIZATION_BROWSER_UTIL_H_
 
-#include <vector>
-
-#include "base/task_scheduler/scheduler_worker_pool_params.h"
-#include "base/time/time.h"
-#include "build/build_config.h"
+#include <stddef.h>
 
 namespace base {
 class TaskTraits;
@@ -18,45 +18,11 @@
 namespace task_scheduler_util {
 namespace initialization {
 
-enum WorkerPoolType : size_t {
-  BACKGROUND = 0,
-  BACKGROUND_FILE_IO,
-  FOREGROUND,
-  FOREGROUND_FILE_IO,
-  WORKER_POOL_COUNT  // Always last.
-};
-
-struct SingleWorkerPoolConfiguration {
-  base::SchedulerWorkerPoolParams::StandbyThreadPolicy standby_thread_policy;
-  int threads = 0;
-  base::TimeDelta detach_period;
-};
-
-struct BrowserWorkerPoolsConfiguration {
-  SingleWorkerPoolConfiguration background;
-  SingleWorkerPoolConfiguration background_file_io;
-  SingleWorkerPoolConfiguration foreground;
-  SingleWorkerPoolConfiguration foreground_file_io;
-};
-
-// Converts a BrowserWorkerPoolsConfiguration to a vector of
-// base::SchedulerWorkerPoolParams for consumption by task scheduler
-// initialization.
-std::vector<base::SchedulerWorkerPoolParams>
-BrowserWorkerPoolConfigurationToSchedulerWorkerPoolParams(
-    const BrowserWorkerPoolsConfiguration& config);
-
 // Maps |traits| to the index of a browser worker pool vector provided by
 // BrowserWorkerPoolConfigurationToSchedulerWorkerPoolParams() or
 // GetDefaultBrowserSchedulerWorkerPoolParams().
 size_t BrowserWorkerPoolIndexForTraits(const base::TaskTraits& traits);
 
-#if defined(OS_IOS)
-// Returns the default browser scheduler worker pool params.
-std::vector<base::SchedulerWorkerPoolParams>
-GetDefaultBrowserSchedulerWorkerPoolParams();
-#endif  // defined(OS_IOS)
-
 }  // namespace initialization
 }  // namespace task_scheduler_util
 
diff --git a/components/task_scheduler_util/initialization/browser_util_unittest.cc b/components/task_scheduler_util/initialization/browser_util_unittest.cc
deleted file mode 100644
index 7dad6ffe1..0000000
--- a/components/task_scheduler_util/initialization/browser_util_unittest.cc
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/task_scheduler_util/initialization/browser_util.h"
-
-#include <vector>
-
-#include "base/task_scheduler/scheduler_worker_pool_params.h"
-#include "base/time/time.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace task_scheduler_util {
-namespace initialization {
-
-TEST(TaskSchedulerUtilBrowserUtilTest, CheckOrdering) {
-  using StandbyThreadPolicy =
-      base::SchedulerWorkerPoolParams::StandbyThreadPolicy;
-  BrowserWorkerPoolsConfiguration config;
-  config.background.standby_thread_policy = StandbyThreadPolicy::LAZY;
-  config.background.threads = 1;
-  config.background.detach_period = base::TimeDelta::FromSeconds(2);
-
-  config.background_file_io.standby_thread_policy = StandbyThreadPolicy::ONE;
-  config.background_file_io.threads = 3;
-  config.background_file_io.detach_period = base::TimeDelta::FromSeconds(4);
-
-  config.foreground.standby_thread_policy = StandbyThreadPolicy::LAZY;
-  config.foreground.threads = 5;
-  config.foreground.detach_period = base::TimeDelta::FromSeconds(6);
-
-  config.foreground_file_io.standby_thread_policy = StandbyThreadPolicy::ONE;
-  config.foreground_file_io.threads = 7;
-  config.foreground_file_io.detach_period = base::TimeDelta::FromSeconds(8);
-
-  const std::vector<base::SchedulerWorkerPoolParams> params_vector =
-      BrowserWorkerPoolConfigurationToSchedulerWorkerPoolParams(config);
-
-  ASSERT_EQ(4U, params_vector.size());
-
-  EXPECT_EQ(StandbyThreadPolicy::LAZY,
-            params_vector[0].standby_thread_policy());
-  EXPECT_EQ(1U, params_vector[0].max_threads());
-  EXPECT_EQ(base::TimeDelta::FromSeconds(2),
-            params_vector[0].suggested_reclaim_time());
-
-  EXPECT_EQ(StandbyThreadPolicy::ONE, params_vector[1].standby_thread_policy());
-  EXPECT_EQ(3U, params_vector[1].max_threads());
-  EXPECT_EQ(base::TimeDelta::FromSeconds(4),
-            params_vector[1].suggested_reclaim_time());
-
-  EXPECT_EQ(StandbyThreadPolicy::LAZY,
-            params_vector[2].standby_thread_policy());
-  EXPECT_EQ(5U, params_vector[2].max_threads());
-  EXPECT_EQ(base::TimeDelta::FromSeconds(6),
-            params_vector[2].suggested_reclaim_time());
-
-  EXPECT_EQ(StandbyThreadPolicy::ONE, params_vector[3].standby_thread_policy());
-  EXPECT_EQ(7U, params_vector[3].max_threads());
-  EXPECT_EQ(base::TimeDelta::FromSeconds(8),
-            params_vector[3].suggested_reclaim_time());
-}
-
-}  // namespace initialization
-}  // namespace task_scheduler_util
diff --git a/components/task_scheduler_util/variations/BUILD.gn b/components/task_scheduler_util/variations/BUILD.gn
index 64aab01..5d66710 100644
--- a/components/task_scheduler_util/variations/BUILD.gn
+++ b/components/task_scheduler_util/variations/BUILD.gn
@@ -10,9 +10,7 @@
 
   deps = [
     "//base",
-    "//base:base_static",
-    "//components/task_scheduler_util/initialization",
-    "//components/variations",
+    "//components/task_scheduler_util/browser",
   ]
 }
 
diff --git a/components/task_scheduler_util/variations/browser_variations_util.cc b/components/task_scheduler_util/variations/browser_variations_util.cc
index 29f2e32..3c02359 100644
--- a/components/task_scheduler_util/variations/browser_variations_util.cc
+++ b/components/task_scheduler_util/variations/browser_variations_util.cc
@@ -2,157 +2,24 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// TODO(fdoray): Remove this file once TaskScheduler initialization in the
+// browser process uses the components/task_scheduler_util/browser/ API on all
+// platforms.
+
 #include "components/task_scheduler_util/variations/browser_variations_util.h"
 
-#include <map>
-#include <string>
-#include <vector>
-
-#include "base/logging.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/strings/string_piece.h"
-#include "base/strings/string_split.h"
-#include "base/task_scheduler/initialization_util.h"
-#include "base/task_scheduler/scheduler_worker_pool_params.h"
-#include "base/task_scheduler/switches.h"
-#include "base/task_scheduler/task_traits.h"
-#include "base/threading/sequenced_worker_pool.h"
-#include "base/time/time.h"
-#include "build/build_config.h"
-#include "components/task_scheduler_util/initialization/browser_util.h"
-#include "components/variations/variations_associated_data.h"
+#include "components/task_scheduler_util/browser/initialization.h"
 
 namespace task_scheduler_util {
 namespace variations {
 
-namespace {
-
-constexpr char kFieldTrialName[] = "BrowserScheduler";
-
-// Converts |pool_descriptor| to a SingleWorkerPoolConfiguration. Returns a
-// default SingleWorkerPoolConfiguration on failure.
-//
-// |pool_descriptor| is a semi-colon separated value string with the following
-// items:
-// 0. Minimum Thread Count (int)
-// 1. Maximum Thread Count (int)
-// 2. Thread Count Multiplier (double)
-// 3. Thread Count Offset (int)
-// 4. Detach Time in Milliseconds (milliseconds)
-// 5. Standby Thread Policy (string)
-// Additional values may appear as necessary and will be ignored.
-initialization::SingleWorkerPoolConfiguration
-StringToSingleWorkerPoolConfiguration(const base::StringPiece pool_descriptor) {
-  using StandbyThreadPolicy =
-      base::SchedulerWorkerPoolParams::StandbyThreadPolicy;
-  const std::vector<base::StringPiece> tokens = SplitStringPiece(
-      pool_descriptor, ";", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
-  // Normally, we wouldn't initialize the values below because we don't read
-  // from them before we write to them. However, some compilers (like MSVC)
-  // complain about uninitialized varaibles due to the as_string() call below.
-  int min = 0;
-  int max = 0;
-  double cores_multiplier = 0.0;
-  int offset = 0;
-  int detach_milliseconds = 0;
-  // Checking for a size greater than the expected amount allows us to be
-  // forward compatible if we add more variation values.
-  if (tokens.size() >= 5 && base::StringToInt(tokens[0], &min) &&
-      base::StringToInt(tokens[1], &max) &&
-      base::StringToDouble(tokens[2].as_string(), &cores_multiplier) &&
-      base::StringToInt(tokens[3], &offset) &&
-      base::StringToInt(tokens[4], &detach_milliseconds)) {
-    initialization::SingleWorkerPoolConfiguration config;
-    config.threads = base::RecommendedMaxNumberOfThreadsInPool(
-        min, max, cores_multiplier, offset);
-    config.detach_period =
-        base::TimeDelta::FromMilliseconds(detach_milliseconds);
-    config.standby_thread_policy = (tokens.size() >= 6 && tokens[5] == "lazy")
-                                       ? StandbyThreadPolicy::LAZY
-                                       : StandbyThreadPolicy::ONE;
-    return config;
-  }
-  DLOG(ERROR) << "Invalid Worker Pool Descriptor: " << pool_descriptor;
-  return initialization::SingleWorkerPoolConfiguration();
-}
-
-// Converts a browser-based |variation_params| to
-// std::vector<base::SchedulerWorkerPoolParams>. Returns an empty vector on
-// failure.
-std::vector<base::SchedulerWorkerPoolParams>
-VariationsParamsToSchedulerWorkerPoolParamsVector(
-    const std::map<std::string, std::string>& variation_params) {
-  static const char* const kWorkerPoolNames[] = {
-      "Background", "BackgroundFileIO", "Foreground", "ForegroundFileIO"};
-  static_assert(
-      arraysize(kWorkerPoolNames) == initialization::WORKER_POOL_COUNT,
-      "Mismatched Worker Pool Types and Worker Pool Names");
-  initialization::BrowserWorkerPoolsConfiguration config;
-  initialization::SingleWorkerPoolConfiguration* const all_pools[]{
-      &config.background, &config.background_file_io, &config.foreground,
-      &config.foreground_file_io,
-  };
-  static_assert(arraysize(kWorkerPoolNames) == arraysize(all_pools),
-                "Mismatched Worker Pool Names and All Pools Array");
-  for (size_t i = 0; i < arraysize(kWorkerPoolNames); ++i) {
-    const auto* const worker_pool_name = kWorkerPoolNames[i];
-    const auto pair = variation_params.find(worker_pool_name);
-    if (pair == variation_params.end()) {
-      DLOG(ERROR) << "Missing Worker Pool Configuration: " << worker_pool_name;
-      return std::vector<base::SchedulerWorkerPoolParams>();
-    }
-
-    auto* const pool_config = all_pools[i];
-    *pool_config = StringToSingleWorkerPoolConfiguration(pair->second);
-    if (pool_config->threads <= 0 ||
-        pool_config->detach_period <= base::TimeDelta()) {
-      DLOG(ERROR) << "Invalid Worker Pool Configuration: " << worker_pool_name
-                  << " [" << pair->second << "]";
-      return std::vector<base::SchedulerWorkerPoolParams>();
-    }
-  }
-  return BrowserWorkerPoolConfigurationToSchedulerWorkerPoolParams(config);
-}
-
-}  // namespace
-
 std::vector<base::SchedulerWorkerPoolParams>
 GetBrowserSchedulerWorkerPoolParamsFromVariations() {
-  std::map<std::string, std::string> variation_params;
-  if (!::variations::GetVariationParams(kFieldTrialName, &variation_params))
-    return std::vector<base::SchedulerWorkerPoolParams>();
-
-  return VariationsParamsToSchedulerWorkerPoolParamsVector(variation_params);
+  return ::task_scheduler_util::GetBrowserWorkerPoolParamsFromVariations();
 }
 
 void MaybePerformBrowserTaskSchedulerRedirection() {
-  std::map<std::string, std::string> variation_params;
-  ::variations::GetVariationParams(kFieldTrialName, &variation_params);
-
-  // TODO(gab): Remove this when http://crbug.com/622400 concludes.
-  const auto sequenced_worker_pool_param =
-      variation_params.find("RedirectSequencedWorkerPools");
-  if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
-          switches::kDisableBrowserTaskScheduler) &&
-      sequenced_worker_pool_param != variation_params.end() &&
-      sequenced_worker_pool_param->second == "true") {
-    // Check a variation that allows capping all redirections at USER_VISIBLE
-    // (no USER_BLOCKING) to observe the side-effects of multiple priority
-    // levels in the foreground IO pool.
-    const auto sequenced_worker_pool_cap_priority_param =
-        variation_params.find("CapSequencedWorkerPoolsAtUserVisible");
-
-    const base::TaskPriority max_task_priority =
-        sequenced_worker_pool_cap_priority_param != variation_params.end() &&
-        sequenced_worker_pool_cap_priority_param->second == "true"
-            ? base::TaskPriority::USER_VISIBLE
-            : base::TaskPriority::HIGHEST;
-
-    base::SequencedWorkerPool::EnableWithRedirectionToTaskSchedulerForProcess(
-        max_task_priority);
-  } else {
-    base::SequencedWorkerPool::EnableForProcess();
-  }
+  ::task_scheduler_util::MaybePerformBrowserTaskSchedulerRedirection();
 }
 
 }  // variations
diff --git a/components/task_scheduler_util/variations/browser_variations_util.h b/components/task_scheduler_util/variations/browser_variations_util.h
index 114d5d3..b0857c9d 100644
--- a/components/task_scheduler_util/variations/browser_variations_util.h
+++ b/components/task_scheduler_util/variations/browser_variations_util.h
@@ -2,6 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// TODO(fdoray): Remove this file once TaskScheduler initialization in the
+// browser process uses the components/task_scheduler_util/browser/ API on all
+// platforms.
+
 #ifndef COMPONENTS_TASK_SCHEDULER_UTIL_VARIATIONS_BROWSER_VARIATIONS_UTIL_H_
 #define COMPONENTS_TASK_SCHEDULER_UTIL_VARIATIONS_BROWSER_VARIATIONS_UTIL_H_
 
diff --git a/components/task_scheduler_util/variations/browser_variations_util_unittest.cc b/components/task_scheduler_util/variations/browser_variations_util_unittest.cc
index 9350c6d0..e2924cf3 100644
--- a/components/task_scheduler_util/variations/browser_variations_util_unittest.cc
+++ b/components/task_scheduler_util/variations/browser_variations_util_unittest.cc
@@ -2,12 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// TODO(fdoray): Remove this file once TaskScheduler initialization in the
+// browser process uses the components/task_scheduler_util/browser/ API on all
+// platforms.
+
 #include "components/task_scheduler_util/variations/browser_variations_util.h"
 
 #include <map>
 #include <vector>
 
 #include "base/metrics/field_trial.h"
+#include "base/task_scheduler/scheduler_worker_pool_params.h"
 #include "components/variations/variations_associated_data.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/content/browser/media/session/media_session_impl.cc b/content/browser/media/session/media_session_impl.cc
index e405b13..136c0ed0 100644
--- a/content/browser/media/session/media_session_impl.cc
+++ b/content/browser/media/session/media_session_impl.cc
@@ -575,6 +575,7 @@
 
 void MediaSessionImpl::OnServiceCreated(MediaSessionServiceImpl* service) {
   services_[service->GetRenderFrameHost()] = service;
+  UpdateRoutedService();
 }
 
 void MediaSessionImpl::OnServiceDestroyed(MediaSessionServiceImpl* service) {
@@ -617,11 +618,7 @@
 }
 
 bool MediaSessionImpl::IsServiceActiveForRenderFrameHost(RenderFrameHost* rfh) {
-  if (!services_.count(rfh))
-    return false;
-
-  return services_[rfh]->metadata().has_value() ||
-         !services_[rfh]->actions().empty();
+  return services_.find(rfh) != services_.end();
 }
 
 void MediaSessionImpl::UpdateRoutedService() {
diff --git a/content/browser/media/session/media_session_impl_browsertest.cc b/content/browser/media/session/media_session_impl_browsertest.cc
index 1fddbe252..198a6ce34 100644
--- a/content/browser/media/session/media_session_impl_browsertest.cc
+++ b/content/browser/media/session/media_session_impl_browsertest.cc
@@ -157,7 +157,6 @@
   void EnsureMediaSessionService() {
     mock_media_session_service_.reset(new MockMediaSessionServiceImpl(
         shell()->web_contents()->GetMainFrame()));
-    mock_media_session_service_->SetMetadata(content::MediaMetadata());
   }
 
   void SetPlaybackState(blink::mojom::MediaSessionPlaybackState state) {
diff --git a/content/browser/media/session/media_session_impl_service_routing_unittest.cc b/content/browser/media/session/media_session_impl_service_routing_unittest.cc
index b1b8c64..1f486748 100644
--- a/content/browser/media/session/media_session_impl_service_routing_unittest.cc
+++ b/content/browser/media/session/media_session_impl_service_routing_unittest.cc
@@ -74,7 +74,10 @@
  protected:
   void CreateServiceForFrame(TestRenderFrameHost* frame) {
     services_[frame] = base::MakeUnique<MockMediaSessionServiceImpl>(frame);
-    services_[frame]->SetMetadata(MediaMetadata());
+  }
+
+  void DestroyServiceForFrame(TestRenderFrameHost* frame) {
+    services_.erase(frame);
   }
 
   void StartPlayerForFrame(TestRenderFrameHost* frame) {
@@ -133,26 +136,40 @@
 }
 
 TEST_F(MediaSessionImplServiceRoutingTest,
-       OnlyMainFrameProducesAudioButHasInactiveService) {
-  StartPlayerForFrame(main_frame_);
-
+       OnlyMainFrameProducesAudioButHasDestroyedService) {
   CreateServiceForFrame(main_frame_);
-  services_[main_frame_]->SetMetadata(base::nullopt);
+  StartPlayerForFrame(main_frame_);
+  DestroyServiceForFrame(main_frame_);
 
   ASSERT_EQ(nullptr, ComputeServiceForRouting());
 }
 
 TEST_F(MediaSessionImplServiceRoutingTest,
-       OnlySubFrameProducesAudioButHasInactiveService) {
-  StartPlayerForFrame(sub_frame_);
-
+       OnlySubFrameProducesAudioButHasDestroyedService) {
   CreateServiceForFrame(sub_frame_);
-  services_[sub_frame_]->SetMetadata(base::nullopt);
+  StartPlayerForFrame(sub_frame_);
+  DestroyServiceForFrame(sub_frame_);
 
   ASSERT_EQ(nullptr, ComputeServiceForRouting());
 }
 
 TEST_F(MediaSessionImplServiceRoutingTest,
+       OnlyMainFrameProducesAudioAndServiceIsCreatedAfterwards) {
+  StartPlayerForFrame(main_frame_);
+  CreateServiceForFrame(main_frame_);
+
+  ASSERT_EQ(services_[main_frame_].get(), ComputeServiceForRouting());
+}
+
+TEST_F(MediaSessionImplServiceRoutingTest,
+       OnlySubFrameProducesAudioAndServiceIsCreatedAfterwards) {
+  StartPlayerForFrame(sub_frame_);
+  CreateServiceForFrame(sub_frame_);
+
+  ASSERT_EQ(services_[sub_frame_].get(), ComputeServiceForRouting());
+}
+
+TEST_F(MediaSessionImplServiceRoutingTest,
        BothFrameProducesAudioButOnlySubFrameHasService) {
   StartPlayerForFrame(main_frame_);
   StartPlayerForFrame(sub_frame_);
diff --git a/content/public/test/test_browser_thread_bundle.cc b/content/public/test/test_browser_thread_bundle.cc
index d683112..211ecdfa 100644
--- a/content/public/test/test_browser_thread_bundle.cc
+++ b/content/public/test/test_browser_thread_bundle.cc
@@ -7,6 +7,7 @@
 #include "base/logging.h"
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
+#include "base/test/scoped_task_scheduler.h"
 #include "content/browser/browser_thread_impl.h"
 #include "content/public/test/test_browser_thread.h"
 
@@ -52,6 +53,8 @@
   ui_thread_->Stop();
   base::RunLoop().RunUntilIdle();
 
+  task_scheduler_.reset();
+
   // |message_loop_| needs to explicitly go away before fake threads in order
   // for DestructionObservers hooked to |message_loop_| to be able to invoke
   // BrowserThread::CurrentlyOn() -- ref. ~TestBrowserThread().
@@ -71,6 +74,9 @@
     message_loop_.reset(new base::MessageLoopForUI());
   }
 
+  task_scheduler_.reset(
+      new base::test::ScopedTaskScheduler(message_loop_.get()));
+
   ui_thread_.reset(
       new TestBrowserThread(BrowserThread::UI, message_loop_.get()));
 
diff --git a/content/public/test/test_browser_thread_bundle.h b/content/public/test/test_browser_thread_bundle.h
index 2e1e69bd..0138a723 100644
--- a/content/public/test/test_browser_thread_bundle.h
+++ b/content/public/test/test_browser_thread_bundle.h
@@ -3,34 +3,27 @@
 // found in the LICENSE file.
 
 // TestBrowserThreadBundle is a convenience class for creating a set of
-// TestBrowserThreads in unit tests.  For most tests, it is sufficient to
-// just instantiate the TestBrowserThreadBundle as a member variable.
-// It is a good idea to put the TestBrowserThreadBundle as the first member
-// variable in test classes, so it is destroyed last, and the test threads
-// always exist from the perspective of other classes.
+// TestBrowserThreads, a blocking pool, and a task scheduler in unit tests. For
+// most tests, it is sufficient to just instantiate the TestBrowserThreadBundle
+// as a member variable. It is a good idea to put the TestBrowserThreadBundle as
+// the first member variable in test classes, so it is destroyed last, and the
+// test threads always exist from the perspective of other classes.
 //
-// By default, all of the created TestBrowserThreads will be backed by a single
-// shared MessageLoop. If a test truly needs separate threads, it can do
-// so by passing the appropriate combination of option values during
-// the TestBrowserThreadBundle construction.
+// By default, all of the created TestBrowserThreads and the task scheduler will
+// be backed by a single shared MessageLoop. If a test truly needs separate
+// threads, it can do so by passing the appropriate combination of option values
+// during the TestBrowserThreadBundle construction.
 //
-// The TestBrowserThreadBundle will attempt to drain the MessageLoop on
-// destruction. Sometimes a test needs to drain currently enqueued tasks
-// mid-test. Browser tests should call content::RunAllPendingInMessageLoop().
-// Unit tests should use base::RunLoop (e.g., base::RunLoop().RunUntilIdle()).
-// TODO(phajdan.jr): Revise this comment after switch to Aura.
+// To synchronously run tasks posted to task scheduler or to TestBrowserThreads
+// that use the shared MessageLoop, call RunLoop::Run/RunUntilIdle() on the
+// thread where the TestBrowserThreadBundle lives. The destructor of
+// TestBrowserThreadBundle runs remaining TestBrowserThreads tasks, remaining
+// blocking pool tasks, and remaining BLOCK_SHUTDOWN task scheduler tasks.
 //
-// The TestBrowserThreadBundle will also flush the blocking pool on destruction.
-// We do this to avoid memory leaks, particularly in the case of threads posting
-// tasks to the blocking pool via PostTaskAndReply. By ensuring that the tasks
-// are run while the originating TestBroswserThreads still exist, we prevent
-// leakage of PostTaskAndReplyRelay objects. We also flush the blocking pool
-// again at the point where it would normally be shut down, to better simulate
-// the normal thread shutdown process.
-//
-// Some tests using the IO thread expect a MessageLoopForIO. Passing
-// IO_MAINLOOP will use a MessageLoopForIO for the main MessageLoop.
-// Most of the time, this avoids needing to use a REAL_IO_THREAD.
+// If a test needs a MessageLoopForIO on the main thread, it should use the
+// IO_MAINLOOP option. This also allows task scheduler tasks to use
+// FileDescriptorWatcher. Most of the time, IO_MAINLOOP avoids needing to use a
+// REAL_IO_THREAD.
 //
 // For some tests it is important to emulate real browser startup. During real
 // browser startup some initialization is done (e.g. creation of thread objects)
@@ -51,6 +44,9 @@
 
 namespace base {
 class MessageLoop;
+namespace test {
+class ScopedTaskScheduler;
+}  // namespace test
 }  // namespace base
 
 namespace content {
@@ -84,6 +80,7 @@
   void Init();
 
   std::unique_ptr<base::MessageLoop> message_loop_;
+  std::unique_ptr<base::test::ScopedTaskScheduler> task_scheduler_;
   std::unique_ptr<TestBrowserThread> ui_thread_;
   std::unique_ptr<TestBrowserThread> db_thread_;
   std::unique_ptr<TestBrowserThread> file_thread_;
diff --git a/third_party/WebKit/LayoutTests/fullscreen/full-screen-test.js b/third_party/WebKit/LayoutTests/fullscreen/full-screen-test.js
index ae6ff34..29484405 100644
--- a/third_party/WebKit/LayoutTests/fullscreen/full-screen-test.js
+++ b/third_party/WebKit/LayoutTests/fullscreen/full-screen-test.js
@@ -137,10 +137,12 @@
 
 function endTest()
 {
+    if (testEnded)
+        return;
     consoleWrite("END OF TEST");
     testEnded = true;
     if (window.testRunner)
-        testRunner.notifyDone();
+        testRunner.layoutAndPaintAsyncThen(() => testRunner.notifyDone());
 }
 
 function logResult(success, text)
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/MANIFEST.json b/third_party/WebKit/LayoutTests/imported/wpt/MANIFEST.json
index e30b7cb..2d6dce32 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/MANIFEST.json
+++ b/third_party/WebKit/LayoutTests/imported/wpt/MANIFEST.json
@@ -5569,6 +5569,12 @@
             "url": "/WebIDL/ecmascript-binding/interface-object.html"
           }
         ],
+        "accelerometer/idlharness.https.html": [
+          {
+            "path": "accelerometer/idlharness.https.html",
+            "url": "/accelerometer/idlharness.https.html"
+          }
+        ],
         "clear-site-data/navigation.html": [
           {
             "path": "clear-site-data/navigation.html",
@@ -6238,6 +6244,12 @@
             "url": "/dom/events/CustomEvent.html"
           }
         ],
+        "dom/events/Event-cancelBubble.html": [
+          {
+            "path": "dom/events/Event-cancelBubble.html",
+            "url": "/dom/events/Event-cancelBubble.html"
+          }
+        ],
         "dom/events/Event-constants.html": [
           {
             "path": "dom/events/Event-constants.html",
@@ -6262,6 +6274,12 @@
             "url": "/dom/events/Event-defaultPrevented.html"
           }
         ],
+        "dom/events/Event-dispatch-bubble-canceled.html": [
+          {
+            "path": "dom/events/Event-dispatch-bubble-canceled.html",
+            "url": "/dom/events/Event-dispatch-bubble-canceled.html"
+          }
+        ],
         "dom/events/Event-dispatch-bubbles-false.html": [
           {
             "path": "dom/events/Event-dispatch-bubbles-false.html",
@@ -6292,6 +6310,12 @@
             "url": "/dom/events/Event-dispatch-handlers-changed.html"
           }
         ],
+        "dom/events/Event-dispatch-multiple-cancelBubble.html": [
+          {
+            "path": "dom/events/Event-dispatch-multiple-cancelBubble.html",
+            "url": "/dom/events/Event-dispatch-multiple-cancelBubble.html"
+          }
+        ],
         "dom/events/Event-dispatch-multiple-stopPropagation.html": [
           {
             "path": "dom/events/Event-dispatch-multiple-stopPropagation.html",
@@ -8654,6 +8678,12 @@
             "url": "/gamepad/idlharness.html"
           }
         ],
+        "gyroscope/idlharness.https.html": [
+          {
+            "path": "gyroscope/idlharness.https.html",
+            "url": "/gyroscope/idlharness.https.html"
+          }
+        ],
         "hr-time/basic.html": [
           {
             "path": "hr-time/basic.html",
@@ -14801,6 +14831,12 @@
             "url": "/input-events/idlharness.html"
           }
         ],
+        "magnetometer/idlharness.https.html": [
+          {
+            "path": "magnetometer/idlharness.https.html",
+            "url": "/magnetometer/idlharness.https.html"
+          }
+        ],
         "mediacapture-streams/GUM-api.https.html": [
           {
             "path": "mediacapture-streams/GUM-api.https.html",
@@ -14975,6 +15011,84 @@
             "url": "/pointerevents/pointerevent_touch-action-verification.html"
           }
         ],
+        "preload/avoid_delaying_onload_link_preload.html": [
+          {
+            "path": "preload/avoid_delaying_onload_link_preload.html",
+            "url": "/preload/avoid_delaying_onload_link_preload.html"
+          }
+        ],
+        "preload/delaying_onload_link_preload_after_discovery.html": [
+          {
+            "path": "preload/delaying_onload_link_preload_after_discovery.html",
+            "url": "/preload/delaying_onload_link_preload_after_discovery.html"
+          }
+        ],
+        "preload/download_resources.html": [
+          {
+            "path": "preload/download_resources.html",
+            "url": "/preload/download_resources.html"
+          }
+        ],
+        "preload/dynamic_adding_preload.html": [
+          {
+            "path": "preload/dynamic_adding_preload.html",
+            "url": "/preload/dynamic_adding_preload.html"
+          }
+        ],
+        "preload/link_header_preload.html": [
+          {
+            "path": "preload/link_header_preload.html",
+            "url": "/preload/link_header_preload.html"
+          }
+        ],
+        "preload/link_header_preload_delay_onload.html": [
+          {
+            "path": "preload/link_header_preload_delay_onload.html",
+            "url": "/preload/link_header_preload_delay_onload.html"
+          }
+        ],
+        "preload/onerror_event.html": [
+          {
+            "path": "preload/onerror_event.html",
+            "url": "/preload/onerror_event.html"
+          }
+        ],
+        "preload/onload_event.html": [
+          {
+            "path": "preload/onload_event.html",
+            "url": "/preload/onload_event.html"
+          }
+        ],
+        "preload/preload-csp.sub.html": [
+          {
+            "path": "preload/preload-csp.sub.html",
+            "url": "/preload/preload-csp.sub.html"
+          }
+        ],
+        "preload/preload-default-csp.sub.html": [
+          {
+            "path": "preload/preload-default-csp.sub.html",
+            "url": "/preload/preload-default-csp.sub.html"
+          }
+        ],
+        "preload/preload_with_type.html": [
+          {
+            "path": "preload/preload_with_type.html",
+            "url": "/preload/preload_with_type.html"
+          }
+        ],
+        "preload/single_download_late_used_preload.html": [
+          {
+            "path": "preload/single_download_late_used_preload.html",
+            "url": "/preload/single_download_late_used_preload.html"
+          }
+        ],
+        "preload/single_download_preload.html": [
+          {
+            "path": "preload/single_download_preload.html",
+            "url": "/preload/single_download_preload.html"
+          }
+        ],
         "quirks-mode/blocks-ignore-line-height.html": [
           {
             "path": "quirks-mode/blocks-ignore-line-height.html",
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/accelerometer/idlharness.https-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/accelerometer/idlharness.https-expected.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/accelerometer/idlharness.https-expected.txt
@@ -0,0 +1 @@
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/accelerometer/idlharness.https.html b/third_party/WebKit/LayoutTests/imported/wpt/accelerometer/idlharness.https.html
new file mode 100644
index 0000000..79cd5c9
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/accelerometer/idlharness.https.html
@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Accelerometer Sensor IDL tests</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://www.w3.org/TR/accelerometer/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<style>
+  pre {
+    display: none;
+  }
+</style>
+<div id="log"></div>
+
+<pre id="idl">
+interface Event {
+};
+
+interface EventTarget {
+};
+
+interface EventHandler {
+};
+
+interface Error {
+};
+
+dictionary EventInit {
+};
+</pre>
+
+<pre id="generic-idl">
+[SecureContext]
+interface Sensor : EventTarget {
+  readonly attribute SensorState state;
+  readonly attribute SensorReading? reading;
+  void start();
+  void stop();
+  attribute EventHandler onchange;
+  attribute EventHandler onactivate;
+  attribute EventHandler onerror;
+};
+
+dictionary SensorOptions {
+  double? frequency;
+};
+
+enum SensorState {
+  "idle",
+  "activating",
+  "activated",
+  "errored"
+};
+
+[SecureContext]
+interface SensorReading {
+  readonly attribute DOMHighResTimeStamp timeStamp;
+};
+
+[SecureContext, Constructor(DOMString type, SensorErrorEventInit errorEventInitDict)]
+interface SensorErrorEvent : Event {
+  readonly attribute Error error;
+};
+
+dictionary SensorErrorEventInit : EventInit {
+  required Error error;
+};
+
+</pre>
+
+<pre id="accelerometer-idl">
+[Constructor(optional AccelerometerOptions accelerometerOptions)]
+interface Accelerometer : Sensor {
+  readonly attribute AccelerometerReading? reading;
+  readonly attribute boolean includesGravity;
+};
+
+dictionary AccelerometerOptions :  SensorOptions  {
+  boolean includeGravity = true;
+};
+
+[Constructor(AccelerometerReadingInit AccelerometerReadingInit)]
+interface AccelerometerReading : SensorReading {
+    readonly attribute double x;
+    readonly attribute double y;
+    readonly attribute double z;
+};
+
+dictionary AccelerometerReadingInit {
+    double x = 0;
+    double y = 0;
+    double z = 0;
+};
+</pre>
+
+<script>
+(function() {
+  "use strict";
+  var idl_array = new IdlArray();
+  idl_array.add_untested_idls(document.getElementById('idl').textContent);
+  idl_array.add_untested_idls(document.getElementById('generic-idl').textContent);
+  idl_array.add_idls(document.getElementById('accelerometer-idl').textContent);
+
+  idl_array.add_objects({
+    Accelerometer: ['new Accelerometer();'],
+    AccelerometerReading: ['new AccelerometerReading({x: 0.5, y: 0.5, z: 0.5});']
+  });
+
+  idl_array.test();
+})();
+</script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-cancelBubble-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-cancelBubble-expected.txt
new file mode 100644
index 0000000..073ac2b6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-cancelBubble-expected.txt
@@ -0,0 +1,11 @@
+This is a testharness.js-based test.
+PASS cancelBubble must be false when an event is initially created. 
+FAIL Initializing an event must set cancelBubble to false. assert_false: initEvent() must set cancelBubble to false. [bubbles=true] expected false got true
+FAIL stopPropagation() must set cancelBubble to true. assert_true: stopPropagation() must set cancelBubble to true. expected true got false
+FAIL stopImmediatePropagation() must set cancelBubble to true. assert_true: stopImmediatePropagation() must set cancelBubble to true. expected true got false
+FAIL Event.cancelBubble=false must have no effect. assert_true: cancelBubble must still be true after attempting to set it to false. expected true got false
+FAIL Event.cancelBubble=false must have no effect during event propagation. assert_unreached: Setting Event.cancelBubble=false after setting Event.cancelBubble=true should have no effect. Reached unreachable code
+PASS cancelBubble must be false after an event has been dispatched. 
+FAIL Event.cancelBubble=true must set the stop propagation flag. assert_unreached: Setting cancelBubble=true should stop the event from propagating further, including during the Capture Phase. Reached unreachable code
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-cancelBubble.html b/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-cancelBubble.html
new file mode 100644
index 0000000..d8d2d72
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-cancelBubble.html
@@ -0,0 +1,132 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Event.cancelBubble</title>
+  <link rel="author" title="Chris Rebert" href="http://chrisrebert.com">
+  <link rel="help" href="https://dom.spec.whatwg.org/#dom-event-cancelbubble">
+  <meta name="flags" content="dom">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+  <div id="outer">
+    <div id="middle">
+      <div id="inner"></div>
+    </div>
+  </div>
+  <script>
+test(function () {
+  // See https://dom.spec.whatwg.org/#stop-propagation-flag
+  var e = document.createEvent('Event');
+  assert_false(e.cancelBubble, "cancelBubble must be false after event creation.");
+}, "cancelBubble must be false when an event is initially created.");
+
+test(function () {
+  // See https://dom.spec.whatwg.org/#concept-event-initialize
+
+  // Event which bubbles.
+  var one = document.createEvent('Event');
+  one.cancelBubble = true;
+  one.initEvent('foo', true/*bubbles*/, false/*cancelable*/);
+  assert_false(one.cancelBubble, "initEvent() must set cancelBubble to false. [bubbles=true]");
+  // Re-initialization.
+  one.cancelBubble = true;
+  one.initEvent('foo', true/*bubbles*/, false/*cancelable*/);
+  assert_false(one.cancelBubble, "2nd initEvent() call must set cancelBubble to false. [bubbles=true]");
+
+  // Event which doesn't bubble.
+  var two = document.createEvent('Event');
+  two.cancelBubble = true;
+  two.initEvent('foo', false/*bubbles*/, false/*cancelable*/);
+  assert_false(two.cancelBubble, "initEvent() must set cancelBubble to false. [bubbles=false]");
+  // Re-initialization.
+  two.cancelBubble = true;
+  two.initEvent('foo', false/*bubbles*/, false/*cancelable*/);
+  assert_false(two.cancelBubble, "2nd initEvent() call must set cancelBubble to false. [bubbles=false]");
+}, "Initializing an event must set cancelBubble to false.");
+
+test(function () {
+  // See https://dom.spec.whatwg.org/#dom-event-stoppropagation
+  var e = document.createEvent('Event');
+  e.stopPropagation();
+  assert_true(e.cancelBubble, "stopPropagation() must set cancelBubble to true.");
+}, "stopPropagation() must set cancelBubble to true.");
+
+test(function () {
+  // See https://dom.spec.whatwg.org/#dom-event-stopimmediatepropagation
+  var e = document.createEvent('Event');
+  e.stopImmediatePropagation();
+  assert_true(e.cancelBubble, "stopImmediatePropagation() must set cancelBubble to true.");
+}, "stopImmediatePropagation() must set cancelBubble to true.");
+
+test(function () {
+  var one = document.createEvent('Event');
+  one.stopPropagation();
+  one.cancelBubble = false;
+  assert_true(one.cancelBubble, "cancelBubble must still be true after attempting to set it to false.");
+}, "Event.cancelBubble=false must have no effect.");
+
+test(function (t) {
+  var outer = document.getElementById('outer');
+  var middle = document.getElementById('middle');
+  var inner = document.getElementById('inner');
+
+  outer.addEventListener('barbaz', t.step_func(function () {
+    assert_unreached("Setting Event.cancelBubble=false after setting Event.cancelBubble=true should have no effect.");
+  }), false/*useCapture*/);
+
+  middle.addEventListener('barbaz', function (e) {
+    e.cancelBubble = true;// Stop propagation.
+    e.cancelBubble = false;// Should be a no-op.
+  }, false/*useCapture*/);
+
+  var barbazEvent = document.createEvent('Event');
+  barbazEvent.initEvent('barbaz', true/*bubbles*/, false/*cancelable*/);
+  inner.dispatchEvent(barbazEvent);
+}, "Event.cancelBubble=false must have no effect during event propagation.");
+
+test(function () {
+  // See https://dom.spec.whatwg.org/#concept-event-dispatch
+  // "14. Unset event’s [...] stop propagation flag,"
+  var e = document.createEvent('Event');
+  e.initEvent('foobar', true/*bubbles*/, true/*cancelable*/);
+  document.body.addEventListener('foobar', function listener(e) {
+    e.stopPropagation();
+  });
+  document.body.dispatchEvent(e);
+  assert_false(e.cancelBubble, "cancelBubble must be false after an event has been dispatched.");
+}, "cancelBubble must be false after an event has been dispatched.");
+
+test(function (t) {
+  var outer = document.getElementById('outer');
+  var middle = document.getElementById('middle');
+  var inner = document.getElementById('inner');
+
+  var propagationStopper = function (e) {
+    e.cancelBubble = true;
+  };
+
+  // Bubble phase
+  middle.addEventListener('bar', propagationStopper, false/*useCapture*/);
+  outer.addEventListener('bar', t.step_func(function listenerOne() {
+    assert_unreached("Setting cancelBubble=true should stop the event from bubbling further.");
+  }), false/*useCapture*/);
+
+  var barEvent = document.createEvent('Event');
+  barEvent.initEvent('bar', true/*bubbles*/, false/*cancelable*/);
+  inner.dispatchEvent(barEvent);
+
+  // Capture phase
+  outer.addEventListener('qux', propagationStopper, true/*useCapture*/);
+  middle.addEventListener('qux', t.step_func(function listenerTwo() {
+    assert_unreached("Setting cancelBubble=true should stop the event from propagating further, including during the Capture Phase.");
+  }), true/*useCapture*/);
+
+  var quxEvent = document.createEvent('Event');
+  quxEvent.initEvent('qux', false/*bubbles*/, false/*cancelable*/);
+  inner.dispatchEvent(quxEvent);
+}, "Event.cancelBubble=true must set the stop propagation flag.");
+  </script>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-dispatch-bubble-canceled-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-dispatch-bubble-canceled-expected.txt
new file mode 100644
index 0000000..1c3c1b8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-dispatch-bubble-canceled-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Setting cancelBubble=true prior to dispatchEvent() assert_array_equals: actual_targets lengths differ, expected 0 got 9
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-dispatch-bubble-canceled.html b/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-dispatch-bubble-canceled.html
new file mode 100644
index 0000000..20f398f6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-dispatch-bubble-canceled.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Setting cancelBubble=true prior to dispatchEvent()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+
+<table id="table" border="1" style="display: none">
+    <tbody id="table-body">
+    <tr id="table-row">
+        <td id="table-cell">Shady Grove</td>
+        <td>Aeolian</td>
+    </tr>
+    <tr id="parent">
+        <td id="target">Over the river, Charlie</td>
+        <td>Dorian</td>
+    </tr>
+    </tbody>
+</table>
+
+<script>
+test(function() {
+    var event = "foo";
+    var target = document.getElementById("target");
+    var parent = document.getElementById("parent");
+    var tbody = document.getElementById("table-body");
+    var table = document.getElementById("table");
+    var body = document.body;
+    var html = document.documentElement;
+    var current_targets = [window, document, html, body, table, tbody, parent, target];
+    var expected_targets = [];
+    var actual_targets = [];
+    var expected_phases = [];
+    var actual_phases = [];
+
+    var test_event = function(evt) {
+        actual_targets.push(evt.currentTarget);
+        actual_phases.push(evt.eventPhase);
+    };
+
+    for (var i = 0; i < current_targets.length; ++i) {
+        current_targets[i].addEventListener(event, test_event, true);
+        current_targets[i].addEventListener(event, test_event, false);
+    }
+
+    var evt = document.createEvent("Event");
+    evt.initEvent(event, true, true);
+    evt.cancelBubble = true;
+    target.dispatchEvent(evt);
+
+    assert_array_equals(actual_targets, expected_targets, "actual_targets");
+    assert_array_equals(actual_phases, expected_phases, "actual_phases");
+});
+</script>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-dispatch-multiple-cancelBubble-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-dispatch-multiple-cancelBubble-expected.txt
new file mode 100644
index 0000000..fc880274
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-dispatch-multiple-cancelBubble-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Multiple dispatchEvent() and cancelBubble assert_array_equals: lengths differ, expected 2 got 1
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-dispatch-multiple-cancelBubble.html b/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-dispatch-multiple-cancelBubble.html
new file mode 100644
index 0000000..2873fd77
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-dispatch-multiple-cancelBubble.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Multiple dispatchEvent() and cancelBubble</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id=log></div>
+
+<div id="parent" style="display: none">
+    <input id="target" type="hidden" value=""/>
+</div>
+
+<script>
+test(function() {
+    var event_type = "foo";
+    var target = document.getElementById("target");
+    var parent = document.getElementById("parent");
+    var actual_result;
+    var test_event = function(evt) {
+        actual_result.push(evt.currentTarget);
+
+        if (parent == evt.currentTarget) {
+            evt.cancelBubble = true;
+        }
+    };
+
+    var evt = document.createEvent("Event");
+    evt.initEvent(event_type, true, true);
+
+    target.addEventListener(event_type, test_event, false);
+    parent.addEventListener(event_type, test_event, false);
+    document.addEventListener(event_type, test_event, false);
+    window.addEventListener(event_type, test_event, false);
+
+    actual_result = [];
+    target.dispatchEvent(evt);
+    assert_array_equals(actual_result, [target, parent]);
+
+    actual_result = [];
+    parent.dispatchEvent(evt);
+    assert_array_equals(actual_result, [parent]);
+
+    actual_result = [];
+    document.dispatchEvent(evt);
+    assert_array_equals(actual_result, [document, window]);
+});
+</script>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-initEvent.html b/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-initEvent.html
index 85abdff2..568232a5 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-initEvent.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-initEvent.html
@@ -85,6 +85,7 @@
   var target = document.createElement("div")
   var called = false
   target.addEventListener("type", function() { called = true }, false)
+  assert_false(e.cancelBubble, "cancelBubble must be false")
   assert_true(target.dispatchEvent(e), "dispatchEvent must return true")
   assert_true(called, "Listener must be called")
 }, "Calling initEvent must unset the stop propagation flag.")
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-propagation-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-propagation-expected.txt
new file mode 100644
index 0000000..718901d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-propagation-expected.txt
@@ -0,0 +1,10 @@
+This is a testharness.js-based test.
+PASS Newly-created Event 
+PASS After stopPropagation() 
+PASS Reinitialized after stopPropagation() 
+PASS After stopImmediatePropagation() 
+PASS Reinitialized after stopImmediatePropagation() 
+FAIL After cancelBubble=true assert_equals: Propagation flag expected false but got true
+PASS Reinitialized after cancelBubble=true 
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-propagation.html b/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-propagation.html
index 459d45c..33989eb 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-propagation.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/dom/events/Event-propagation.html
@@ -38,4 +38,11 @@
 testPropagationFlag(ev, false, "After stopImmediatePropagation()");
 ev.initEvent("foo", true, false);
 testPropagationFlag(ev, true, "Reinitialized after stopImmediatePropagation()");
+
+var ev = document.createEvent("Event");
+ev.initEvent("foo", true, false);
+ev.cancelBubble = true;
+testPropagationFlag(ev, false, "After cancelBubble=true");
+ev.initEvent("foo", true, false);
+testPropagationFlag(ev, true, "Reinitialized after cancelBubble=true");
 </script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/gyroscope/idlharness.https-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/gyroscope/idlharness.https-expected.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/gyroscope/idlharness.https-expected.txt
@@ -0,0 +1 @@
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/gyroscope/idlharness.https.html b/third_party/WebKit/LayoutTests/imported/wpt/gyroscope/idlharness.https.html
new file mode 100644
index 0000000..53d3b81c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/gyroscope/idlharness.https.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Gyroscope Sensor IDL tests</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://www.w3.org/TR/gyroscope/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<style>
+  pre {
+    display: none;
+  }
+</style>
+<div id="log"></div>
+
+<pre id="idl">
+interface Event {
+};
+
+interface EventTarget {
+};
+
+interface EventHandler {
+};
+
+interface Error {
+};
+
+dictionary EventInit {
+};
+</pre>
+
+<pre id="generic-idl">
+[SecureContext]
+interface Sensor : EventTarget {
+  readonly attribute SensorState state;
+  readonly attribute SensorReading? reading;
+  void start();
+  void stop();
+  attribute EventHandler onchange;
+  attribute EventHandler onactivate;
+  attribute EventHandler onerror;
+};
+
+dictionary SensorOptions {
+  double? frequency;
+};
+
+enum SensorState {
+  "idle",
+  "activating",
+  "activated",
+  "errored"
+};
+
+[SecureContext]
+interface SensorReading {
+  readonly attribute DOMHighResTimeStamp timeStamp;
+};
+
+[SecureContext, Constructor(DOMString type, SensorErrorEventInit errorEventInitDict)]
+interface SensorErrorEvent : Event {
+  readonly attribute Error error;
+};
+
+dictionary SensorErrorEventInit : EventInit {
+  required Error error;
+};
+
+</pre>
+
+<pre id="gyroscope-idl">
+[Constructor(optional SensorOptions sensorOptions)]
+interface Gyroscope : Sensor {
+  readonly attribute GyroscopeReading? reading;
+};
+
+[Constructor(GyroscopeReadingInit GyroscopeReadingInit)]
+interface GyroscopeReading : SensorReading {
+    readonly attribute unrestricted double x;
+    readonly attribute unrestricted double y;
+    readonly attribute unrestricted double z;
+};
+
+dictionary GyroscopeReadingInit {
+    unrestricted double x = 0;
+    unrestricted double y = 0;
+    unrestricted double z = 0;
+};
+</pre>
+
+<script>
+(function() {
+  "use strict";
+  var idl_array = new IdlArray();
+  idl_array.add_untested_idls(document.getElementById('idl').textContent);
+  idl_array.add_untested_idls(document.getElementById('generic-idl').textContent);
+  idl_array.add_idls(document.getElementById('gyroscope-idl').textContent);
+
+  idl_array.add_objects({
+    Gyroscope: ['new Gyroscope();'],
+    GyroscopeReading: ['new GyroscopeReading({x: 0.5, y: 0.5, z: 0.5});']
+  });
+
+  idl_array.test();
+})();
+</script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/magnetometer/idlharness.https-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/magnetometer/idlharness.https-expected.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/magnetometer/idlharness.https-expected.txt
@@ -0,0 +1 @@
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/magnetometer/idlharness.https.html b/third_party/WebKit/LayoutTests/imported/wpt/magnetometer/idlharness.https.html
new file mode 100644
index 0000000..00fdafe
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/magnetometer/idlharness.https.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Magnetometer Sensor IDL tests</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://www.w3.org/TR/magnetometer/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<style>
+  pre {
+    display: none;
+  }
+</style>
+<div id="log"></div>
+
+<pre id="idl">
+interface Event {
+};
+
+interface EventTarget {
+};
+
+interface EventHandler {
+};
+
+interface Error {
+};
+
+dictionary EventInit {
+};
+</pre>
+
+<pre id="generic-idl">
+[SecureContext]
+interface Sensor : EventTarget {
+  readonly attribute SensorState state;
+  readonly attribute SensorReading? reading;
+  void start();
+  void stop();
+  attribute EventHandler onchange;
+  attribute EventHandler onactivate;
+  attribute EventHandler onerror;
+};
+
+dictionary SensorOptions {
+  double? frequency;
+};
+
+enum SensorState {
+  "idle",
+  "activating",
+  "activated",
+  "errored"
+};
+
+[SecureContext]
+interface SensorReading {
+  readonly attribute DOMHighResTimeStamp timeStamp;
+};
+
+[SecureContext, Constructor(DOMString type, SensorErrorEventInit errorEventInitDict)]
+interface SensorErrorEvent : Event {
+  readonly attribute Error error;
+};
+
+dictionary SensorErrorEventInit : EventInit {
+  required Error error;
+};
+
+</pre>
+
+<pre id="magnetometer-idl">
+[Constructor(optional SensorOptions sensorOptions)]
+interface Magnetometer : Sensor {
+  readonly attribute MagnetometerReading? reading;
+};
+
+[Constructor(MagnetometerReadingInit magnetometerReadingInit)]
+interface MagnetometerReading : SensorReading {
+    readonly attribute double x;
+    readonly attribute double y;
+    readonly attribute double z;
+};
+
+dictionary MagnetometerReadingInit {
+  double x = 0;
+  double y = 0;
+  double z = 0;
+};
+</pre>
+
+<script>
+(function() {
+  "use strict";
+  var idl_array = new IdlArray();
+  idl_array.add_untested_idls(document.getElementById('idl').textContent);
+  idl_array.add_untested_idls(document.getElementById('generic-idl').textContent);
+  idl_array.add_idls(document.getElementById('magnetometer-idl').textContent);
+
+  idl_array.add_objects({
+    Magnetometer: ['new Magnetometer();'],
+    MagnetometerReading: ['new MagnetometerReading({x: 0.5, y: 0.5, z: 0.5});']
+  });
+
+  idl_array.test();
+})();
+</script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/avoid_delaying_onload_link_preload-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/preload/avoid_delaying_onload_link_preload-expected.txt
new file mode 100644
index 0000000..94f13eaa
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/avoid_delaying_onload_link_preload-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Makes sure link preload preloaded resources are not delaying onload assert_equals: expected 2 but got 0
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/avoid_delaying_onload_link_preload.html b/third_party/WebKit/LayoutTests/imported/wpt/preload/avoid_delaying_onload_link_preload.html
new file mode 100644
index 0000000..e92658dd2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/avoid_delaying_onload_link_preload.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    var t = async_test('Makes sure link preload preloaded resources are not delaying onload');
+</script>
+<link rel=preload href="resources/dummy.js?pipe=trickle(d5)" as=script>
+<script>
+    window.addEventListener("load", t.step_func(function() {
+        assert_equals(performance.getEntriesByType("resource").length, 2);
+        t.done();
+    }));
+</script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/delaying_onload_link_preload_after_discovery-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/preload/delaying_onload_link_preload_after_discovery-expected.txt
new file mode 100644
index 0000000..497aad5
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/delaying_onload_link_preload_after_discovery-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Makes sure link preload preloaded resources are delaying onload after discovery assert_equals: expected 4 but got 0
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/delaying_onload_link_preload_after_discovery.html b/third_party/WebKit/LayoutTests/imported/wpt/preload/delaying_onload_link_preload_after_discovery.html
new file mode 100644
index 0000000..44a8cef
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/delaying_onload_link_preload_after_discovery.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    var t = async_test('Makes sure link preload preloaded resources are delaying onload after discovery');
+</script>
+<link rel=preload href="resources/dummy.js?pipe=trickle(d5)" as=script>
+<link rel=preload href="resources/square.png?pipe=trickle(d5)" as=image>
+<body>
+<script>
+    window.addEventListener("load", t.step_func(function() {
+        assert_equals(performance.getEntriesByType("resource").length, 4);
+        t.done();
+    }));
+    var script = document.createElement("script");
+    script.src = "resources/dummy.js?pipe=trickle(d5)";
+    document.body.appendChild(script);
+    var img = new Image();
+    img.src = "resources/square.png?pipe=trickle(d5)";
+</script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/download_resources-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/preload/download_resources-expected.txt
new file mode 100644
index 0000000..d0b9ad4
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/download_resources-expected.txt
@@ -0,0 +1,5 @@
+CONSOLE WARNING: line 15: <link rel=preload> must have a valid `as` value
+This is a testharness.js-based test.
+FAIL Makes sure that preloaded resources are downloaded assert_equals: expected 11 but got 0
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/download_resources.html b/third_party/WebKit/LayoutTests/imported/wpt/preload/download_resources.html
new file mode 100644
index 0000000..a5f275a
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/download_resources.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    var t = async_test('Makes sure that preloaded resources are downloaded');
+</script>
+<link rel=preload href="resources/dummy.js" as=script>
+<link rel=preload href="resources/dummy.css" as=style>
+<link rel=preload href="resources/square.png" as=image>
+<link rel=preload href="/fonts/CanvasTest.ttf" as=font crossorigin>
+<link rel=preload href="/media/white.mp4" as=media>
+<link rel=preload href="/media/sound_5.oga" as=media>
+<link rel=preload href="/media/foo.vtt" as=media>
+<link rel=preload href="resources/dummy.xml?foo=bar" as=foobarxmlthing>
+<link rel=preload href="resources/dummy.xml">
+<body>
+<script src="resources/dummy.js?pipe=trickle(d5)"></script>
+<script>
+    window.addEventListener("load", t.step_func(function() {
+        var entries = performance.getEntriesByType("resource");
+        assert_equals(performance.getEntriesByType("resource").length, 11);
+        t.done();
+    }));
+</script>
+</body>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/dynamic_adding_preload-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/preload/dynamic_adding_preload-expected.txt
new file mode 100644
index 0000000..cb06632
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/dynamic_adding_preload-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Makes sure that a dynamically added preloaded resource is downloaded assert_equals: expected 3 but got 0
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/dynamic_adding_preload.html b/third_party/WebKit/LayoutTests/imported/wpt/preload/dynamic_adding_preload.html
new file mode 100644
index 0000000..3b341b5
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/dynamic_adding_preload.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    var t = async_test('Makes sure that a dynamically added preloaded resource is downloaded');
+</script>
+<body>
+<script>
+    t.step(function() {
+        var link = document.createElement("link");
+        assert_true(link.relList && link.relList.supports && link.relList.supports("preload"), "relList is not defined or browser doesn't support preload.");
+        link.as = "script";
+        link.rel = "preload";
+        link.href = "resources/dummy.js";
+        link.onload = t.step_func(function() {
+            t.step_timeout(function() {
+                assert_equals(performance.getEntriesByType("resource").length, 3);
+                t.done();
+            }, 0);
+        });
+        document.body.appendChild(link);
+    });
+</script>
+</body>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/link_header_preload-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/preload/link_header_preload-expected.txt
new file mode 100644
index 0000000..12f8bbf8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/link_header_preload-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Makes sure that Link headers preload resources assert_equals: expected 6 but got 0
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/link_header_preload.html b/third_party/WebKit/LayoutTests/imported/wpt/preload/link_header_preload.html
new file mode 100644
index 0000000..a63ffde
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/link_header_preload.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    var t = async_test('Makes sure that Link headers preload resources');
+</script>
+<body>
+<script src="resources/dummy.js?pipe=trickle(d5)"></script>
+<script>
+    window.addEventListener("load", t.step_func(function() {
+        var entries = performance.getEntriesByType("resource");
+        assert_equals(entries.length, 6);
+        t.done();
+    }));
+</script>
+</body>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/link_header_preload.html.headers b/third_party/WebKit/LayoutTests/imported/wpt/preload/link_header_preload.html.headers
new file mode 100644
index 0000000..e61b22e0
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/link_header_preload.html.headers
@@ -0,0 +1,4 @@
+Link: </preload/resources/dummy.js>;rel=preload;as=script
+Link: </preload/resources/dummy.css>;rel=preload;as=style
+Link: </preload/resources/square.png>;rel=preload;as=image
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/link_header_preload_delay_onload-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/preload/link_header_preload_delay_onload-expected.txt
new file mode 100644
index 0000000..75961b8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/link_header_preload_delay_onload-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Makes sure that Link headers preload resources and block window.onload after resource discovery assert_true: expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/link_header_preload_delay_onload.html b/third_party/WebKit/LayoutTests/imported/wpt/preload/link_header_preload_delay_onload.html
new file mode 100644
index 0000000..2ee24b8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/link_header_preload_delay_onload.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    var t = async_test('Makes sure that Link headers preload resources and block window.onload after resource discovery');
+</script>
+<body>
+<style>
+    #background {
+        width: 200px;
+        height: 200px;
+        background-image: url(resources/square.png?background);
+    }
+</style>
+<link rel="stylesheet" href="resources/dummy.css">
+<script src="resources/dummy.js"></script>
+<div id="background"></div>
+<script>
+    document.write('<img src="resources/square.png">');
+    window.addEventListener("load", t.step_func(function() {
+        var entries = performance.getEntriesByType("resource");
+        var found_background_first = false;
+        for (var i = 0; i < entries.length; ++i) {
+            var entry = entries[i];
+            if (entry.name.indexOf("square") != -1) {
+                if (entry.name.indexOf("background") != -1)
+                    found_background_first = true;
+            }
+        }
+        assert_true(found_background_first);
+        assert_equals(entries.length, 6);
+        t.done();
+    }));
+</script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/link_header_preload_delay_onload.html.headers b/third_party/WebKit/LayoutTests/imported/wpt/preload/link_header_preload_delay_onload.html.headers
new file mode 100644
index 0000000..6dad172
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/link_header_preload_delay_onload.html.headers
@@ -0,0 +1,5 @@
+Link: </preload/resources/square.png?background>;rel=preload;as=image
+Link: </preload/resources/dummy.js>;rel=preload;as=script
+Link: </preload/resources/dummy.css>;rel=preload;as=style
+Link: </preload/resources/square.png>;rel=preload;as=image
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/onerror_event-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/preload/onerror_event-expected.txt
new file mode 100644
index 0000000..2e89d50
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/onerror_event-expected.txt
@@ -0,0 +1,7 @@
+CONSOLE WARNING: line 23: <link rel=preload> must have a valid `as` value
+CONSOLE WARNING: line 24: <link rel=preload> must have a valid `as` value
+CONSOLE WARNING: line 26: <link rel=preload> must have a valid `as` value
+This is a testharness.js-based test.
+FAIL Makes sure that preloaded resources trigger the onerror event assert_true: style triggered error event expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/onerror_event.html b/third_party/WebKit/LayoutTests/imported/wpt/preload/onerror_event.html
new file mode 100644
index 0000000..cb2d9ff
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/onerror_event.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    var t = async_test('Makes sure that preloaded resources trigger the onerror event');
+    var scriptFailed = false;
+    var styleFailed = false;
+    var imageFailed = false;
+    var fontFailed = false;
+    var videoFailed = false;
+    var audioFailed = false;
+    var trackFailed = false;
+    var gibberishFailed = false;
+    var noTypeFailed = false;
+</script>
+<link rel=preload href="non-existent/dummy.js" as=script onerror="scriptFailed = true;">
+<link rel=preload href="non-existent/dummy.css" as=style onerror="styleFailed = true;">
+<link rel=preload href="non-existent/square.png" as=image onerror="imageFailed = true;">
+<link rel=preload href="non-existent/Ahem.ttf" as=font crossorigin onerror="fontFailed = true;">
+<link rel=preload href="non-existent/test.mp4" as=video onerror="videoFailed = true;">
+<link rel=preload href="non-existent/test.oga" as=audio onerror="audioFailed = true;">
+<link rel=preload href="non-existent/security/captions.vtt" as=media onerror="trackFailed = true;">
+<link rel=preload href="non-existent/dummy.xml" as=foobarxmlthing onerror="gibberishFailed = true;">
+<link rel=preload href="non-existent/dummy.xml" onerror="noTypeFailed = true;">
+<script src="resources/dummy.js?pipe=trickle(d5)"></script>
+<script>
+    window.onload = t.step_func(function() {
+        assert_true(styleFailed, "style triggered error event");
+        assert_true(scriptFailed, "script triggered error event");
+        assert_true(imageFailed, "image triggered error event");
+        assert_true(fontFailed, "font triggered error event");
+        assert_true(videoFailed, "video triggered error event");
+        assert_true(audioFailed, "audio triggered error event");
+        assert_true(trackFailed, "track triggered error event");
+        assert_true(gibberishFailed, "gibberish as value triggered error event");
+        assert_true(noTypeFailed, "empty as triggered error event");
+        t.done();
+    });
+</script>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/onload_event-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/preload/onload_event-expected.txt
new file mode 100644
index 0000000..16ff81f3
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/onload_event-expected.txt
@@ -0,0 +1,5 @@
+CONSOLE WARNING: line 24: <link rel=preload> must have a valid `as` value
+This is a testharness.js-based test.
+FAIL Makes sure that preloaded resources trigger the onload event assert_true: style triggered load event expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/onload_event.html b/third_party/WebKit/LayoutTests/imported/wpt/preload/onload_event.html
new file mode 100644
index 0000000..2f19cc2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/onload_event.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    var t = async_test('Makes sure that preloaded resources trigger the onload event');
+    var scriptLoaded = false;
+    var styleLoaded = false;
+    var imageLoaded = false;
+    var fontLoaded = false;
+    var videoLoaded = false;
+    var audioLoaded = false;
+    var trackLoaded = false;
+    var gibberishLoaded = false;
+    var gibberishErrored = false;
+    var noTypeLoaded = false;
+</script>
+<link rel=preload href="resources/dummy.js" as=script onload="scriptLoaded = true;">
+<link rel=preload href="resources/dummy.css" as=style onload="styleLoaded = true;">
+<link rel=preload href="resources/square.png" as=image onload="imageLoaded = true;">
+<link rel=preload href="/fonts/CanvasTest.ttf" as=font crossorigin onload="fontLoaded = true;">
+<link rel=preload href="/media/white.mp4" as=media onload="videoLoaded = true;">
+<link rel=preload href="/media/sound_5.oga" as=media onload="audioLoaded = true;">
+<link rel=preload href="/media/foo.vtt" as=media onload="trackLoaded = true;">
+<link rel=preload href="resources/dummy.xml?foo=bar" as=foobarxmlthing onload="gibberishLoaded = true;" onerror="gibberishErrored = true;">
+<link rel=preload href="resources/dummy.xml" onload="noTypeLoaded = true;">
+<body>
+<script src="resources/dummy.js?pipe=trickle(d5)"></script>
+<script>
+    window.onload = t.step_func(function() {
+        assert_true(styleLoaded, "style triggered load event");
+        assert_true(scriptLoaded, "script triggered load event");
+        assert_true(imageLoaded, "image triggered load event");
+        assert_true(fontLoaded, "font triggered load event");
+        assert_true(videoLoaded, "video triggered load event");
+        assert_true(audioLoaded, "audio triggered load event");
+        assert_true(trackLoaded, "track triggered load event");
+        assert_false(gibberishLoaded, "gibberish as value triggered load event");
+        assert_true(gibberishErrored, "gibberish as value triggered error event");
+        assert_true(noTypeLoaded, "empty as triggered load event");
+        t.done();
+    });
+</script>
+</body>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/preload-csp.sub.html b/third_party/WebKit/LayoutTests/imported/wpt/preload/preload-csp.sub.html
new file mode 100644
index 0000000..3f3b42e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/preload-csp.sub.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'; font-src 'none'; style-src 'none'; img-src 'none'; media-src 'none'; connect-src 'none'">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    var t = async_test('Makes sure that preload requests respect CSP');
+</script>
+<link rel=preload href="{{host}}:{{ports[http][1]}}/preload/resources/dummy.js" as=style>
+<link rel=preload href="resources/dummy.css" as=style>
+<link rel=preload href="resources/square.png" as=image>
+<link rel=preload href="/fonts/CanvasTest.ttf" as=font crossorigin>
+<link rel=preload href="/media/white.mp4" as=media>
+<link rel=preload href="/media/sound_5.oga" as=media>
+<link rel=preload href="/media/foo.vtt" as=media>
+<link rel=preload href="resources/dummy.xml?foo=bar" as=foobarxmlthing>
+<link rel=preload href="resources/dummy.xml">
+<body>
+<script src="resources/dummy.js?pipe=trickle(d5)"></script>
+<script>
+    var assertRequestTerminated = function(url) {
+        var entries = performance.getEntriesByName(new URL(url, location.href).href);
+        assert_equals(entries.length, 0);
+    };
+    window.onload = t.step_func(function() {
+        assertRequestTerminated("{{host}}:{{ports[http][1]}}/preload/resources/dummy.js");
+        assertRequestTerminated("resources/dummy.css");
+        assertRequestTerminated("resources/square.png");
+        assertRequestTerminated("/fonts/CanvasTest.ttf");
+        assertRequestTerminated("/media/white.mp4");
+        assertRequestTerminated("/media/sound_5.oga");
+        assertRequestTerminated("/media/foo.vtt");
+        assertRequestTerminated("resources/dummy.xml");
+        t.done();
+    });
+</script>
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/preload-default-csp.sub.html b/third_party/WebKit/LayoutTests/imported/wpt/preload/preload-default-csp.sub.html
new file mode 100644
index 0000000..76c1b53
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/preload-default-csp.sub.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'; default-src 'none'">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    var t = async_test('Makes sure that preload requests respect CSP');
+</script>
+<link rel=preload href="{{host}}:{{ports[http][1]}}/preload/resources/dummy.js" as=style>
+<link rel=preload href="resources/dummy.css" as=style>
+<link rel=preload href="resources/square.png" as=image>
+<link rel=preload href="/fonts/CanvasTest.ttf" as=font crossorigin>
+<link rel=preload href="/media/white.mp4" as=media>
+<link rel=preload href="/media/sound_5.oga" as=media>
+<link rel=preload href="/media/foo.vtt" as=media>
+<link rel=preload href="resources/dummy.xml?foo=bar" as=foobarxmlthing>
+<link rel=preload href="resources/dummy.xml">
+<body>
+<script src="resources/dummy.js?pipe=trickle(d5)"></script>
+<script>
+    var assertRequestTerminated = function(url) {
+        var entries = performance.getEntriesByName(new URL(url, location.href).href);
+        assert_equals(entries.length, 0);
+    };
+    window.onload = t.step_func(function() {
+        assertRequestTerminated("{{host}}:{{ports[http][1]}}/preload/resources/dummy.js");
+        assertRequestTerminated("resources/dummy.css");
+        assertRequestTerminated("resources/square.png");
+        assertRequestTerminated("/fonts/CanvasTest.ttf");
+        assertRequestTerminated("/media/white.mp4");
+        assertRequestTerminated("/media/sound_5.oga");
+        assertRequestTerminated("/media/foo.vtt");
+        assertRequestTerminated("resources/dummy.xml");
+        t.done();
+    });
+</script>
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/preload_with_type-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/preload/preload_with_type-expected.txt
new file mode 100644
index 0000000..6cef0b3
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/preload_with_type-expected.txt
@@ -0,0 +1,12 @@
+CONSOLE WARNING: line 38: <link rel=preload> has an unsupported `type` value
+CONSOLE WARNING: line 39: <link rel=preload> has an unsupported `type` value
+CONSOLE WARNING: line 40: <link rel=preload> has an unsupported `type` value
+CONSOLE WARNING: line 41: <link rel=preload> has an unsupported `type` value
+CONSOLE WARNING: line 42: <link rel=preload> has an unsupported `type` value
+CONSOLE WARNING: line 44: <link rel=preload> has an unsupported `type` value
+CONSOLE WARNING: line 45: <link rel=preload> has an unsupported `type` value
+CONSOLE WARNING: line 47: <link rel=preload> has an unsupported `type` value
+This is a testharness.js-based test.
+FAIL Makes sure that preloaded resources with a type attribute trigger the onload event assert_true: style triggered load event expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/preload_with_type.html b/third_party/WebKit/LayoutTests/imported/wpt/preload/preload_with_type.html
new file mode 100644
index 0000000..1678258
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/preload_with_type.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/media.js"></script>
+<script>
+    var t = async_test('Makes sure that preloaded resources with a type attribute trigger the onload event');
+    var scriptLoaded = false;
+    var styleLoaded = false;
+    var imageLoaded = false;
+    var fontLoaded = false;
+    var videoLoaded = false;
+    var audioLoaded = false;
+    var trackLoaded = false;
+    var gibberishLoaded = 0;
+    var getFormat = function(url) {
+        var dot = url.lastIndexOf('.');
+        if (dot != -1) {
+            var extension = url.substring(dot + 1);
+            if (extension.startsWith("og"))
+                return "ogg";
+            return extension;
+        }
+        return null;
+    };
+    var videoURL = getVideoURI("/media/A4");
+    var audioURL = getAudioURI("/media/sound_5");
+    var videoFormat = getFormat(videoURL);
+    var audioFormat = getFormat(audioURL);
+</script>
+<link rel=preload href="resources/dummy.js" as=script type="text/javascript" onload="scriptLoaded = true;">
+<link rel=preload href="resources/dummy.css" as=style type="text/css" onload="styleLoaded = true;">
+<link rel=preload href="resources/square.png" as=image type="image/png" onload="imageLoaded = true;">
+<link rel=preload href="/fonts/CanvasTest.ttf" as=font type="font/ttf" crossorigin onload="fontLoaded = true;">
+<script>
+    document.write('<link rel=preload href="' + videoURL + '" as=media type="video/' + videoFormat + '" onload="videoLoaded = true;">');
+    document.write('<link rel=preload href="' + audioURL + '" as=media type="audio/' + audioFormat + '" onload="audioLoaded = true;">');
+</script>
+<link rel=preload href="/media/foo.vtt" as=media type="text/vtt" onload="trackLoaded = true;">
+<link rel=preload href="resources/dummy.js" as=script type="application/foobar" onload="gibberishLoaded++;">
+<link rel=preload href="resources/dummy.css" as=style type="text/foobar" onload="gibberishLoaded++;">
+<link rel=preload href="resources/square.png" as=image type="image/foobar" onload="gibberishLoaded++;">
+<link rel=preload href="/fonts/CanvasTest.ttf" as=font type="font/foobar" crossorigin onload="gibberishLoaded++;">
+<script>
+    document.write('<link rel=preload href="' + videoURL + '" as=media type="video/foobar" onload="gibberishLoaded++;">');
+    document.write('<link rel=preload href="' + audioURL + '" as=media type="audio/foobar" onload="gibberishLoaded++;">');
+</script>
+<link rel=preload href="/media/foo.vtt" as=media type="text/foobar" onload="gibberishLoaded++;">
+<body>
+<script src="resources/dummy.js?pipe=trickle(d5)"></script>
+<script>
+    window.onload = t.step_func(function() {
+        assert_true(styleLoaded, "style triggered load event");
+        assert_true(scriptLoaded, "script triggered load event");
+        assert_true(imageLoaded, "image triggered load event");
+        assert_true(fontLoaded, "font triggered load event");
+        assert_true(videoLoaded, "video triggered load event");
+        assert_true(audioLoaded, "audio triggered load event");
+        assert_true(trackLoaded, "track triggered load event");
+        assert_equals(gibberishLoaded, 0, "resources with gibberish type should not be loaded");
+        t.done();
+    });
+</script>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/resources/dummy.css b/third_party/WebKit/LayoutTests/imported/wpt/preload/resources/dummy.css
new file mode 100644
index 0000000..5097166a0
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/resources/dummy.css
@@ -0,0 +1 @@
+/* This is just a dummy, empty CSS file */
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/resources/dummy.js b/third_party/WebKit/LayoutTests/imported/wpt/preload/resources/dummy.js
new file mode 100644
index 0000000..cfcb9d8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/resources/dummy.js
@@ -0,0 +1 @@
+// This is example JS content. Nothing to see here.
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/resources/dummy.xml b/third_party/WebKit/LayoutTests/imported/wpt/preload/resources/dummy.xml
new file mode 100644
index 0000000..0d88d0c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/resources/dummy.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>Text.me</root>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/resources/square.png b/third_party/WebKit/LayoutTests/imported/wpt/preload/resources/square.png
new file mode 100644
index 0000000..01c9666a
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/resources/square.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/single_download_late_used_preload-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/preload/single_download_late_used_preload-expected.txt
new file mode 100644
index 0000000..7f916fd51
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/single_download_late_used_preload-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Makes sure that preloaded resources are not downloaded again when used assert_equals: expected 3 but got 0
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/single_download_late_used_preload.html b/third_party/WebKit/LayoutTests/imported/wpt/preload/single_download_late_used_preload.html
new file mode 100644
index 0000000..64e7085
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/single_download_late_used_preload.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    var t = async_test('Makes sure that preloaded resources are not downloaded again when used');
+</script>
+<div id=result></div>
+<link rel=preload href="resources/square.png" as=image>
+<script>
+    window.addEventListener("load", t.step_func(function() {
+        t.step_timeout(function() {
+            document.getElementById("result").innerHTML = "<image src='resources/square.png'>";
+            t.step_timeout(function() {
+                assert_equals(performance.getEntriesByType("resource").length, 3);
+                t.done();
+            }, 1000);
+        }, 1000);
+    }));
+</script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/single_download_preload-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/preload/single_download_preload-expected.txt
new file mode 100644
index 0000000..b1dcffab
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/single_download_preload-expected.txt
@@ -0,0 +1,5 @@
+CONSOLE WARNING: line 15: <link rel=preload> must have a valid `as` value
+This is a testharness.js-based test.
+FAIL Makes sure that preloaded resources are not downloaded again when used assert_equals: expected 14 but got 0
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/preload/single_download_preload.html b/third_party/WebKit/LayoutTests/imported/wpt/preload/single_download_preload.html
new file mode 100644
index 0000000..0c20194
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/preload/single_download_preload.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+    var t = async_test('Makes sure that preloaded resources are not downloaded again when used');
+</script>
+<link rel=preload href="resources/dummy.js" as=script>
+<link rel=preload href="resources/dummy.css" as=style>
+<link rel=preload href="resources/square.png" as=image>
+<link rel=preload href="resources/square.png?background" as=image>
+<link rel=preload href="/fonts/CanvasTest.ttf" as=font crossorigin>
+<link rel=preload href="/media/white.mp4" as=media>
+<link rel=preload href="/media/sound_5.oga" as=media>
+<link rel=preload href="/media/foo.vtt" as=media>
+<link rel=preload href="resources/dummy.xml?foo=bar" as=foobarxmlthing>
+<link rel=preload href="resources/dummy.xml">
+<body>
+<script src="resources/dummy.js?pipe=trickle(d5)"></script>
+<style>
+    #background {
+        width: 200px;
+        height: 200px;
+        background-image: url(resources/square.png?background);
+    }
+    @font-face {
+      font-family:ahem;
+      src: url(/fonts/CanvasTest.ttf);
+    }
+    span { font-family: ahem, Arial; }
+</style>
+<link rel="stylesheet" href="resources/dummy.css">
+<script src="resources/dummy.js"></script>
+<div id="background"></div>
+<img src="resources/square.png">
+<video src="/media/white.mp4">
+    <track kind=subtitles src="/media/foo.vtt" srclang=en>
+</video>
+<audio src="/media/sound_5.oga"></audio>
+<script>
+    var xhr = new XMLHttpRequest();
+    xhr.open("GET", "resources/dummy.xml");
+    xhr.send();
+
+    window.addEventListener("load", t.step_func(function() {
+        // Audio and video show 2 extra requests as the main request is followed by a range request
+        assert_equals(performance.getEntriesByType("resource").length, 14);
+        t.done();
+    }));
+</script>
+<span>PASS - this text is here just so that the browser will download the font.</span>
diff --git a/third_party/WebKit/LayoutTests/sensor/resources/generic-sensor-tests.js b/third_party/WebKit/LayoutTests/sensor/resources/generic-sensor-tests.js
index 52d878d..ee5f312 100644
--- a/third_party/WebKit/LayoutTests/sensor/resources/generic-sensor-tests.js
+++ b/third_party/WebKit/LayoutTests/sensor/resources/generic-sensor-tests.js
@@ -304,10 +304,7 @@
                 // By the moment slow sensor (9 Hz) is notified for the
                 // next time, the fast sensor (30 Hz) has been notified
                 // for int(30/9) = 3 times.
-                // In actual implementation updates are bound to rAF,
-                // (not to a timer) sometimes fast sensor gets invoked 4 times.
-                assert_true(fastSensorNotifiedCounter == 3 ||
-                            fastSensorNotifiedCounter == 4);
+                assert_equals(fastSensorNotifiedCounter, 3);
                 fastSensor.stop();
                 slowSensor.stop();
                 resolve(mockSensor);
diff --git a/third_party/WebKit/Source/core/dom/StyleEngine.cpp b/third_party/WebKit/Source/core/dom/StyleEngine.cpp
index 63ed121d..045bf79 100644
--- a/third_party/WebKit/Source/core/dom/StyleEngine.cpp
+++ b/third_party/WebKit/Source/core/dom/StyleEngine.cpp
@@ -460,6 +460,7 @@
   clearResolver();
   m_viewportResolver = nullptr;
   m_mediaQueryEvaluator = nullptr;
+  clearFontCache();
 }
 
 void StyleEngine::clearFontCache() {
diff --git a/third_party/WebKit/Source/core/frame/EventHandlerRegistry.cpp b/third_party/WebKit/Source/core/frame/EventHandlerRegistry.cpp
index a9ae3ce..407f538 100644
--- a/third_party/WebKit/Source/core/frame/EventHandlerRegistry.cpp
+++ b/third_party/WebKit/Source/core/frame/EventHandlerRegistry.cpp
@@ -191,15 +191,8 @@
                                                     FrameHost* oldFrameHost,
                                                     FrameHost* newFrameHost) {
   ASSERT(newFrameHost != oldFrameHost);
-  for (size_t i = 0; i < EventHandlerClassCount; ++i) {
-    EventHandlerClass handlerClass = static_cast<EventHandlerClass>(i);
-    const EventTargetSet* targets =
-        &oldFrameHost->eventHandlerRegistry().m_targets[handlerClass];
-    for (unsigned count = targets->count(&target); count > 0; --count)
-      newFrameHost->eventHandlerRegistry().didAddEventHandler(target,
-                                                              handlerClass);
-  }
-  oldFrameHost->eventHandlerRegistry().didRemoveAllEventHandlers(target);
+  oldFrameHost->eventHandlerRegistry().didMoveOutOfFrameHost(target);
+  newFrameHost->eventHandlerRegistry().didMoveIntoFrameHost(target);
 }
 
 void EventHandlerRegistry::didRemoveAllEventHandlers(EventTarget& target) {
diff --git a/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp b/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp
index f2371c9..cd50a3b 100644
--- a/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp
@@ -1488,18 +1488,17 @@
   }
 
   if (error == WebMediaPlayer::NetworkStateNetworkError &&
-      m_readyState >= kHaveMetadata)
+      m_readyState >= kHaveMetadata) {
     mediaEngineError(MediaError::create(MediaError::kMediaErrNetwork));
-  else if (error == WebMediaPlayer::NetworkStateDecodeError)
+  } else if (error == WebMediaPlayer::NetworkStateDecodeError) {
     mediaEngineError(MediaError::create(MediaError::kMediaErrDecode));
-  else if ((error == WebMediaPlayer::NetworkStateFormatError ||
-            error == WebMediaPlayer::NetworkStateNetworkError) &&
-           m_loadState == LoadingFromSrcAttr)
+  } else if ((error == WebMediaPlayer::NetworkStateFormatError ||
+              error == WebMediaPlayer::NetworkStateNetworkError) &&
+             m_loadState == LoadingFromSrcAttr) {
     noneSupported();
+  }
 
   updateDisplayState();
-  if (mediaControls())
-    mediaControls()->reset();
 }
 
 void HTMLMediaElement::setNetworkState(WebMediaPlayer::NetworkState state) {
@@ -1658,8 +1657,6 @@
       jumped = true;
     }
 
-    if (mediaControls())
-      mediaControls()->reset();
     if (layoutObject())
       layoutObject()->updateFromElement();
   }
@@ -3852,11 +3849,12 @@
 }
 
 void HTMLMediaElement::setNetworkState(NetworkState state) {
-  if (m_networkState != state) {
-    m_networkState = state;
-    if (MediaControls* controls = mediaControls())
-      controls->networkStateChanged();
-  }
+  if (m_networkState == state)
+    return;
+
+  m_networkState = state;
+  if (mediaControls())
+    mediaControls()->networkStateChanged();
 }
 
 void HTMLMediaElement::videoWillBeDrawnToCanvas() const {
diff --git a/third_party/WebKit/Source/core/html/shadow/MediaControls.cpp b/third_party/WebKit/Source/core/html/shadow/MediaControls.cpp
index ded935a..bd03224 100644
--- a/third_party/WebKit/Source/core/html/shadow/MediaControls.cpp
+++ b/third_party/WebKit/Source/core/html/shadow/MediaControls.cpp
@@ -748,6 +748,18 @@
   m_toggleClosedCaptionsButton->updateDisplayType();
 }
 
+void MediaControls::onError() {
+  // TODO(mlamouri): we should only change the aspects of the control that need
+  // to be changed.
+  reset();
+}
+
+void MediaControls::onLoadedMetadata() {
+  // TODO(mlamouri): we should only change the aspects of the control that need
+  // to be changed.
+  reset();
+}
+
 void MediaControls::notifyPanelWidthChanged(const LayoutUnit& newWidth) {
   // Don't bother to do any work if this matches the most recent panel
   // width, since we're called after layout.
diff --git a/third_party/WebKit/Source/core/html/shadow/MediaControls.h b/third_party/WebKit/Source/core/html/shadow/MediaControls.h
index 636e41a7..5112210 100644
--- a/third_party/WebKit/Source/core/html/shadow/MediaControls.h
+++ b/third_party/WebKit/Source/core/html/shadow/MediaControls.h
@@ -160,6 +160,8 @@
   void onPause();
   void onTextTracksAddedOrRemoved();
   void onTextTracksChanged();
+  void onError();
+  void onLoadedMetadata();
 
   Member<HTMLMediaElement> m_mediaElement;
 
diff --git a/third_party/WebKit/Source/core/html/shadow/MediaControlsMediaEventListener.cpp b/third_party/WebKit/Source/core/html/shadow/MediaControlsMediaEventListener.cpp
index d440d05..b2a71ec3 100644
--- a/third_party/WebKit/Source/core/html/shadow/MediaControlsMediaEventListener.cpp
+++ b/third_party/WebKit/Source/core/html/shadow/MediaControlsMediaEventListener.cpp
@@ -26,6 +26,10 @@
                                                     false);
   m_mediaControls->m_mediaElement->addEventListener(
       EventTypeNames::durationchange, this, false);
+  m_mediaControls->m_mediaElement->addEventListener(EventTypeNames::error, this,
+                                                    false);
+  m_mediaControls->m_mediaElement->addEventListener(
+      EventTypeNames::loadedmetadata, this, false);
 
   // TextTracks events.
   TextTrackList* textTracks = m_mediaControls->m_mediaElement->textTracks();
@@ -66,6 +70,14 @@
     m_mediaControls->onPause();
     return;
   }
+  if (event->type() == EventTypeNames::error) {
+    m_mediaControls->onError();
+    return;
+  }
+  if (event->type() == EventTypeNames::loadedmetadata) {
+    m_mediaControls->onLoadedMetadata();
+    return;
+  }
 
   // TextTracks events.
   if (event->type() == EventTypeNames::addtrack ||
diff --git a/third_party/WebKit/Source/modules/sensor/BUILD.gn b/third_party/WebKit/Source/modules/sensor/BUILD.gn
index 6f59d028..8bc2ebb 100644
--- a/third_party/WebKit/Source/modules/sensor/BUILD.gn
+++ b/third_party/WebKit/Source/modules/sensor/BUILD.gn
@@ -32,8 +32,8 @@
     "SensorProxy.h",
     "SensorReading.cpp",
     "SensorReading.h",
-    "SensorReadingUpdater.cpp",
-    "SensorReadingUpdater.h",
+    "SensorUpdateNotificationStrategy.cpp",
+    "SensorUpdateNotificationStrategy.h",
   ]
 
   deps = [
diff --git a/third_party/WebKit/Source/modules/sensor/Sensor.cpp b/third_party/WebKit/Source/modules/sensor/Sensor.cpp
index 5f83ae8..62e2a5c 100644
--- a/third_party/WebKit/Source/modules/sensor/Sensor.cpp
+++ b/third_party/WebKit/Source/modules/sensor/Sensor.cpp
@@ -13,6 +13,7 @@
 #include "modules/sensor/SensorErrorEvent.h"
 #include "modules/sensor/SensorProviderProxy.h"
 #include "modules/sensor/SensorReading.h"
+#include "modules/sensor/SensorUpdateNotificationStrategy.h"
 
 using namespace device::mojom::blink;
 
@@ -25,8 +26,7 @@
     : ContextLifecycleObserver(executionContext),
       m_sensorOptions(sensorOptions),
       m_type(type),
-      m_state(Sensor::SensorState::Idle),
-      m_lastUpdateTimestamp(0.0) {
+      m_state(Sensor::SensorState::Idle) {
   // Check secure context.
   String errorMessage;
   if (!executionContext->isSecureContext(errorMessage)) {
@@ -164,7 +164,7 @@
   m_sensorProxy = provider->getSensorProxy(m_type);
 
   if (!m_sensorProxy) {
-    m_sensorProxy = provider->createSensorProxy(m_type, document,
+    m_sensorProxy = provider->createSensorProxy(m_type, document->page(),
                                                 createSensorReadingFactory());
   }
 }
@@ -182,15 +182,10 @@
   startListening();
 }
 
-void Sensor::onSensorReadingChanged(double timestamp) {
-  if (m_state != Sensor::SensorState::Activated)
-    return;
-
-  DCHECK_GT(m_configuration->frequency, 0.0);
-  double period = 1 / m_configuration->frequency;
-  if (timestamp - m_lastUpdateTimestamp >= period) {
-    m_lastUpdateTimestamp = timestamp;
-    notifySensorReadingChanged();
+void Sensor::onSensorReadingChanged() {
+  if (m_state == Sensor::SensorState::Activated) {
+    DCHECK(m_sensorUpdateNotifier);
+    m_sensorUpdateNotifier->onSensorReadingChanged();
   }
 }
 
@@ -198,6 +193,8 @@
                            const String& sanitizedMessage,
                            const String& unsanitizedMessage) {
   reportError(code, sanitizedMessage, unsanitizedMessage);
+  if (m_sensorUpdateNotifier)
+    m_sensorUpdateNotifier->cancelPendingNotifications();
 }
 
 void Sensor::onStartRequestCompleted(bool result) {
@@ -211,6 +208,13 @@
     return;
   }
 
+  DCHECK(m_configuration);
+  DCHECK(m_sensorProxy);
+  auto updateCallback =
+      WTF::bind(&Sensor::onSensorUpdateNotification, wrapWeakPersistent(this));
+  DCHECK_GT(m_configuration->frequency, 0);
+  m_sensorUpdateNotifier = SensorUpdateNotificationStrategy::create(
+      m_configuration->frequency, std::move(updateCallback));
   updateState(Sensor::SensorState::Activated);
 }
 
@@ -241,6 +245,9 @@
   DCHECK(m_sensorProxy);
   updateState(Sensor::SensorState::Idle);
 
+  if (m_sensorUpdateNotifier)
+    m_sensorUpdateNotifier->cancelPendingNotifications();
+
   if (m_sensorProxy->isInitialized()) {
     DCHECK(m_configuration);
     m_sensorProxy->removeConfiguration(m_configuration->Clone());
@@ -248,6 +255,25 @@
   m_sensorProxy->removeObserver(this);
 }
 
+void Sensor::onSensorUpdateNotification() {
+  if (m_state != Sensor::SensorState::Activated)
+    return;
+
+  DCHECK(m_sensorProxy);
+  DCHECK(m_sensorProxy->isInitialized());
+  DCHECK(m_sensorProxy->sensorReading());
+
+  if (getExecutionContext() &&
+      m_sensorProxy->sensorReading()->isReadingUpdated(m_storedData)) {
+    getExecutionContext()->postTask(
+        TaskType::Sensor, BLINK_FROM_HERE,
+        createSameThreadTask(&Sensor::notifySensorReadingChanged,
+                             wrapWeakPersistent(this)));
+  }
+
+  m_storedData = m_sensorProxy->sensorReading()->data();
+}
+
 void Sensor::updateState(Sensor::SensorState newState) {
   if (newState == m_state)
     return;
@@ -277,14 +303,13 @@
   }
 }
 
-void Sensor::notifySensorReadingChanged() {
-  DCHECK(m_sensorProxy);
-  DCHECK(m_sensorProxy->sensorReading());
+void Sensor::onSuspended() {
+  if (m_sensorUpdateNotifier)
+    m_sensorUpdateNotifier->cancelPendingNotifications();
+}
 
-  if (m_sensorProxy->sensorReading()->isReadingUpdated(m_storedData)) {
-    m_storedData = m_sensorProxy->sensorReading()->data();
-    dispatchEvent(Event::create(EventTypeNames::change));
-  }
+void Sensor::notifySensorReadingChanged() {
+  dispatchEvent(Event::create(EventTypeNames::change));
 }
 
 void Sensor::notifyOnActivate() {
diff --git a/third_party/WebKit/Source/modules/sensor/Sensor.h b/third_party/WebKit/Source/modules/sensor/Sensor.h
index b599797..1e2e7ef 100644
--- a/third_party/WebKit/Source/modules/sensor/Sensor.h
+++ b/third_party/WebKit/Source/modules/sensor/Sensor.h
@@ -20,6 +20,7 @@
 class ExceptionState;
 class ExecutionContext;
 class SensorReading;
+class SensorUpdateNotificationStrategy;
 
 class Sensor : public EventTargetWithInlineData,
                public ActiveScriptWrappable<Sensor>,
@@ -82,10 +83,11 @@
 
   // SensorController::Observer overrides.
   void onSensorInitialized() override;
-  void onSensorReadingChanged(double timestamp) override;
+  void onSensorReadingChanged() override;
   void onSensorError(ExceptionCode,
                      const String& sanitizedMessage,
                      const String& unsanitizedMessage) override;
+  void onSuspended() override;
 
   void onStartRequestCompleted(bool);
   void onStopRequestCompleted(bool);
@@ -93,6 +95,8 @@
   void startListening();
   void stopListening();
 
+  void onSensorUpdateNotification();
+
   void updateState(SensorState newState);
   void reportError(ExceptionCode = UnknownError,
                    const String& sanitizedMessage = String(),
@@ -107,9 +111,9 @@
   device::mojom::blink::SensorType m_type;
   SensorState m_state;
   Member<SensorProxy> m_sensorProxy;
+  std::unique_ptr<SensorUpdateNotificationStrategy> m_sensorUpdateNotifier;
   device::SensorReading m_storedData;
   SensorConfigurationPtr m_configuration;
-  double m_lastUpdateTimestamp;
 };
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/modules/sensor/SensorProviderProxy.cpp b/third_party/WebKit/Source/modules/sensor/SensorProviderProxy.cpp
index 9dca7431..be951e0 100644
--- a/third_party/WebKit/Source/modules/sensor/SensorProviderProxy.cpp
+++ b/third_party/WebKit/Source/modules/sensor/SensorProviderProxy.cpp
@@ -55,12 +55,12 @@
 
 SensorProxy* SensorProviderProxy::createSensorProxy(
     device::mojom::blink::SensorType type,
-    Document* document,
+    Page* page,
     std::unique_ptr<SensorReadingFactory> readingFactory) {
   DCHECK(!getSensorProxy(type));
 
   SensorProxy* sensor =
-      new SensorProxy(type, this, document, std::move(readingFactory));
+      new SensorProxy(type, this, page, std::move(readingFactory));
   m_sensorProxies.add(sensor);
 
   return sensor;
diff --git a/third_party/WebKit/Source/modules/sensor/SensorProviderProxy.h b/third_party/WebKit/Source/modules/sensor/SensorProviderProxy.h
index 6cb2293a..e9c7701 100644
--- a/third_party/WebKit/Source/modules/sensor/SensorProviderProxy.h
+++ b/third_party/WebKit/Source/modules/sensor/SensorProviderProxy.h
@@ -13,7 +13,7 @@
 
 namespace blink {
 
-class Document;
+class Page;
 class SensorProxy;
 class SensorReadingFactory;
 
@@ -31,7 +31,7 @@
   ~SensorProviderProxy();
 
   SensorProxy* createSensorProxy(device::mojom::blink::SensorType,
-                                 Document*,
+                                 Page*,
                                  std::unique_ptr<SensorReadingFactory>);
 
   SensorProxy* getSensorProxy(device::mojom::blink::SensorType);
diff --git a/third_party/WebKit/Source/modules/sensor/SensorProxy.cpp b/third_party/WebKit/Source/modules/sensor/SensorProxy.cpp
index 1d3e991f..44df768 100644
--- a/third_party/WebKit/Source/modules/sensor/SensorProxy.cpp
+++ b/third_party/WebKit/Source/modules/sensor/SensorProxy.cpp
@@ -4,11 +4,9 @@
 
 #include "modules/sensor/SensorProxy.h"
 
-#include "core/dom/Document.h"
 #include "core/frame/LocalFrame.h"
 #include "modules/sensor/SensorProviderProxy.h"
 #include "modules/sensor/SensorReading.h"
-#include "modules/sensor/SensorReadingUpdater.h"
 #include "platform/mojo/MojoHelper.h"
 #include "public/platform/Platform.h"
 
@@ -18,18 +16,18 @@
 
 SensorProxy::SensorProxy(SensorType sensorType,
                          SensorProviderProxy* provider,
-                         Document* document,
+                         Page* page,
                          std::unique_ptr<SensorReadingFactory> readingFactory)
-    : PageVisibilityObserver(document->page()),
+    : PageVisibilityObserver(page),
       m_type(sensorType),
       m_mode(ReportingMode::CONTINUOUS),
       m_provider(provider),
       m_clientBinding(this),
       m_state(SensorProxy::Uninitialized),
       m_suspended(false),
-      m_document(document),
       m_readingFactory(std::move(readingFactory)),
-      m_maximumFrequency(0.0) {}
+      m_maximumFrequency(0.0),
+      m_timer(this, &SensorProxy::onTimerFired) {}
 
 SensorProxy::~SensorProxy() {}
 
@@ -38,8 +36,6 @@
 }
 
 DEFINE_TRACE(SensorProxy) {
-  visitor->trace(m_document);
-  visitor->trace(m_readingUpdater);
   visitor->trace(m_reading);
   visitor->trace(m_observers);
   visitor->trace(m_provider);
@@ -71,10 +67,6 @@
                                              callback);
 }
 
-bool SensorProxy::isActive() const {
-  return isInitialized() && !m_suspended && !m_frequenciesUsed.isEmpty();
-}
-
 void SensorProxy::addConfiguration(
     SensorConfigurationPtr configuration,
     std::unique_ptr<Function<void(bool)>> callback) {
@@ -101,6 +93,12 @@
 
   m_sensor->Suspend();
   m_suspended = true;
+
+  if (usesPollingTimer())
+    updatePollingStatus();
+
+  for (Observer* observer : m_observers)
+    observer->onSuspended();
 }
 
 void SensorProxy::resume() {
@@ -111,8 +109,8 @@
   m_sensor->Resume();
   m_suspended = false;
 
-  if (isActive())
-    m_readingUpdater->start();
+  if (usesPollingTimer())
+    updatePollingStatus();
 }
 
 const SensorConfiguration* SensorProxy::defaultConfig() const {
@@ -120,6 +118,10 @@
   return m_defaultConfig.get();
 }
 
+bool SensorProxy::usesPollingTimer() const {
+  return isInitialized() && (m_mode == ReportingMode::CONTINUOUS);
+}
+
 void SensorProxy::updateSensorReading() {
   DCHECK(isInitialized());
   DCHECK(m_readingFactory);
@@ -134,14 +136,9 @@
   }
 
   m_reading = m_readingFactory->createSensorReading(readingData);
-}
 
-void SensorProxy::notifySensorChanged(double timestamp) {
-  // This notification leads to sync 'onchange' event sending, so
-  // we must cache m_observers as it can be modified within event handlers.
-  auto copy = m_observers;
-  for (Observer* observer : copy)
-    observer->onSensorReadingChanged(timestamp);
+  for (Observer* observer : m_observers)
+    observer->onSensorReadingChanged();
 }
 
 void SensorProxy::RaiseError() {
@@ -150,8 +147,7 @@
 
 void SensorProxy::SensorReadingChanged() {
   DCHECK_EQ(ReportingMode::ON_CHANGE, m_mode);
-  if (isActive())
-    m_readingUpdater->start();
+  updateSensorReading();
 }
 
 void SensorProxy::pageVisibilityChanged() {
@@ -173,9 +169,12 @@
     return;
   }
 
-  m_state = Uninitialized;
-  m_frequenciesUsed.clear();
+  if (usesPollingTimer()) {  // Stop polling.
+    m_frequenciesUsed.clear();
+    updatePollingStatus();
+  }
 
+  m_state = Uninitialized;
   // The m_sensor.reset() will release all callbacks and its bound parameters,
   // therefore, handleSensorError accepts messages by value.
   m_sensor.reset();
@@ -229,10 +228,7 @@
   m_sensor.set_connection_error_handler(
       convertToBaseCallback(std::move(errorCallback)));
 
-  m_readingUpdater = SensorReadingUpdater::create(this, m_mode);
-
   m_state = Initialized;
-
   for (Observer* observer : m_observers)
     observer->onSensorInitialized();
 }
@@ -241,11 +237,9 @@
     double frequency,
     std::unique_ptr<Function<void(bool)>> callback,
     bool result) {
-  if (result) {
+  if (usesPollingTimer() && result) {
     m_frequenciesUsed.append(frequency);
-    std::sort(m_frequenciesUsed.begin(), m_frequenciesUsed.end());
-    if (isActive())
-      m_readingUpdater->start();
+    updatePollingStatus();
   }
 
   (*callback)(result);
@@ -256,6 +250,9 @@
   if (!result)
     DVLOG(1) << "Failure at sensor configuration removal";
 
+  if (!usesPollingTimer())
+    return;
+
   size_t index = m_frequenciesUsed.find(frequency);
   if (index == kNotFound) {
     // Could happen e.g. if 'handleSensorError' was called before.
@@ -263,6 +260,7 @@
   }
 
   m_frequenciesUsed.remove(index);
+  updatePollingStatus();
 }
 
 bool SensorProxy::tryReadFromBuffer(device::SensorReading& result) {
@@ -278,4 +276,28 @@
   return true;
 }
 
+void SensorProxy::updatePollingStatus() {
+  DCHECK(usesPollingTimer());
+
+  if (m_suspended || m_frequenciesUsed.isEmpty()) {
+    m_timer.stop();
+    return;
+  }
+  // TODO(Mikhail): Consider using sorted queue instead of searching
+  // max element each time.
+  auto it =
+      std::max_element(m_frequenciesUsed.begin(), m_frequenciesUsed.end());
+  DCHECK_GT(*it, 0.0);
+
+  double repeatInterval = 1 / *it;
+  if (!m_timer.isActive() || m_timer.repeatInterval() != repeatInterval) {
+    updateSensorReading();
+    m_timer.startRepeating(repeatInterval, BLINK_FROM_HERE);
+  }
+}
+
+void SensorProxy::onTimerFired(TimerBase*) {
+  updateSensorReading();
+}
+
 }  // namespace blink
diff --git a/third_party/WebKit/Source/modules/sensor/SensorProxy.h b/third_party/WebKit/Source/modules/sensor/SensorProxy.h
index 3787ef84..3243636 100644
--- a/third_party/WebKit/Source/modules/sensor/SensorProxy.h
+++ b/third_party/WebKit/Source/modules/sensor/SensorProxy.h
@@ -12,6 +12,7 @@
 #include "device/generic_sensor/public/interfaces/sensor_provider.mojom-blink.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "platform/Supplementable.h"
+#include "platform/Timer.h"
 #include "platform/heap/Handle.h"
 #include "wtf/Vector.h"
 
@@ -20,7 +21,6 @@
 class SensorProviderProxy;
 class SensorReading;
 class SensorReadingFactory;
-class SensorReadingUpdater;
 
 // This class wraps 'Sensor' mojo interface and used by multiple
 // JS sensor instances of the same type (within a single frame).
@@ -38,16 +38,13 @@
     // methods can be called.
     virtual void onSensorInitialized() {}
     // Platfrom sensort reading has changed.
-    // |timestamp| Reference timestamp in seconds of the moment when
-    // sensor reading was updated from the buffer.
-    // Note: |timestamp| values are only used to calculate elapsed time
-    // between shared buffer readings. These values *do not* correspond
-    // to sensor reading timestamps which are obtained on platform side.
-    virtual void onSensorReadingChanged(double timestamp) {}
+    virtual void onSensorReadingChanged() {}
     // An error has occurred.
     virtual void onSensorError(ExceptionCode,
                                const String& sanitizedMessage,
                                const String& unsanitizedMessage) {}
+    // Sensor reading change notification is suspended.
+    virtual void onSuspended() {}
   };
 
   ~SensorProxy();
@@ -62,10 +59,6 @@
   bool isInitializing() const { return m_state == Initializing; }
   bool isInitialized() const { return m_state == Initialized; }
 
-  // Is watching new reading data (initialized, not suspended and has
-  // configurations added).
-  bool isActive() const;
-
   void addConfiguration(device::mojom::blink::SensorConfigurationPtr,
                         std::unique_ptr<Function<void(bool)>>);
 
@@ -86,25 +79,20 @@
 
   double maximumFrequency() const { return m_maximumFrequency; }
 
-  Document* document() const { return m_document; }
-  const WTF::Vector<double>& frequenciesUsed() const {
-    return m_frequenciesUsed;
-  }
-
   DECLARE_VIRTUAL_TRACE();
 
  private:
   friend class SensorProviderProxy;
-  friend class SensorReadingUpdaterContinuous;
-  friend class SensorReadingUpdaterOnChange;
   SensorProxy(device::mojom::blink::SensorType,
               SensorProviderProxy*,
-              Document*,
+              Page*,
               std::unique_ptr<SensorReadingFactory>);
+  // Returns true if this instance is using polling timer to
+  // periodically fetch reading data from shared buffer.
+  bool usesPollingTimer() const;
 
   // Updates sensor reading from shared buffer.
   void updateSensorReading();
-  void notifySensorChanged(double timestamp);
 
   // device::mojom::blink::SensorClient overrides.
   void RaiseError() override;
@@ -128,7 +116,8 @@
   void onRemoveConfigurationCompleted(double frequency, bool result);
 
   bool tryReadFromBuffer(device::SensorReading& result);
-  void onAnimationFrame(double timestamp);
+  void updatePollingStatus();
+  void onTimerFired(TimerBase*);
 
   device::mojom::blink::SensorType m_type;
   device::mojom::blink::ReportingMode m_mode;
@@ -145,14 +134,13 @@
   mojo::ScopedSharedBufferHandle m_sharedBufferHandle;
   mojo::ScopedSharedBufferMapping m_sharedBuffer;
   bool m_suspended;
-  Member<Document> m_document;
   Member<SensorReading> m_reading;
   std::unique_ptr<SensorReadingFactory> m_readingFactory;
   double m_maximumFrequency;
 
-  Member<SensorReadingUpdater> m_readingUpdater;
+  // Used for continious reporting mode.
+  Timer<SensorProxy> m_timer;
   WTF::Vector<double> m_frequenciesUsed;
-  double m_lastRafTimestamp;
 
   using ReadingBuffer = device::SensorReadingSharedBuffer;
   static_assert(
diff --git a/third_party/WebKit/Source/modules/sensor/SensorReadingUpdater.cpp b/third_party/WebKit/Source/modules/sensor/SensorReadingUpdater.cpp
deleted file mode 100644
index 3d35f7da..0000000
--- a/third_party/WebKit/Source/modules/sensor/SensorReadingUpdater.cpp
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "modules/sensor/SensorReadingUpdater.h"
-
-#include "core/dom/Document.h"
-#include "device/generic_sensor/public/interfaces/sensor.mojom-blink.h"
-#include "modules/sensor/SensorProxy.h"
-#include "wtf/CurrentTime.h"
-
-using device::mojom::blink::ReportingMode;
-
-namespace blink {
-
-SensorReadingUpdater::SensorReadingUpdater(SensorProxy* sensorProxy)
-    : m_sensorProxy(sensorProxy), m_hasPendingAnimationFrameTask(false) {}
-
-void SensorReadingUpdater::enqueueAnimationFrameTask() {
-  if (m_hasPendingAnimationFrameTask)
-    return;
-
-  auto callback = WTF::bind(&SensorReadingUpdater::onAnimationFrame,
-                            wrapWeakPersistent(this));
-  m_sensorProxy->document()->enqueueAnimationFrameTask(std::move(callback));
-  m_hasPendingAnimationFrameTask = true;
-}
-
-void SensorReadingUpdater::start() {
-  enqueueAnimationFrameTask();
-}
-
-void SensorReadingUpdater::onAnimationFrame() {
-  m_hasPendingAnimationFrameTask = false;
-  onAnimationFrameInternal();
-}
-
-DEFINE_TRACE(SensorReadingUpdater) {
-  visitor->trace(m_sensorProxy);
-}
-
-class SensorReadingUpdaterContinuous : public SensorReadingUpdater {
- public:
-  explicit SensorReadingUpdaterContinuous(SensorProxy* sensorProxy)
-      : SensorReadingUpdater(sensorProxy) {}
-
-  DEFINE_INLINE_VIRTUAL_TRACE() { SensorReadingUpdater::trace(visitor); }
-
- protected:
-  void onAnimationFrameInternal() override {
-    if (!m_sensorProxy->isActive())
-      return;
-
-    m_sensorProxy->updateSensorReading();
-    m_sensorProxy->notifySensorChanged(WTF::monotonicallyIncreasingTime());
-    enqueueAnimationFrameTask();
-  }
-};
-
-// New data is fetched from shared buffer only once after 'start()'
-// call. Further, notification is send until every client is updated
-// (i.e. until longest notification period elapses) rAF stops after that.
-class SensorReadingUpdaterOnChange : public SensorReadingUpdater {
- public:
-  explicit SensorReadingUpdaterOnChange(SensorProxy* sensorProxy)
-      : SensorReadingUpdater(sensorProxy),
-        m_newDataArrivedTime(0.0),
-        m_newDataArrived(false) {}
-
-  DEFINE_INLINE_VIRTUAL_TRACE() { SensorReadingUpdater::trace(visitor); }
-
-  void start() override {
-    m_newDataArrived = true;
-    SensorReadingUpdater::start();
-  }
-
- protected:
-  void onAnimationFrameInternal() override {
-    if (!m_sensorProxy->isActive())
-      return;
-
-    double timestamp = WTF::monotonicallyIncreasingTime();
-
-    if (m_newDataArrived) {
-      m_newDataArrived = false;
-      m_sensorProxy->updateSensorReading();
-      m_newDataArrivedTime = timestamp;
-    }
-    m_sensorProxy->notifySensorChanged(timestamp);
-
-    DCHECK_GT(m_sensorProxy->frequenciesUsed().front(), 0.0);
-    double longestNotificationPeriod =
-        1 / m_sensorProxy->frequenciesUsed().front();
-
-    if (timestamp - m_newDataArrivedTime <= longestNotificationPeriod)
-      enqueueAnimationFrameTask();
-  }
-
- private:
-  double m_newDataArrivedTime;
-  bool m_newDataArrived;
-};
-
-// static
-SensorReadingUpdater* SensorReadingUpdater::create(SensorProxy* proxy,
-                                                   ReportingMode mode) {
-  if (mode == ReportingMode::CONTINUOUS)
-    return new SensorReadingUpdaterContinuous(proxy);
-  return new SensorReadingUpdaterOnChange(proxy);
-}
-
-}  // namespace blink
diff --git a/third_party/WebKit/Source/modules/sensor/SensorReadingUpdater.h b/third_party/WebKit/Source/modules/sensor/SensorReadingUpdater.h
deleted file mode 100644
index fa28150..0000000
--- a/third_party/WebKit/Source/modules/sensor/SensorReadingUpdater.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef SensorReadingUpdater_h
-#define SensorReadingUpdater_h
-
-#include "device/generic_sensor/public/interfaces/sensor_provider.mojom-blink.h"
-#include "platform/heap/Handle.h"
-
-namespace blink {
-
-class SensorProxy;
-
-// This class encapsulates sensor reading update notification logic.
-class SensorReadingUpdater : public GarbageCollected<SensorProxy> {
- public:
-  static SensorReadingUpdater* create(SensorProxy*,
-                                      device::mojom::blink::ReportingMode);
-
-  virtual void start();
-
-  DECLARE_VIRTUAL_TRACE();
-
- protected:
-  explicit SensorReadingUpdater(SensorProxy*);
-  void enqueueAnimationFrameTask();
-  virtual void onAnimationFrameInternal() = 0;
-
-  Member<SensorProxy> m_sensorProxy;
-  bool m_hasPendingAnimationFrameTask;
-
- private:
-  void onAnimationFrame();
-};
-
-}  // namespace blink
-
-#endif  // SensorReadingUpdater_h
diff --git a/third_party/WebKit/Source/modules/sensor/SensorUpdateNotificationStrategy.cpp b/third_party/WebKit/Source/modules/sensor/SensorUpdateNotificationStrategy.cpp
new file mode 100644
index 0000000..1482398
--- /dev/null
+++ b/third_party/WebKit/Source/modules/sensor/SensorUpdateNotificationStrategy.cpp
@@ -0,0 +1,85 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "modules/sensor/SensorUpdateNotificationStrategy.h"
+
+#include "device/generic_sensor/public/interfaces/sensor.mojom-blink.h"
+#include "platform/Timer.h"
+#include "wtf/CurrentTime.h"
+
+namespace blink {
+
+// Polls the buffer on signal from platform but not more frequently
+// than the given 'pollingPeriod'.
+class SensorUpdateNotificationStrategyImpl
+    : public SensorUpdateNotificationStrategy {
+ public:
+  SensorUpdateNotificationStrategyImpl(double frequency,
+                                       std::unique_ptr<Function<void()>> func)
+      : m_pollingPeriod(1 / frequency),
+        m_func(std::move(func)),
+        m_timer(this, &SensorUpdateNotificationStrategyImpl::onTimer),
+        m_lastPollingTimestamp(0.0) {
+    DCHECK_GT(frequency, 0.0);
+    DCHECK(m_func);
+  }
+
+ private:
+  // SensorUpdateNotificationStrategy overrides.
+  void onSensorReadingChanged() override;
+  void cancelPendingNotifications() override;
+
+  void onTimer(TimerBase*);
+  void notifyUpdate();
+
+  double m_pollingPeriod;
+  std::unique_ptr<Function<void()>> m_func;
+  Timer<SensorUpdateNotificationStrategyImpl> m_timer;
+  double m_lastPollingTimestamp;
+};
+
+void SensorUpdateNotificationStrategyImpl::onSensorReadingChanged() {
+  if (m_timer.isActive())
+    return;  // Skipping changes if update notification was already sheduled.
+
+  double elapsedTime =
+      WTF::monotonicallyIncreasingTime() - m_lastPollingTimestamp;
+
+  double waitingTime = m_pollingPeriod - elapsedTime;
+  const double minInterval =
+      1 / device::mojom::blink::SensorConfiguration::kMaxAllowedFrequency;
+
+  // Negative or zero 'waitingTime' means that polling period has elapsed.
+  // We also avoid scheduling if the elapsed time is slightly behind the
+  // polling period.
+  if (waitingTime < minInterval) {
+    notifyUpdate();
+  } else {
+    m_timer.startOneShot(waitingTime, BLINK_FROM_HERE);
+  }
+}
+
+void SensorUpdateNotificationStrategyImpl::cancelPendingNotifications() {
+  m_timer.stop();
+}
+
+void SensorUpdateNotificationStrategyImpl::notifyUpdate() {
+  m_lastPollingTimestamp = WTF::monotonicallyIncreasingTime();
+  (*m_func)();
+}
+
+void SensorUpdateNotificationStrategyImpl::onTimer(TimerBase*) {
+  notifyUpdate();
+}
+
+// static
+std::unique_ptr<SensorUpdateNotificationStrategy>
+SensorUpdateNotificationStrategy::create(
+    double pollingPeriod,
+    std::unique_ptr<Function<void()>> func) {
+  return std::unique_ptr<SensorUpdateNotificationStrategy>(
+      new SensorUpdateNotificationStrategyImpl(pollingPeriod, std::move(func)));
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/modules/sensor/SensorUpdateNotificationStrategy.h b/third_party/WebKit/Source/modules/sensor/SensorUpdateNotificationStrategy.h
new file mode 100644
index 0000000..720e9a1
--- /dev/null
+++ b/third_party/WebKit/Source/modules/sensor/SensorUpdateNotificationStrategy.h
@@ -0,0 +1,29 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SensorUpdateNotificationStrategy_h
+#define SensorUpdateNotificationStrategy_h
+
+#include "wtf/Functional.h"
+
+namespace blink {
+
+// This class encapsulates sensor reading update notification logic:
+// the callback is invoked after client calls 'onSensorReadingChanged()'
+// however considering the given sample frequency:
+// guaranteed not to be called more often than expected.
+class SensorUpdateNotificationStrategy {
+ public:
+  static std::unique_ptr<SensorUpdateNotificationStrategy> create(
+      double frequency,
+      std::unique_ptr<Function<void()>>);
+
+  virtual void onSensorReadingChanged() = 0;
+  virtual void cancelPendingNotifications() = 0;
+  virtual ~SensorUpdateNotificationStrategy() {}
+};
+
+}  // namespace blink
+
+#endif  // SensorUpdateNotificationStrategy_h
diff --git a/tools/gn/bootstrap/bootstrap.py b/tools/gn/bootstrap/bootstrap.py
index bfdf0fc..c3642e8 100755
--- a/tools/gn/bootstrap/bootstrap.py
+++ b/tools/gn/bootstrap/bootstrap.py
@@ -162,7 +162,10 @@
       {'USE_EXPERIMENTAL_ALLOCATOR_SHIM': 'true' if is_linux else 'false'})
 
   write_buildflag_header_manually(root_gen_dir, 'base/debug/debugging_flags.h',
-      {'ENABLE_PROFILING': 'false'})
+      {
+          'ENABLE_PROFILING': 'false',
+          'ENABLE_MEMORY_TASK_PROFILER': 'false'
+      })
 
   if is_mac:
     # //base/build_time.cc needs base/generated_build_date.h,
@@ -402,6 +405,7 @@
       'base/memory/ref_counted.cc',
       'base/memory/ref_counted_memory.cc',
       'base/memory/singleton.cc',
+      'base/memory/shared_memory_helper.cc',
       'base/memory/weak_ptr.cc',
       'base/message_loop/incoming_task_queue.cc',
       'base/message_loop/message_loop.cc',
@@ -456,6 +460,7 @@
       'base/task_scheduler/scheduler_worker_pool_impl.cc',
       'base/task_scheduler/scheduler_worker_pool_params.cc',
       'base/task_scheduler/scheduler_worker_stack.cc',
+      'base/task_scheduler/scoped_set_task_priority_for_current_thread.cc',
       'base/task_scheduler/sequence.cc',
       'base/task_scheduler/sequence_sort_key.cc',
       'base/task_scheduler/task.cc',
diff --git a/ui/gfx/mojo/color_space.typemap b/ui/gfx/mojo/color_space.typemap
index 4ab7efa4..36ebf09 100644
--- a/ui/gfx/mojo/color_space.typemap
+++ b/ui/gfx/mojo/color_space.typemap
@@ -4,8 +4,9 @@
 
 mojom = "//ui/gfx/mojo/color_space.mojom"
 public_headers = [ "//ui/gfx/color_space.h" ]
-traits_headers = [ "ui/gfx/ipc/color/gfx_param_traits.h" ]
+traits_headers = [ "//ui/gfx/ipc/color/gfx_param_traits.h" ]
 public_deps = [
+  "//ipc",
   "//ui/gfx",
 ]
 deps = [
diff --git a/ui/gfx/typemaps.gni b/ui/gfx/typemaps.gni
index 5420df3..d28b2cb 100644
--- a/ui/gfx/typemaps.gni
+++ b/ui/gfx/typemaps.gni
@@ -6,6 +6,7 @@
   "//ui/gfx/geometry/mojo/geometry.typemap",
   "//ui/gfx/mojo/accelerated_widget.typemap",
   "//ui/gfx/mojo/buffer_types.typemap",
+  "//ui/gfx/mojo/color_space.typemap",
   "//ui/gfx/mojo/icc_profile.typemap",
   "//ui/gfx/mojo/selection_bound.typemap",
   "//ui/gfx/mojo/transform.typemap",