diff --git a/BUILD.gn b/BUILD.gn
index 0687919..b5fedfe 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -69,14 +69,9 @@
     ":gn_visibility",
     "//base:base_unittests",
     "//chrome/installer",
-    "//components:components_unittests",
     "//net:net_unittests",
-    "//skia:skia_unittests",
     "//sql:sql_unittests",
     "//tools/ipc_fuzzer:ipc_fuzzer_all",
-    "//tools/metrics:metrics_metadata",
-    "//ui/base:ui_base_unittests",
-    "//ui/gfx:gfx_unittests",
     "//url:url_unittests",
   ]
 
@@ -87,7 +82,17 @@
     ]
   }
 
-  if (!is_ios && !is_android && !is_chromecast) {
+  if (!is_fuchsia) {
+    deps += [
+      "//components:components_unittests",
+      "//skia:skia_unittests",
+      "//tools/metrics:metrics_metadata",
+      "//ui/base:ui_base_unittests",
+      "//ui/gfx:gfx_unittests",
+    ]
+  }
+
+  if (!is_ios && !is_android && !is_chromecast && !is_fuchsia) {
     deps += [
       "//chrome",
       "//chrome/test:browser_tests",
@@ -146,7 +151,7 @@
     ]
   }
 
-  if (!is_ios) {
+  if (!is_ios && !is_fuchsia) {
     deps += [
       "//cc:cc_unittests",
       "//chrome/test:telemetry_perf_unittests",
@@ -193,7 +198,7 @@
       "//url/ipc:url_ipc_unittests",
       "//v8:gn_all",
     ]
-  } else {
+  } else if (is_ios) {
     deps += [ "//ios:all" ]
   }
 
@@ -397,7 +402,7 @@
 
   # TODO(GYP): Figure out which of these should (and can) build
   # for chromeos/ios.
-  if (!is_chromeos && !is_ios) {
+  if (!is_chromeos && !is_ios && !is_fuchsia) {
     deps += [
       "//base:build_utf8_validator_tables",
       "//base:check_example",
@@ -574,11 +579,11 @@
     ]
   }
 
-  if (!is_android && !is_ios) {
+  if (!is_android && !is_ios && !is_fuchsia) {
     deps += [ "//content/browser/bluetooth/tools:bluetooth_metrics_hash" ]
   }
 
-  if (!is_android && !is_ios && !is_chromeos) {
+  if (!is_android && !is_ios && !is_chromeos && !is_fuchsia) {
     deps += [ "//components/proximity_auth:proximity_auth_unittests" ]
   }
 
@@ -812,7 +817,7 @@
   }
 }
 
-if (!is_ios) {
+if (!is_ios && !is_fuchsia) {
   # This group includes all of the targets needed to build and test Blink,
   # including running the layout tests (see below).
   group("blink_tests") {
@@ -942,7 +947,7 @@
 group("chromium_builder_perf") {
   testonly = true
 
-  if (!is_ios && !is_android && !is_chromecast) {
+  if (!is_ios && !is_android && !is_chromecast && !is_fuchsia) {
     data_deps = [
       "//cc:cc_perftests",
       "//chrome/test:load_library_perf_tests",
@@ -982,7 +987,7 @@
   }
 }
 
-if (!is_ios && !is_android && !is_chromecast) {
+if (!is_ios && !is_android && !is_chromecast && !is_fuchsia) {
   group("chromium_builder_asan") {
     testonly = true
 
diff --git a/DEPS b/DEPS
index 9aa62496..203db0e 100644
--- a/DEPS
+++ b/DEPS
@@ -40,11 +40,11 @@
   # 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': 'df3a371c904c2e3e1d3d9201b7dfc0d080e5f12a',
+  'skia_revision': 'd45afc036484e74d528ceb3274e3df1983d81ac5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'b7b57175c330acb8abeda6061eb172f43ed252f4',
+  'v8_revision': '071b1f432154ea6cd332d918fe514d55deb55194',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index faaa672..a320354 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -369,6 +369,7 @@
     'OS_CAT',       # For testing.
     'OS_CHROMEOS',
     'OS_FREEBSD',
+    'OS_FUCHSIA',
     'OS_IOS',
     'OS_LINUX',
     'OS_MACOSX',
diff --git a/ash/public/interfaces/BUILD.gn b/ash/public/interfaces/BUILD.gn
index a999955..6d03343 100644
--- a/ash/public/interfaces/BUILD.gn
+++ b/ash/public/interfaces/BUILD.gn
@@ -41,6 +41,7 @@
     "//ui/gfx/image/mojo:interfaces",
   ]
 
+  component_output_prefix = "ash_public_interfaces_internal"
   export_class_attribute = "ASH_PUBLIC_EXPORT"
   export_define = "ASH_PUBLIC_IMPLEMENTATION=1"
   export_header = "ash/public/cpp/ash_public_export.h"
diff --git a/base/metrics/histogram.cc b/base/metrics/histogram.cc
index c35f76c..09f0bbb6 100644
--- a/base/metrics/histogram.cc
+++ b/base/metrics/histogram.cc
@@ -414,7 +414,7 @@
 }
 
 uint64_t Histogram::name_hash() const {
-  return samples_->id();
+  return unlogged_samples_->id();
 }
 
 HistogramType Histogram::GetHistogramType() const {
@@ -445,29 +445,33 @@
     NOTREACHED();
     return;
   }
-  samples_->Accumulate(value, count);
+  unlogged_samples_->Accumulate(value, count);
 
   FindAndRunCallback(value);
 }
 
 std::unique_ptr<HistogramSamples> Histogram::SnapshotSamples() const {
-  return SnapshotSampleVector();
+  return SnapshotAllSamples();
 }
 
 std::unique_ptr<HistogramSamples> Histogram::SnapshotDelta() {
   DCHECK(!final_delta_created_);
+  // The code below has subtle thread-safety guarantees! All changes to
+  // the underlying SampleVectors use atomic integer operations, which guarantee
+  // eventual consistency, but do not guarantee full synchronization between
+  // different entries in the SampleVector. In particular, this means that
+  // concurrent updates to the histogram might result in the reported sum not
+  // matching the individual bucket counts; or there being some buckets that are
+  // logically updated "together", but end up being only partially updated when
+  // a snapshot is captured. Note that this is why it's important to subtract
+  // exactly the snapshotted unlogged samples, rather than simply resetting the
+  // vector: this way, the next snapshot will include any concurrent updates
+  // missed by the current snapshot.
 
-  std::unique_ptr<HistogramSamples> snapshot = SnapshotSampleVector();
-  if (!logged_samples_) {
-    // If nothing has been previously logged, save this one as
-    // |logged_samples_| and gather another snapshot to return.
-    logged_samples_.swap(snapshot);
-    return SnapshotSampleVector();
-  }
-
-  // Subtract what was previously logged and update that information.
-  snapshot->Subtract(*logged_samples_);
+  std::unique_ptr<HistogramSamples> snapshot = SnapshotUnloggedSamples();
+  unlogged_samples_->Subtract(*snapshot);
   logged_samples_->Add(*snapshot);
+
   return snapshot;
 }
 
@@ -475,20 +479,15 @@
   DCHECK(!final_delta_created_);
   final_delta_created_ = true;
 
-  std::unique_ptr<HistogramSamples> snapshot = SnapshotSampleVector();
-
-  // Subtract what was previously logged and then return.
-  if (logged_samples_)
-    snapshot->Subtract(*logged_samples_);
-  return snapshot;
+  return SnapshotUnloggedSamples();
 }
 
 void Histogram::AddSamples(const HistogramSamples& samples) {
-  samples_->Add(samples);
+  unlogged_samples_->Add(samples);
 }
 
 bool Histogram::AddSamplesFromPickle(PickleIterator* iter) {
-  return samples_->AddFromPickle(iter);
+  return unlogged_samples_->AddFromPickle(iter);
 }
 
 // The following methods provide a graphical histogram display.
@@ -521,8 +520,10 @@
     bucket_ranges_(ranges),
     declared_min_(minimum),
     declared_max_(maximum) {
-  if (ranges)
-    samples_.reset(new SampleVector(HashMetricName(name), ranges));
+  if (ranges) {
+    unlogged_samples_.reset(new SampleVector(HashMetricName(name), ranges));
+    logged_samples_.reset(new SampleVector(unlogged_samples_->id(), ranges));
+  }
 }
 
 Histogram::Histogram(const std::string& name,
@@ -538,10 +539,10 @@
       declared_min_(minimum),
       declared_max_(maximum) {
   if (ranges) {
-    samples_.reset(
+    unlogged_samples_.reset(
         new PersistentSampleVector(HashMetricName(name), ranges, meta, counts));
     logged_samples_.reset(new PersistentSampleVector(
-        samples_->id(), ranges, logged_meta, logged_counts));
+        unlogged_samples_->id(), ranges, logged_meta, logged_counts));
   }
 }
 
@@ -598,10 +599,16 @@
   return histogram;
 }
 
-std::unique_ptr<SampleVector> Histogram::SnapshotSampleVector() const {
+std::unique_ptr<SampleVector> Histogram::SnapshotAllSamples() const {
+  std::unique_ptr<SampleVector> samples = SnapshotUnloggedSamples();
+  samples->Add(*logged_samples_);
+  return samples;
+}
+
+std::unique_ptr<SampleVector> Histogram::SnapshotUnloggedSamples() const {
   std::unique_ptr<SampleVector> samples(
-      new SampleVector(samples_->id(), bucket_ranges()));
-  samples->Add(*samples_);
+      new SampleVector(unlogged_samples_->id(), bucket_ranges()));
+  samples->Add(*unlogged_samples_);
   return samples;
 }
 
@@ -610,7 +617,7 @@
                                std::string* output) const {
   // Get local (stack) copies of all effectively volatile class data so that we
   // are consistent across our output activities.
-  std::unique_ptr<SampleVector> snapshot = SnapshotSampleVector();
+  std::unique_ptr<SampleVector> snapshot = SnapshotAllSamples();
   Count sample_count = snapshot->TotalCount();
 
   WriteAsciiHeader(*snapshot, sample_count, output);
@@ -722,7 +729,7 @@
 void Histogram::GetCountAndBucketData(Count* count,
                                       int64_t* sum,
                                       ListValue* buckets) const {
-  std::unique_ptr<SampleVector> snapshot = SnapshotSampleVector();
+  std::unique_ptr<SampleVector> snapshot = SnapshotAllSamples();
   *count = snapshot->TotalCount();
   *sum = snapshot->sum();
   uint32_t index = 0;
diff --git a/base/metrics/histogram.h b/base/metrics/histogram.h
index 503d246..78ab116 100644
--- a/base/metrics/histogram.h
+++ b/base/metrics/histogram.h
@@ -264,8 +264,13 @@
       base::PickleIterator* iter);
   static HistogramBase* DeserializeInfoImpl(base::PickleIterator* iter);
 
-  // Implementation of SnapshotSamples function.
-  std::unique_ptr<SampleVector> SnapshotSampleVector() const;
+  // Create a snapshot containing all samples (both logged and unlogged).
+  // Implementation of SnapshotSamples method with a more specific type for
+  // internal use.
+  std::unique_ptr<SampleVector> SnapshotAllSamples() const;
+
+  // Create a copy of unlogged samples.
+  std::unique_ptr<SampleVector> SnapshotUnloggedSamples() const;
 
   //----------------------------------------------------------------------------
   // Helpers for emitting Ascii graphic.  Each method appends data to output.
@@ -303,11 +308,10 @@
   Sample declared_min_;  // Less than this goes into the first bucket.
   Sample declared_max_;  // Over this goes into the last bucket.
 
-  // Finally, provide the state that changes with the addition of each new
-  // sample.
-  std::unique_ptr<SampleVectorBase> samples_;
+  // Samples that have not yet been logged with SnapshotDelta().
+  std::unique_ptr<HistogramSamples> unlogged_samples_;
 
-  // Also keep a previous uploaded state for calculating deltas.
+  // Accumulation of all samples that have been logged with SnapshotDelta().
   std::unique_ptr<HistogramSamples> logged_samples_;
 
   // Flag to indicate if PrepareFinalDelta has been previously called. It is
diff --git a/base/metrics/histogram_base.h b/base/metrics/histogram_base.h
index 4f5ba04..3c6e136e 100644
--- a/base/metrics/histogram_base.h
+++ b/base/metrics/histogram_base.h
@@ -187,6 +187,9 @@
 
   // Snapshot the current complete set of sample data.
   // Override with atomic/locked snapshot if needed.
+  // NOTE: this data can overflow for long-running sessions. It should be
+  // handled with care and this method is recommended to be used only
+  // in about:histograms and test code.
   virtual std::unique_ptr<HistogramSamples> SnapshotSamples() const = 0;
 
   // Calculate the change (delta) in histogram counts since the previous call
diff --git a/base/metrics/histogram_unittest.cc b/base/metrics/histogram_unittest.cc
index d89e7e93..18534be 100644
--- a/base/metrics/histogram_unittest.cc
+++ b/base/metrics/histogram_unittest.cc
@@ -401,6 +401,26 @@
   EXPECT_EQ(19400000000LL, samples2->sum());
 }
 
+// Some metrics are designed so that they are guaranteed not to overflow between
+// snapshots, but could overflow over a long-running session.
+// Make sure that counts returned by Histogram::SnapshotDelta do not overflow
+// even when a total count (returned by Histogram::SnapshotSample) does.
+TEST_P(HistogramTest, AddCount_LargeCountsDontOverflow) {
+  const size_t kBucketCount = 10;
+  Histogram* histogram = static_cast<Histogram*>(Histogram::FactoryGet(
+      "AddCountHistogram", 10, 50, kBucketCount, HistogramBase::kNoFlags));
+
+  const int count = (1 << 30) - 1;
+
+  // Repeat N times to make sure that there is no internal value overflow.
+  for (int i = 0; i < 10; ++i) {
+    histogram->AddCount(42, count);
+    std::unique_ptr<HistogramSamples> samples = histogram->SnapshotDelta();
+    EXPECT_EQ(count, samples->TotalCount());
+    EXPECT_EQ(count, samples->GetCount(42));
+  }
+}
+
 // Make sure histogram handles out-of-bounds data gracefully.
 TEST_P(HistogramTest, BoundsTest) {
   const size_t kBucketCount = 50;
@@ -416,7 +436,7 @@
   histogram->Add(10000);
 
   // Verify they landed in the underflow, and overflow buckets.
-  std::unique_ptr<SampleVector> samples = histogram->SnapshotSampleVector();
+  std::unique_ptr<SampleVector> samples = histogram->SnapshotAllSamples();
   EXPECT_EQ(2, samples->GetCountAtIndex(0));
   EXPECT_EQ(0, samples->GetCountAtIndex(1));
   size_t array_size = histogram->bucket_count();
@@ -441,7 +461,7 @@
 
   // Verify they landed in the underflow, and overflow buckets.
   std::unique_ptr<SampleVector> custom_samples =
-      test_custom_histogram->SnapshotSampleVector();
+      test_custom_histogram->SnapshotAllSamples();
   EXPECT_EQ(2, custom_samples->GetCountAtIndex(0));
   EXPECT_EQ(0, custom_samples->GetCountAtIndex(1));
   size_t bucket_count = test_custom_histogram->bucket_count();
@@ -464,7 +484,7 @@
   }
 
   // Check to see that the bucket counts reflect our additions.
-  std::unique_ptr<SampleVector> samples = histogram->SnapshotSampleVector();
+  std::unique_ptr<SampleVector> samples = histogram->SnapshotAllSamples();
   for (int i = 0; i < 8; i++)
     EXPECT_EQ(i + 1, samples->GetCountAtIndex(i));
 }
@@ -484,7 +504,7 @@
   histogram->Add(20);
   histogram->Add(40);
 
-  std::unique_ptr<SampleVector> snapshot = histogram->SnapshotSampleVector();
+  std::unique_ptr<SampleVector> snapshot = histogram->SnapshotAllSamples();
   EXPECT_EQ(HistogramBase::NO_INCONSISTENCIES,
             histogram->FindCorruption(*snapshot));
   EXPECT_EQ(2, snapshot->redundant_count());
diff --git a/base/metrics/sparse_histogram.cc b/base/metrics/sparse_histogram.cc
index 916dcd57..e9bacd1 100644
--- a/base/metrics/sparse_histogram.cc
+++ b/base/metrics/sparse_histogram.cc
@@ -85,7 +85,7 @@
 SparseHistogram::~SparseHistogram() {}
 
 uint64_t SparseHistogram::name_hash() const {
-  return samples_->id();
+  return unlogged_samples_->id();
 }
 
 HistogramType SparseHistogram::GetHistogramType() const {
@@ -111,7 +111,7 @@
   }
   {
     base::AutoLock auto_lock(lock_);
-    samples_->Accumulate(value, count);
+    unlogged_samples_->Accumulate(value, count);
   }
 
   FindAndRunCallback(value);
@@ -121,7 +121,8 @@
   std::unique_ptr<SampleMap> snapshot(new SampleMap(name_hash()));
 
   base::AutoLock auto_lock(lock_);
-  snapshot->Add(*samples_);
+  snapshot->Add(*unlogged_samples_);
+  snapshot->Add(*logged_samples_);
   return std::move(snapshot);
 }
 
@@ -130,10 +131,9 @@
 
   std::unique_ptr<SampleMap> snapshot(new SampleMap(name_hash()));
   base::AutoLock auto_lock(lock_);
-  snapshot->Add(*samples_);
+  snapshot->Add(*unlogged_samples_);
 
-  // Subtract what was previously logged and update that information.
-  snapshot->Subtract(*logged_samples_);
+  unlogged_samples_->Subtract(*snapshot);
   logged_samples_->Add(*snapshot);
   return std::move(snapshot);
 }
@@ -144,21 +144,19 @@
 
   std::unique_ptr<SampleMap> snapshot(new SampleMap(name_hash()));
   base::AutoLock auto_lock(lock_);
-  snapshot->Add(*samples_);
+  snapshot->Add(*unlogged_samples_);
 
-  // Subtract what was previously logged and then return.
-  snapshot->Subtract(*logged_samples_);
   return std::move(snapshot);
 }
 
 void SparseHistogram::AddSamples(const HistogramSamples& samples) {
   base::AutoLock auto_lock(lock_);
-  samples_->Add(samples);
+  unlogged_samples_->Add(samples);
 }
 
 bool SparseHistogram::AddSamplesFromPickle(PickleIterator* iter) {
   base::AutoLock auto_lock(lock_);
-  return samples_->AddFromPickle(iter);
+  return unlogged_samples_->AddFromPickle(iter);
 }
 
 void SparseHistogram::WriteHTMLGraph(std::string* output) const {
@@ -177,8 +175,8 @@
 
 SparseHistogram::SparseHistogram(const std::string& name)
     : HistogramBase(name),
-      samples_(new SampleMap(HashMetricName(name))),
-      logged_samples_(new SampleMap(samples_->id())) {}
+      unlogged_samples_(new SampleMap(HashMetricName(name))),
+      logged_samples_(new SampleMap(unlogged_samples_->id())) {}
 
 SparseHistogram::SparseHistogram(PersistentHistogramAllocator* allocator,
                                  const std::string& name,
@@ -195,10 +193,11 @@
       // "active" samples use, for convenience purposes, an ID matching
       // that of the histogram while the "logged" samples use that number
       // plus 1.
-      samples_(new PersistentSampleMap(HashMetricName(name), allocator, meta)),
-      logged_samples_(
-          new PersistentSampleMap(samples_->id() + 1, allocator, logged_meta)) {
-}
+      unlogged_samples_(
+          new PersistentSampleMap(HashMetricName(name), allocator, meta)),
+      logged_samples_(new PersistentSampleMap(unlogged_samples_->id() + 1,
+                                              allocator,
+                                              logged_meta)) {}
 
 HistogramBase* SparseHistogram::DeserializeInfoImpl(PickleIterator* iter) {
   std::string histogram_name;
diff --git a/base/metrics/sparse_histogram.h b/base/metrics/sparse_histogram.h
index 97709ba..66e9825 100644
--- a/base/metrics/sparse_histogram.h
+++ b/base/metrics/sparse_histogram.h
@@ -97,7 +97,7 @@
   // Flag to indicate if PrepareFinalDelta has been previously called.
   mutable bool final_delta_created_ = false;
 
-  std::unique_ptr<HistogramSamples> samples_;
+  std::unique_ptr<HistogramSamples> unlogged_samples_;
   std::unique_ptr<HistogramSamples> logged_samples_;
 
   DISALLOW_COPY_AND_ASSIGN(SparseHistogram);
diff --git a/base/metrics/sparse_histogram_unittest.cc b/base/metrics/sparse_histogram_unittest.cc
index 40e6fbf..3ad2bc8 100644
--- a/base/metrics/sparse_histogram_unittest.cc
+++ b/base/metrics/sparse_histogram_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/metrics/histogram_base.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/histogram_samples.h"
+#include "base/metrics/metrics_hashes.h"
 #include "base/metrics/persistent_histogram_allocator.h"
 #include "base/metrics/persistent_memory_allocator.h"
 #include "base/metrics/sample_map.h"
@@ -149,6 +150,25 @@
   EXPECT_EQ(55250000000LL, snapshot2->sum());
 }
 
+// Make sure that counts returned by Histogram::SnapshotDelta do not overflow
+// even when a total count (returned by Histogram::SnapshotSample) does.
+TEST_P(SparseHistogramTest, AddCount_LargeCountsDontOverflow) {
+  std::unique_ptr<SparseHistogram> histogram(NewSparseHistogram("Sparse"));
+  std::unique_ptr<HistogramSamples> snapshot(histogram->SnapshotSamples());
+  EXPECT_EQ(0, snapshot->TotalCount());
+  EXPECT_EQ(0, snapshot->sum());
+
+  const int count = (1 << 30) - 1;
+
+  // Repeat N times to make sure that there is no internal value overflow.
+  for (int i = 0; i < 10; ++i) {
+    histogram->AddCount(42, count);
+    std::unique_ptr<HistogramSamples> samples = histogram->SnapshotDelta();
+    EXPECT_EQ(count, samples->TotalCount());
+    EXPECT_EQ(count, samples->GetCount(42));
+  }
+}
+
 TEST_P(SparseHistogramTest, MacroBasicTest) {
   UMA_HISTOGRAM_SPARSE_SLOWLY("Sparse", 100);
   UMA_HISTOGRAM_SPARSE_SLOWLY("Sparse", 200);
@@ -364,4 +384,11 @@
   }
 }
 
+TEST_P(SparseHistogramTest, HistogramNameHash) {
+  const char kName[] = "TestName";
+  HistogramBase* histogram = SparseHistogram::FactoryGet(
+      kName, HistogramBase::kUmaTargetedHistogramFlag);
+  EXPECT_EQ(histogram->name_hash(), HashMetricName(kName));
+}
+
 }  // namespace base
diff --git a/base/profiler/native_stack_sampler_mac.cc b/base/profiler/native_stack_sampler_mac.cc
index 5a51f26..e29545a 100644
--- a/base/profiler/native_stack_sampler_mac.cc
+++ b/base/profiler/native_stack_sampler_mac.cc
@@ -143,7 +143,7 @@
 
   Dl_info info;
   dladdr(reinterpret_cast<void*>(_exit), &info);
-  strncpy(path, info.dli_fname, PATH_MAX);
+  strlcpy(path, info.dli_fname, PATH_MAX);
   name = path;
 
 #if !defined(ADDRESS_SANITIZER)
@@ -201,9 +201,14 @@
 
 // Module identifiers ---------------------------------------------------------
 
-// Returns the hex encoding of a 16-byte ID for the binary loaded at
-// |module_addr|. Returns an empty string if the UUID cannot be found at
-// |module_addr|.
+// Returns the unique build ID for a module loaded at |module_addr|. Returns the
+// empty string if the function fails to get the build ID.
+//
+// Build IDs are created by the concatenation of the module's GUID (Windows) /
+// UUID (Mac) and an "age" field that indicates how many times that GUID/UUID
+// has been reused. In Windows binaries, the "age" field is present in the
+// module header, but on the Mac, UUIDs are never reused and so the "age" value
+// appended to the UUID is always 0.
 std::string GetUniqueId(const void* module_addr) {
   const mach_header_64* mach_header =
       reinterpret_cast<const mach_header_64*>(module_addr);
@@ -232,7 +237,9 @@
           reinterpret_cast<const uuid_command*>(current_cmd);
       static_assert(sizeof(uuid_cmd->uuid) == sizeof(uuid_t),
                     "UUID field of UUID command should be 16 bytes.");
-      return HexEncode(&uuid_cmd->uuid, sizeof(uuid_cmd->uuid));
+      // The ID is comprised of the UUID concatenated with the Mac's "age" value
+      // which is always 0.
+      return HexEncode(&uuid_cmd->uuid, sizeof(uuid_cmd->uuid)) + "0";
     }
     offset += current_cmd->cmdsize;
   }
diff --git a/base/tracked_objects_unittest.cc b/base/tracked_objects_unittest.cc
index 80b4064..1eb4199 100644
--- a/base/tracked_objects_unittest.cc
+++ b/base/tracked_objects_unittest.cc
@@ -346,9 +346,23 @@
   EXPECT_EQ(data->alloc_overhead_bytes(), 3 * kAllocOverheadBytes);
   EXPECT_EQ(data->max_allocated_bytes(), kLargerMaxAllocatedBytes);
 
-  // Saturate everything but aggregate byte counts. The byte counts will be
-  // pushed past the 32 bit value range.
+  // Saturate everything but aggregate byte counts.
+  // In the 32 bit implementation, this tests the case where the low-order
+  // word goes negative.
   data->RecordAllocations(INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX);
+  EXPECT_EQ(data->alloc_ops(), INT_MAX);
+  EXPECT_EQ(data->free_ops(), INT_MAX);
+  // The cumulative byte counts are 64 bit wide, and won't saturate easily.
+  EXPECT_EQ(data->allocated_bytes(),
+            static_cast<int64_t>(INT_MAX) +
+                static_cast<int64_t>(3 * kAllocatedBytes));
+  EXPECT_EQ(data->freed_bytes(),
+            static_cast<int64_t>(INT_MAX) + 3 * kFreedBytes);
+  EXPECT_EQ(data->alloc_overhead_bytes(),
+            static_cast<int64_t>(INT_MAX) + 3 * kAllocOverheadBytes);
+  EXPECT_EQ(data->max_allocated_bytes(), INT_MAX);
+
+  // The byte counts will be pushed past the 32 bit value range.
   data->RecordAllocations(INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX);
   EXPECT_EQ(data->alloc_ops(), INT_MAX);
   EXPECT_EQ(data->free_ops(), INT_MAX);
diff --git a/build/build_config.h b/build/build_config.h
index a672380..0906419 100644
--- a/build/build_config.h
+++ b/build/build_config.h
@@ -49,6 +49,8 @@
 #endif
 #elif defined(_WIN32)
 #define OS_WIN 1
+#elif defined(__Fuchsia__)
+#define OS_FUCHSIA 1
 #elif defined(__FreeBSD__)
 #define OS_FREEBSD 1
 #elif defined(__NetBSD__)
@@ -77,10 +79,10 @@
 
 // For access to standard POSIXish features, use OS_POSIX instead of a
 // more specific macro.
-#if defined(OS_AIX) || defined(OS_ANDROID) || defined(OS_FREEBSD) || \
-    defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_NACL) ||   \
-    defined(OS_NETBSD) || defined(OS_OPENBSD) || defined(OS_QNX) ||  \
-    defined(OS_SOLARIS)
+#if defined(OS_AIX) || defined(OS_ANDROID) || defined(OS_FREEBSD) ||  \
+    defined(OS_FUCHSIA) || defined(OS_LINUX) || defined(OS_MACOSX) || \
+    defined(OS_NACL) || defined(OS_NETBSD) || defined(OS_OPENBSD) ||  \
+    defined(OS_QNX) || defined(OS_SOLARIS)
 #define OS_POSIX 1
 #endif
 
@@ -165,12 +167,10 @@
 // Type detection for wchar_t.
 #if defined(OS_WIN)
 #define WCHAR_T_IS_UTF16
-#elif defined(OS_POSIX) && defined(COMPILER_GCC) && \
-    defined(__WCHAR_MAX__) && \
+#elif defined(OS_POSIX) && defined(COMPILER_GCC) && defined(__WCHAR_MAX__) && \
     (__WCHAR_MAX__ == 0x7fffffff || __WCHAR_MAX__ == 0xffffffff)
 #define WCHAR_T_IS_UTF32
-#elif defined(OS_POSIX) && defined(COMPILER_GCC) && \
-    defined(__WCHAR_MAX__) && \
+#elif defined(OS_POSIX) && defined(COMPILER_GCC) && defined(__WCHAR_MAX__) && \
     (__WCHAR_MAX__ == 0x7fff || __WCHAR_MAX__ == 0xffff)
 // On Posix, we'll detect short wchar_t, but projects aren't guaranteed to
 // compile in this mode (in particular, Chrome doesn't). This is intended for
diff --git a/build/config/BUILDCONFIG.gn b/build/config/BUILDCONFIG.gn
index 4dddecd..ba1e5c9 100644
--- a/build/config/BUILDCONFIG.gn
+++ b/build/config/BUILDCONFIG.gn
@@ -137,6 +137,7 @@
   # to configure warnings.
   is_clang =
       current_os == "mac" || current_os == "ios" || current_os == "chromeos" ||
+      current_os == "fuchsia" ||
       (current_os == "linux" && current_cpu != "s390x" &&
        current_cpu != "s390" && current_cpu != "ppc64" && current_cpu != "ppc")
 
@@ -163,7 +164,8 @@
   #
   # For more information see
   # https://chromium.googlesource.com/chromium/src/+/master/docs/component_build.md
-  is_component_build = is_debug && current_os != "ios"
+  is_component_build =
+      is_debug && current_os != "ios" && current_os != "fuchsia"
 }
 
 assert(!(is_debug && is_official_build), "Can't do official debug builds")
@@ -233,6 +235,8 @@
   } else {
     _default_toolchain = "//build/toolchain/linux:$target_cpu"
   }
+} else if (target_os == "fuchsia") {
+  _default_toolchain = "//build/toolchain/fuchsia:$target_cpu"
 } else if (target_os == "ios") {
   _default_toolchain = "//build/toolchain/mac:ios_clang_$target_cpu"
 } else if (target_os == "mac") {
@@ -285,6 +289,7 @@
     current_os == "winrt_81_phone" || current_os == "winrt_10") {
   is_android = false
   is_chromeos = false
+  is_fuchsia = false
   is_ios = false
   is_linux = false
   is_mac = false
@@ -294,6 +299,7 @@
 } else if (current_os == "mac") {
   is_android = false
   is_chromeos = false
+  is_fuchsia = false
   is_ios = false
   is_linux = false
   is_mac = true
@@ -303,6 +309,7 @@
 } else if (current_os == "android") {
   is_android = true
   is_chromeos = false
+  is_fuchsia = false
   is_ios = false
   is_linux = false
   is_mac = false
@@ -312,6 +319,7 @@
 } else if (current_os == "chromeos") {
   is_android = false
   is_chromeos = true
+  is_fuchsia = false
   is_ios = false
   is_linux = true
   is_mac = false
@@ -324,15 +332,27 @@
   # Posix variant.
   is_android = false
   is_chromeos = false
+  is_fuchsia = false
   is_ios = false
   is_linux = false
   is_mac = false
   is_nacl = true
   is_posix = true
   is_win = false
+} else if (current_os == "fuchsia") {
+  is_android = false
+  is_chromeos = false
+  is_fuchsia = true
+  is_ios = false
+  is_linux = false
+  is_mac = false
+  is_nacl = false
+  is_posix = true
+  is_win = false
 } else if (current_os == "ios") {
   is_android = false
   is_chromeos = false
+  is_fuchsia = false
   is_ios = true
   is_linux = false
   is_mac = false
@@ -342,6 +362,7 @@
 } else if (current_os == "linux") {
   is_android = false
   is_chromeos = false
+  is_fuchsia = false
   is_ios = false
   is_linux = true
   is_mac = false
diff --git a/build/config/allocator.gni b/build/config/allocator.gni
index 6c3b48f..09f5ce8 100644
--- a/build/config/allocator.gni
+++ b/build/config/allocator.gni
@@ -6,7 +6,7 @@
 
 # Temporarily disable tcmalloc on arm64 linux to get rid of compilation errors.
 if (is_android || current_cpu == "mipsel" || is_mac || is_ios || is_asan ||
-    is_lsan || is_tsan || is_msan || is_win || is_syzyasan ||
+    is_lsan || is_tsan || is_msan || is_win || is_syzyasan || is_fuchsia ||
     (is_linux && target_cpu == "arm64")) {
   _default_allocator = "none"
 } else {
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index 5961d2f..901259911 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -120,7 +120,8 @@
 }
 
 if (use_debug_fission == "default") {
-  use_debug_fission = is_debug && !is_win && use_gold && cc_wrapper == ""
+  use_debug_fission =
+      is_debug && !is_android && !is_win && use_gold && cc_wrapper == ""
 }
 
 # default_include_dirs ---------------------------------------------------------
@@ -168,6 +169,8 @@
     configs += [ "//build/config/mac:compiler" ]
   } else if (is_ios) {
     configs += [ "//build/config/ios:compiler" ]
+  } else if (is_fuchsia) {
+    configs += [ "//build/config/fuchsia:compiler" ]
   } else if (current_os == "aix") {
     configs += [ "//build/config/aix:compiler" ]
   }
@@ -308,7 +311,7 @@
 
   # Linux/Android common flags setup.
   # ---------------------------------
-  if (is_linux || is_android) {
+  if (is_linux || is_android || is_fuchsia) {
     if (use_pic) {
       cflags += [ "-fPIC" ]
       ldflags += [ "-fPIC" ]
diff --git a/build/config/compiler/compiler.gni b/build/config/compiler/compiler.gni
index d1bcc4c..46b048f 100644
--- a/build/config/compiler/compiler.gni
+++ b/build/config/compiler/compiler.gni
@@ -121,12 +121,14 @@
 
 declare_args() {
   # Whether to use the gold linker from binutils instead of lld or bfd.
-  use_gold = !use_lld && !(is_chromecast && is_linux &&
-                           (current_cpu == "arm" || current_cpu == "mipsel")) &&
-             ((is_linux && (current_cpu == "x64" || current_cpu == "x86" ||
-                            current_cpu == "arm" || current_cpu == "mipsel")) ||
-              (is_android && (current_cpu == "x86" || current_cpu == "x64" ||
-                              current_cpu == "arm" || current_cpu == "arm64")))
+  use_gold =
+      !use_lld && !(is_chromecast && is_linux &&
+                    (current_cpu == "arm" || current_cpu == "mipsel")) &&
+      ((is_linux && (current_cpu == "x64" || current_cpu == "x86" ||
+                     current_cpu == "arm" || current_cpu == "mipsel")) ||
+       (is_android && (current_cpu == "x86" || current_cpu == "x64" ||
+                       current_cpu == "arm" || current_cpu == "arm64")) ||
+       is_fuchsia)
 }
 
 # If it wasn't manually set, set to an appropriate default.
diff --git a/build/config/fuchsia/BUILD.gn b/build/config/fuchsia/BUILD.gn
new file mode 100644
index 0000000..9ec7b11
--- /dev/null
+++ b/build/config/fuchsia/BUILD.gn
@@ -0,0 +1,30 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/sysroot.gni")
+
+assert(is_fuchsia)
+assert(is_posix)
+
+config("compiler") {
+  defines = [ "SYSROOT_VERSION=$sysroot_version" ]
+  cflags = []
+  ldflags = []
+  if (current_cpu == "arm64") {
+    cflags += [ "--target=aarch64-fuchsia" ]
+    ldflags += [ "--target=aarch64-fuchsia" ]
+  } else if (current_cpu == "x64") {
+    cflags += [ "--target=x86_64-fuchsia" ]
+    ldflags += [ "--target=x86_64-fuchsia" ]
+  } else {
+    assert(false, "Unsupported architecture")
+  }
+  asmflags = cflags
+
+  libs = [
+    "mxio",
+    "magenta",
+    "unwind",
+  ]
+}
diff --git a/build/config/fuchsia/OWNERS b/build/config/fuchsia/OWNERS
new file mode 100644
index 0000000..3f809e82
--- /dev/null
+++ b/build/config/fuchsia/OWNERS
@@ -0,0 +1 @@
+scottmg@chromium.org
diff --git a/build/config/fuchsia/config.gni b/build/config/fuchsia/config.gni
new file mode 100644
index 0000000..d1d1104
--- /dev/null
+++ b/build/config/fuchsia/config.gni
@@ -0,0 +1,10 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+assert(current_os == "fuchsia")
+
+declare_args() {
+  # Path to Fuchsia SDK.
+  fuchsia_sdk = "//third_party/fuchsia-sdk"
+}
diff --git a/build/config/sysroot.gni b/build/config/sysroot.gni
index 2c26c34..51f106e 100644
--- a/build/config/sysroot.gni
+++ b/build/config/sysroot.gni
@@ -75,6 +75,17 @@
 } else if (is_ios) {
   import("//build/config/ios/ios_sdk.gni")
   sysroot = ios_sdk_path
+} else if (is_fuchsia) {
+  import("//build/config/fuchsia/config.gni")
+  if (current_cpu == "arm64") {
+    sysroot = fuchsia_sdk + "/sysroot/aarch64-fuchsia"
+  } else if (current_cpu == "x64") {
+    sysroot = fuchsia_sdk + "/sysroot/x86_64-fuchsia"
+  } else {
+    sysroot = ""
+  }
+  sysroot_stamp = rebase_path("$sysroot/.stamp")
+  sysroot_version = read_file(sysroot_stamp, "trim string")
 } else {
   sysroot = ""
 }
diff --git a/build/toolchain/fuchsia/BUILD.gn b/build/toolchain/fuchsia/BUILD.gn
new file mode 100644
index 0000000..ec94b982
--- /dev/null
+++ b/build/toolchain/fuchsia/BUILD.gn
@@ -0,0 +1,16 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/toolchain/gcc_toolchain.gni")
+import("//build/config/fuchsia/config.gni")
+
+clang_toolchain("x64") {
+  assert(current_cpu == "x64")
+  assert(host_os == "linux")
+
+  toolchain_args = {
+    current_cpu = "x64"
+    current_os = "fuchsia"
+  }
+}
diff --git a/build/toolchain/fuchsia/OWNERS b/build/toolchain/fuchsia/OWNERS
new file mode 100644
index 0000000..3f809e82
--- /dev/null
+++ b/build/toolchain/fuchsia/OWNERS
@@ -0,0 +1 @@
+scottmg@chromium.org
diff --git a/cc/layers/layer.cc b/cc/layers/layer.cc
index 10bc522..f496d69 100644
--- a/cc/layers/layer.cc
+++ b/cc/layers/layer.cc
@@ -1301,11 +1301,6 @@
     inputs_.client->didChangeScrollbarsHidden(hidden);
 }
 
-bool Layer::TransformIsAnimating() const {
-  return GetMutatorHost()->IsAnimatingTransformProperty(
-      element_id(), GetElementTypeForAnimation());
-}
-
 gfx::ScrollOffset Layer::ScrollOffsetForAnimation() const {
   return CurrentScrollOffset();
 }
diff --git a/cc/layers/layer.h b/cc/layers/layer.h
index bb8f2f1..80f9647 100644
--- a/cc/layers/layer.h
+++ b/cc/layers/layer.h
@@ -472,7 +472,6 @@
   void OnTransformAnimated(const gfx::Transform& transform);
   void OnScrollOffsetAnimated(const gfx::ScrollOffset& scroll_offset);
 
-  bool TransformIsAnimating() const;
   bool ScrollOffsetAnimationWasInterrupted() const;
 
   void AddScrollChild(Layer* child);
diff --git a/cc/layers/layer_impl.cc b/cc/layers/layer_impl.cc
index e438943..81e5069 100644
--- a/cc/layers/layer_impl.cc
+++ b/cc/layers/layer_impl.cc
@@ -667,11 +667,6 @@
   position_ = position;
 }
 
-bool LayerImpl::TransformIsAnimating() const {
-  return GetMutatorHost()->IsAnimatingTransformProperty(
-      element_id(), GetElementTypeForAnimation());
-}
-
 bool LayerImpl::HasPotentiallyRunningTransformAnimation() const {
   return GetMutatorHost()->HasPotentiallyRunningTransformAnimation(
       element_id(), GetElementTypeForAnimation());
diff --git a/cc/layers/layer_impl.h b/cc/layers/layer_impl.h
index 4f419eb..f497b7d 100644
--- a/cc/layers/layer_impl.h
+++ b/cc/layers/layer_impl.h
@@ -335,7 +335,6 @@
     return touch_event_handler_region_;
   }
 
-  bool TransformIsAnimating() const;
   bool HasPotentiallyRunningTransformAnimation() const;
 
   bool HasFilterAnimationThatInflatesBounds() const;
diff --git a/cc/tiles/picture_layer_tiling.cc b/cc/tiles/picture_layer_tiling.cc
index ecf8fe1..2a20ddc 100644
--- a/cc/tiles/picture_layer_tiling.cc
+++ b/cc/tiles/picture_layer_tiling.cc
@@ -376,9 +376,8 @@
     const gfx::Rect& coverage_rect)
     : tiling_(tiling),
       coverage_rect_(coverage_rect),
-      coverage_to_content_(
-          gfx::PreScaleAxisTransform2d(tiling->raster_transform(),
-                                       1.f / coverage_scale)) {
+      coverage_to_content_(tiling->raster_transform().scale() / coverage_scale,
+                           tiling->raster_transform().translation()) {
   DCHECK(tiling_);
   // In order to avoid artifacts in geometry_rect scaling and clamping to ints,
   // the |coverage_scale| should always be at least as big as the tiling's
@@ -435,25 +434,70 @@
     return *this;
 
   bool first_time = tile_i_ < left_;
-  bool new_row = false;
-  tile_i_++;
-  if (tile_i_ > right_) {
-    tile_i_ = left_;
-    tile_j_++;
-    new_row = true;
-    if (tile_j_ > bottom_) {
-      current_tile_ = NULL;
-      return *this;
+  while (true) {
+    bool new_row = false;
+    tile_i_++;
+    if (tile_i_ > right_) {
+      tile_i_ = left_;
+      tile_j_++;
+      new_row = true;
+      if (tile_j_ > bottom_) {
+        current_tile_ = NULL;
+        break;
+      }
     }
+
+    DCHECK_LT(tile_i_, tiling_->tiling_data_.num_tiles_x());
+    DCHECK_LT(tile_j_, tiling_->tiling_data_.num_tiles_y());
+    current_tile_ = tiling_->TileAt(tile_i_, tile_j_);
+
+    gfx::Rect geometry_rect_candidate = ComputeGeometryRect();
+
+    // This can happen due to floating point inprecision when calculating the
+    // |wanted_texels| area in the constructor.
+    if (geometry_rect_candidate.IsEmpty())
+      continue;
+
+    gfx::Rect last_geometry_rect = current_geometry_rect_;
+    current_geometry_rect_ = geometry_rect_candidate;
+
+    if (first_time)
+      break;
+
+    // Iteration happens left->right, top->bottom.  Running off the bottom-right
+    // edge is handled by the intersection above with dest_rect_.  Here we make
+    // sure that the new current geometry rect doesn't overlap with the last.
+    int min_left;
+    int min_top;
+    if (new_row) {
+      min_left = coverage_rect_.x();
+      min_top = last_geometry_rect.bottom();
+    } else {
+      min_left = last_geometry_rect.right();
+      min_top = last_geometry_rect.y();
+    }
+
+    int inset_left = std::max(0, min_left - current_geometry_rect_.x());
+    int inset_top = std::max(0, min_top - current_geometry_rect_.y());
+    current_geometry_rect_.Inset(inset_left, inset_top, 0, 0);
+
+#if DCHECK_IS_ON()
+    if (!new_row) {
+      DCHECK_EQ(last_geometry_rect.right(), current_geometry_rect_.x());
+      DCHECK_EQ(last_geometry_rect.bottom(), current_geometry_rect_.bottom());
+      DCHECK_EQ(last_geometry_rect.y(), current_geometry_rect_.y());
+    }
+#endif
+
+    break;
   }
+  return *this;
+}
 
-  current_tile_ = tiling_->TileAt(tile_i_, tile_j_);
-
+gfx::Rect PictureLayerTiling::CoverageIterator::ComputeGeometryRect() const {
   // Calculate the current geometry rect. As we reserved overlap between tiles
   // to accommodate bilinear filtering and rounding errors in destination
   // space, the geometry rect might overlap on the edges.
-  gfx::Rect last_geometry_rect = current_geometry_rect_;
-
   gfx::RectF texel_extent = tiling_->tiling_data_.TexelExtent(tile_i_, tile_j_);
   {
     // Adjust tile extent to accommodate numerical errors.
@@ -470,7 +514,7 @@
 
   // Convert texel_extent to coverage scale, which is what we have to report
   // geometry_rect in.
-  current_geometry_rect_ =
+  gfx::Rect candidate =
       gfx::ToEnclosedRect(coverage_to_content_.InverseMapRect(texel_extent));
   {
     // Adjust external edges to cover the whole layer in dest space.
@@ -482,48 +526,18 @@
     // sampled as the AA fragment shader clamps sample coordinate and
     // antialiasing itself.
     const TilingData& data = tiling_->tiling_data_;
-    current_geometry_rect_.Inset(tile_i_ ? 0 : -current_geometry_rect_.x(),
-                                 tile_j_ ? 0 : -current_geometry_rect_.y(),
-                                 (tile_i_ != data.num_tiles_x() - 1)
-                                     ? 0
-                                     : current_geometry_rect_.right() -
-                                           coverage_rect_max_bounds_.width(),
-                                 (tile_j_ != data.num_tiles_y() - 1)
-                                     ? 0
-                                     : current_geometry_rect_.bottom() -
-                                           coverage_rect_max_bounds_.height());
+    candidate.Inset(
+        tile_i_ ? 0 : -candidate.x(), tile_j_ ? 0 : -candidate.y(),
+        (tile_i_ != data.num_tiles_x() - 1)
+            ? 0
+            : candidate.right() - coverage_rect_max_bounds_.width(),
+        (tile_j_ != data.num_tiles_y() - 1)
+            ? 0
+            : candidate.bottom() - coverage_rect_max_bounds_.height());
   }
 
-  current_geometry_rect_.Intersect(coverage_rect_);
-  DCHECK(!current_geometry_rect_.IsEmpty());
-
-  if (first_time)
-    return *this;
-
-  // Iteration happens left->right, top->bottom.  Running off the bottom-right
-  // edge is handled by the intersection above with dest_rect_.  Here we make
-  // sure that the new current geometry rect doesn't overlap with the last.
-  int min_left;
-  int min_top;
-  if (new_row) {
-    min_left = coverage_rect_.x();
-    min_top = last_geometry_rect.bottom();
-  } else {
-    min_left = last_geometry_rect.right();
-    min_top = last_geometry_rect.y();
-  }
-
-  int inset_left = std::max(0, min_left - current_geometry_rect_.x());
-  int inset_top = std::max(0, min_top - current_geometry_rect_.y());
-  current_geometry_rect_.Inset(inset_left, inset_top, 0, 0);
-
-  if (!new_row) {
-    DCHECK_EQ(last_geometry_rect.right(), current_geometry_rect_.x());
-    DCHECK_EQ(last_geometry_rect.bottom(), current_geometry_rect_.bottom());
-    DCHECK_EQ(last_geometry_rect.y(), current_geometry_rect_.y());
-  }
-
-  return *this;
+  candidate.Intersect(coverage_rect_);
+  return candidate;
 }
 
 gfx::Rect PictureLayerTiling::CoverageIterator::geometry_rect() const {
diff --git a/cc/tiles/picture_layer_tiling.h b/cc/tiles/picture_layer_tiling.h
index 44c4a20..d49d22e 100644
--- a/cc/tiles/picture_layer_tiling.h
+++ b/cc/tiles/picture_layer_tiling.h
@@ -237,6 +237,8 @@
     int j() const { return tile_j_; }
 
    private:
+    gfx::Rect ComputeGeometryRect() const;
+
     const PictureLayerTiling* tiling_ = nullptr;
     gfx::Size coverage_rect_max_bounds_;
     gfx::Rect coverage_rect_;
diff --git a/cc/tiles/picture_layer_tiling_unittest.cc b/cc/tiles/picture_layer_tiling_unittest.cc
index f6185bf..39d5867 100644
--- a/cc/tiles/picture_layer_tiling_unittest.cc
+++ b/cc/tiles/picture_layer_tiling_unittest.cc
@@ -1255,5 +1255,33 @@
   EXPECT_FALSE(++iter);
 }
 
+TEST_F(PictureLayerTilingIteratorTest, EdgeCaseLargeIntBounds) {
+  gfx::Size tile_size(256, 256);
+  float scale = 7352.331055f;
+  gfx::Size layer_bounds(292082, 26910);
+  gfx::Rect coverage_rect(2104641536, 522015, 29440, 66172);
+  Initialize(tile_size, scale, layer_bounds);
+  int count = 0;
+  for (PictureLayerTiling::CoverageIterator
+           iter(tiling_.get(), scale, coverage_rect);
+       iter && count < 200; ++count, ++iter) {
+    EXPECT_FALSE(iter.geometry_rect().IsEmpty());
+  }
+}
+
+TEST_F(PictureLayerTilingIteratorTest, EdgeCaseLargeIntBounds2) {
+  gfx::RectF rect(2104670720.f, 522014.5f, 192.f, 1.f);
+  gfx::Size tile_size(256, 256);
+  float scale = 7352.331055f;
+  gfx::Size layer_bounds(292082, 26910);
+  gfx::Rect coverage_rect(2104670720, 522015, 192, 1);
+  Initialize(tile_size, scale, layer_bounds);
+  for (PictureLayerTiling::CoverageIterator iter(tiling_.get(), scale,
+                                                 coverage_rect);
+       iter; ++iter) {
+    EXPECT_FALSE(iter.geometry_rect().IsEmpty());
+  }
+}
+
 }  // namespace
 }  // namespace cc
diff --git a/cc/trees/layer_tree_host_common_unittest.cc b/cc/trees/layer_tree_host_common_unittest.cc
index e4a6f57..aed6857c7 100644
--- a/cc/trees/layer_tree_host_common_unittest.cc
+++ b/cc/trees/layer_tree_host_common_unittest.cc
@@ -2134,6 +2134,11 @@
   EXPECT_EQ(gfx::Rect(), grand_child->visible_layer_rect());
 }
 
+static bool TransformIsAnimating(LayerImpl* layer) {
+  return layer->GetMutatorHost()->IsAnimatingTransformProperty(
+      layer->element_id(), layer->GetElementTypeForAnimation());
+}
+
 TEST_F(LayerTreeHostCommonTest,
        ScreenSpaceTransformIsAnimatingWithDelayedAnimation) {
   LayerImpl* root = root_layer_for_testing();
@@ -2166,7 +2171,7 @@
   EXPECT_FALSE(root->screen_space_transform_is_animating());
   EXPECT_FALSE(child->screen_space_transform_is_animating());
 
-  EXPECT_FALSE(grand_child->TransformIsAnimating());
+  EXPECT_FALSE(TransformIsAnimating(grand_child));
   EXPECT_TRUE(grand_child->HasPotentiallyRunningTransformAnimation());
   EXPECT_TRUE(grand_child->screen_space_transform_is_animating());
   EXPECT_TRUE(great_grand_child->screen_space_transform_is_animating());
diff --git a/chrome/android/java/res/color/bookmark_drawer_text_color.xml b/chrome/android/java/res/color/bookmark_drawer_text_color.xml
deleted file mode 100644
index dc0fe0e3..0000000
--- a/chrome/android/java/res/color/bookmark_drawer_text_color.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2015 The Chromium Authors. All rights reserved.
-     Use of this source code is governed by a BSD-style license that can be
-     found in the LICENSE file.
--->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_selected="true" android:color="@color/light_active_color" />
-    <item android:state_focused="true" android:color="@color/light_active_color" />
-    <item android:state_pressed="true" android:color="@color/light_active_color" />
-    <item android:state_activated="true" android:color="@color/light_active_color" />
-    <item android:state_enabled="false" android:color="#335a5a5a" />
-    <item android:color="@color/bookmark_detail_text"/>
-</selector>
\ No newline at end of file
diff --git a/chrome/android/java/res/layout/page_info_permission_row.xml b/chrome/android/java/res/layout/page_info_permission_row.xml
index 703bd7eb..61f9bfc 100644
--- a/chrome/android/java/res/layout/page_info_permission_row.xml
+++ b/chrome/android/java/res/layout/page_info_permission_row.xml
@@ -3,7 +3,9 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:chrome="http://schemas.android.com/apk/res-auto"
     android:id="@+id/page_info_permission_row"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
@@ -24,14 +26,25 @@
         android:textColor="@color/page_info_popup_text"
         android:textSize="14sp" />
 
-    <TextView
+    <org.chromium.ui.widget.TextViewWithLeading
         android:id="@+id/page_info_permission_unavailable_message"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_below="@id/page_info_permission_status"
         android:layout_toEndOf="@id/page_info_permission_icon"
-        android:lineSpacingExtra="6dp"
         android:textColor="@color/page_info_popup_text_link"
         android:textSize="14sp"
-        android:visibility="gone" />
+        android:visibility="gone"
+        chrome:leading="20dp" />
+
+    <org.chromium.ui.widget.TextViewWithLeading
+        android:id="@+id/page_info_permission_subtitle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/page_info_permission_status"
+        android:layout_toEndOf="@id/page_info_permission_icon"
+        android:textSize="14sp"
+        android:textColor="@color/secondary_text_default_material_light"
+        android:visibility="gone"
+        chrome:leading="20dp" />
 </RelativeLayout>
diff --git a/chrome/android/java/res/layout/promo_dialog_layout.xml b/chrome/android/java/res/layout/promo_dialog_layout.xml
index 00793b6..8efde5cb7 100644
--- a/chrome/android/java/res/layout/promo_dialog_layout.xml
+++ b/chrome/android/java/res/layout/promo_dialog_layout.xml
@@ -42,7 +42,7 @@
                         android:layout_width="@dimen/promo_dialog_illustration_width"
                         android:layout_height="wrap_content"
                         android:layout_gravity="center"
-                        android:layout_marginTop="@dimen/promo_dialog_illustration_margin"
+                        android:paddingTop="@dimen/promo_dialog_illustration_margin"
                         android:layout_marginStart="@dimen/promo_dialog_illustration_margin"
                         android:layout_marginEnd="@dimen/promo_dialog_illustration_margin"
                         android:scaleType="fitCenter"
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
index 5c7244f..0e71b3ea7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
@@ -952,12 +952,15 @@
         });
     }
 
-    private boolean maySpeculate(CustomTabsSessionToken session) {
+    @VisibleForTesting
+    boolean maySpeculate(CustomTabsSessionToken session) {
         if (!DeviceClassManager.enablePrerendering()) return false;
+        PrefServiceBridge prefs = PrefServiceBridge.getInstance();
+        if (prefs.isBlockThirdPartyCookiesEnabled()) return false;
         // TODO(yusufo): The check for prerender in PrivacyManager now checks for the network
         // connection type as well, we should either change that or add another check for custom
         // tabs. Then PrivacyManager should be used to make the below check.
-        if (!PrefServiceBridge.getInstance().getNetworkPredictionEnabled()) return false;
+        if (!prefs.getNetworkPredictionEnabled()) return false;
         if (DataReductionProxySettings.getInstance().isDataReductionProxyEnabled()) return false;
         ConnectivityManager cm =
                 (ConnectivityManager) mApplication.getApplicationContext().getSystemService(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
index efc813b..27843622 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
@@ -1114,7 +1114,7 @@
         mHasStartedNewOmniboxEditSession = false;
         mNewOmniboxEditSessionTimestamp = -1;
         Tab currentTab = getCurrentTab();
-        if (mNativeInitialized && mBottomSheet == null && mUrlHasFocus && currentTab != null) {
+        if (mNativeInitialized && mUrlHasFocus && currentTab != null) {
             mAutocomplete.startZeroSuggest(currentTab.getProfile(), mUrlBar.getQueryText(),
                     mToolbarDataProvider.getCurrentUrl(), mUrlFocusedFromFakebox);
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/page_info/PageInfoPopup.java b/chrome/android/java/src/org/chromium/chrome/browser/page_info/PageInfoPopup.java
index c44e6f5..953715c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/page_info/PageInfoPopup.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/page_info/PageInfoPopup.java
@@ -621,6 +621,14 @@
             }
         }
 
+        // The subresource filter permission requires an additional static subtitle.
+        if (permission.type == ContentSettingsType.CONTENT_SETTINGS_TYPE_SUBRESOURCE_FILTER) {
+            TextView permissionUnavailable =
+                    (TextView) permissionRow.findViewById(R.id.page_info_permission_subtitle);
+            permissionUnavailable.setVisibility(View.VISIBLE);
+            permissionUnavailable.setText(R.string.subresource_filter_permission_title);
+        }
+
         TextView permissionStatus = (TextView) permissionRow.findViewById(
                 R.id.page_info_permission_status);
         SpannableStringBuilder builder = new SpannableStringBuilder();
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 adc7e58..cb50615f 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
@@ -857,13 +857,14 @@
                 formatter.format(total.amount.value), false /* isPending */));
         mUiShoppingCart.setAdditionalContents(
                 modifier == null ? null : getLineItems(modifier.additionalDisplayItems));
-        mUI.updateOrderSummarySection(mUiShoppingCart);
+        if (mUI != null) mUI.updateOrderSummarySection(mUiShoppingCart);
     }
 
     /** @return The first modifier that matches the given instrument, or null. */
     @Nullable private PaymentDetailsModifier getModifier(@Nullable PaymentInstrument instrument) {
         if (mModifiers == null || instrument == null) return null;
-        Set<String> methodNames = instrument.getInstrumentMethodNames();
+        // Makes a copy to ensure it is modifiable.
+        Set<String> methodNames = new HashSet<>(instrument.getInstrumentMethodNames());
         methodNames.retainAll(mModifiers.keySet());
         return methodNames.isEmpty() ? null : mModifiers.get(methodNames.iterator().next());
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/BottomToolbarPhone.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/BottomToolbarPhone.java
index 0b25445..549c461 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/BottomToolbarPhone.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/BottomToolbarPhone.java
@@ -19,6 +19,7 @@
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.device.DeviceClassManager;
+import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.util.ColorUtils;
 import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.chrome.browser.widget.TintedImageButton;
@@ -182,6 +183,17 @@
     }
 
     @Override
+    public void onUrlFocusChange(boolean hasFocus) {
+        Tab currentTab = getToolbarDataProvider().getTab();
+        if (currentTab != null) {
+            currentTab.getActivity().getBottomSheetContentController().onOmniboxFocusChange(
+                    hasFocus);
+        }
+
+        super.onUrlFocusChange(hasFocus);
+    }
+
+    @Override
     protected void triggerUrlFocusAnimation(final boolean hasFocus) {
         super.triggerUrlFocusAnimation(hasFocus);
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java
index 8f7cbfb..41e9937 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java
@@ -178,9 +178,6 @@
     /** A handle to the toolbar control container. */
     private View mControlContainer;
 
-    /** A placeholder for if there is no content in the bottom sheet. */
-    private View mPlaceholder;
-
     /** A handle to the find-in-page toolbar. */
     private View mFindInPageView;
 
@@ -392,6 +389,15 @@
         setSheetState(BottomSheet.SHEET_STATE_HALF, true);
     }
 
+    /**
+     * Immediately end the bottom sheet content transition animations and null the animator.
+     */
+    public void endTransitionAnimations() {
+        if (mContentSwapAnimatorSet == null || !mContentSwapAnimatorSet.isRunning()) return;
+        mContentSwapAnimatorSet.end();
+        mContentSwapAnimatorSet = null;
+    }
+
     @Override
     public boolean onInterceptTouchEvent(MotionEvent e) {
         // If touch is disabled, act like a black hole and consume touch events without doing
@@ -554,13 +560,6 @@
             }
         });
 
-        mPlaceholder = new View(getContext());
-        LayoutParams placeHolderParams =
-                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
-        mPlaceholder.setBackgroundColor(
-                ApiCompatibilityUtils.getColor(getResources(), R.color.default_primary_color));
-        mBottomSheetContentContainer.addView(mPlaceholder, placeHolderParams);
-
         mToolbarHolder = (FrameLayout) mControlContainer.findViewById(R.id.toolbar_holder);
         mDefaultToolbarView = (BottomToolbarPhone) mControlContainer.findViewById(R.id.toolbar);
     }
@@ -654,8 +653,6 @@
         // If the desired content is already showing, do nothing.
         if (mSheetContent == content) return;
 
-        mBottomSheetContentContainer.removeView(mPlaceholder);
-
         View newToolbar =
                 content.getToolbarView() != null ? content.getToolbarView() : mDefaultToolbarView;
         View oldToolbar = mSheetContent != null && mSheetContent.getToolbarView() != null
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetContentController.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetContentController.java
index ef8f6fa..93476608 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetContentController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetContentController.java
@@ -52,7 +52,8 @@
 public class BottomSheetContentController extends BottomNavigationView
         implements OnNavigationItemSelectedListener {
     /** The different types of content that may be displayed in the bottom sheet. */
-    @IntDef({TYPE_SUGGESTIONS, TYPE_DOWNLOADS, TYPE_BOOKMARKS, TYPE_HISTORY, TYPE_INCOGNITO_HOME})
+    @IntDef({TYPE_SUGGESTIONS, TYPE_DOWNLOADS, TYPE_BOOKMARKS, TYPE_HISTORY, TYPE_INCOGNITO_HOME,
+            TYPE_PLACEHOLDER})
     @Retention(RetentionPolicy.SOURCE)
     public @interface ContentType {}
     public static final int TYPE_SUGGESTIONS = 0;
@@ -60,11 +61,16 @@
     public static final int TYPE_BOOKMARKS = 2;
     public static final int TYPE_HISTORY = 3;
     public static final int TYPE_INCOGNITO_HOME = 4;
+    public static final int TYPE_PLACEHOLDER = 5;
 
     // R.id.action_home is overloaded, so an invalid ID is used to reference the incognito version
     // of the home content.
     private static final int INCOGNITO_HOME_ID = -1;
 
+    // Since the placeholder content cannot be triggered by a navigation item like the others, this
+    // value must also be an invalid ID.
+    private static final int PLACEHOLDER_ID = -2;
+
     private final Map<Integer, BottomSheetContent> mBottomSheetContents = new HashMap<>();
 
     private final BottomSheetObserver mBottomSheetObserver = new EmptyBottomSheetObserver() {
@@ -201,6 +207,25 @@
         }
     }
 
+    /**
+     * A notification that the omnibox focus state is changing.
+     * @param hasFocus Whether or not the omnibox has focus.
+     */
+    public void onOmniboxFocusChange(boolean hasFocus) {
+        BottomSheetContent placeHolder = getSheetContentForId(PLACEHOLDER_ID);
+
+        // If the omnibox is being focused, show the placeholder.
+        if (hasFocus && mBottomSheet.getSheetState() != BottomSheet.SHEET_STATE_HALF
+                && mBottomSheet.getSheetState() != BottomSheet.SHEET_STATE_FULL) {
+            mBottomSheet.showContent(placeHolder);
+            mBottomSheet.endTransitionAnimations();
+        }
+
+        if (!hasFocus && mBottomSheet.getCurrentSheetContent() == placeHolder) {
+            showBottomSheetContent(R.id.action_home);
+        }
+    }
+
     @Override
     public boolean onNavigationItemSelected(MenuItem item) {
         if (mSelectedItemId == item.getItemId()) return false;
@@ -251,7 +276,10 @@
             content = new HistorySheetContent(mActivity, mSnackbarManager);
         } else if (navItemId == INCOGNITO_HOME_ID) {
             content = new IncognitoBottomSheetContent(mActivity);
+        } else if (navItemId == PLACEHOLDER_ID) {
+            content = new PlaceholderSheetContent(getContext());
         }
+
         mBottomSheetContents.put(navItemId, content);
         return content;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetMetrics.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetMetrics.java
index 8cbbd17..05e8a147b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetMetrics.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetMetrics.java
@@ -125,6 +125,8 @@
             RecordUserAction.record("Android.ChromeHome.ShowHistory");
         } else if (newContent.getType() == BottomSheetContentController.TYPE_INCOGNITO_HOME) {
             RecordUserAction.record("Android.ChromeHome.ShowIncognitoHome");
+        } else if (newContent.getType() == BottomSheetContentController.TYPE_PLACEHOLDER) {
+            // Intentionally do nothing; the placeholder is not user triggered.
         } else {
             assert false;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/PlaceholderSheetContent.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/PlaceholderSheetContent.java
new file mode 100644
index 0000000..551fab1b
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/PlaceholderSheetContent.java
@@ -0,0 +1,59 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.widget.bottomsheet;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.chrome.R;
+
+/**
+ * This class is used as a placeholder when there should otherwise be no content in the bottom
+ * sheet.
+ */
+class PlaceholderSheetContent implements BottomSheet.BottomSheetContent {
+    /** The view that represents this placeholder. */
+    private View mView;
+
+    public PlaceholderSheetContent(Context context) {
+        mView = new View(context);
+        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
+        mView.setLayoutParams(params);
+        mView.setBackgroundColor(ApiCompatibilityUtils.getColor(
+                context.getResources(), R.color.default_primary_color));
+    }
+
+    @Override
+    public View getContentView() {
+        return mView;
+    }
+
+    @Override
+    public View getToolbarView() {
+        return null;
+    }
+
+    @Override
+    public boolean isUsingLightToolbarTheme() {
+        return false;
+    }
+
+    @Override
+    public int getVerticalScrollOffset() {
+        return 0;
+    }
+
+    @Override
+    public void destroy() {}
+
+    @Override
+    @BottomSheetContentController.ContentType
+    public int getType() {
+        return BottomSheetContentController.TYPE_PLACEHOLDER;
+    }
+}
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 51f444e..5b309cbe 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -1236,6 +1236,7 @@
   "java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetMetrics.java",
   "java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetObserver.java",
   "java/src/org/chromium/chrome/browser/widget/bottomsheet/EmptyBottomSheetObserver.java",
+  "java/src/org/chromium/chrome/browser/widget/bottomsheet/PlaceholderSheetContent.java",
   "java/src/org/chromium/chrome/browser/widget/displaystyle/MarginResizer.java",
   "java/src/org/chromium/chrome/browser/widget/displaystyle/DisplayStyleObserver.java",
   "java/src/org/chromium/chrome/browser/widget/displaystyle/DisplayStyleObserverAdapter.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
index affa661..eef5209 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
@@ -71,6 +71,7 @@
 import org.chromium.chrome.browser.history.HistoryItem;
 import org.chromium.chrome.browser.history.TestBrowsingHistoryObserver;
 import org.chromium.chrome.browser.metrics.PageLoadMetrics;
+import org.chromium.chrome.browser.preferences.PrefServiceBridge;
 import org.chromium.chrome.browser.prerender.ExternalPrerenderHandler;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
@@ -1942,6 +1943,38 @@
                 CustomTabsConnection.SpeculationParams.PRERENDER);
     }
 
+    /**
+     * Test that hidden tab speculation is not performed if 3rd party cookies are blocked.
+     **/
+    @SmallTest
+    @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
+    @RetryOnFailure
+    @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+            "enable-features=" + ChromeFeatureList.CCT_BACKGROUND_TAB})
+    public void testHiddenTabThirdPartyCookiesBlocked() throws Exception {
+        final CustomTabsConnection connection = warmUpAndWait();
+        final CustomTabsSessionToken token =
+                CustomTabsSessionToken.createDummySessionTokenForTesting();
+        connection.newSession(token);
+        connection.setSpeculationModeForSession(
+                token, CustomTabsConnection.SpeculationParams.HIDDEN_TAB);
+        connection.warmup(0);
+
+        // Needs the browser process to be initialized.
+        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+            @Override
+            public void run() {
+                PrefServiceBridge prefs = PrefServiceBridge.getInstance();
+                boolean old_block_pref = prefs.isBlockThirdPartyCookiesEnabled();
+                prefs.setBlockThirdPartyCookiesEnabled(false);
+                assertTrue(connection.maySpeculate(token));
+                prefs.setBlockThirdPartyCookiesEnabled(true);
+                assertFalse(connection.maySpeculate(token));
+                prefs.setBlockThirdPartyCookiesEnabled(old_block_pref);
+            }
+        });
+    }
+
     private void testSpeculateCorrectUrl(int speculationMode) throws Exception {
         testSpeculateCorrectUrl(speculationMode, speculationMode);
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/history/HistoryActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/history/HistoryActivityTest.java
index e52f2f8..f56cf10a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/history/HistoryActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/history/HistoryActivityTest.java
@@ -26,6 +26,7 @@
 import org.chromium.base.test.BaseActivityInstrumentationTestCase;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.Restriction;
+import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.IntentHandler;
 import org.chromium.chrome.browser.preferences.PrefServiceBridge;
@@ -269,6 +270,7 @@
     }
 
     @SmallTest
+    @RetryOnFailure(message = "crbug.com/718689")
     public void testOpenSelectedItems() throws Exception {
         IntentFilter filter = new IntentFilter(Intent.ACTION_VIEW);
         filter.addDataPath(mItem1.getUrl(), PatternMatcher.PATTERN_LITERAL);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/EmulatedVrController.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/EmulatedVrController.java
index 304169a..60352ac 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/EmulatedVrController.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/EmulatedVrController.java
@@ -5,9 +5,12 @@
 package org.chromium.chrome.browser.vr_shell;
 
 import android.content.Context;
+import android.os.SystemClock;
 
 import com.google.vr.testframework.controller.ControllerTestApi;
 
+import java.util.concurrent.TimeUnit;
+
 /**
  * Wrapper for the ControllerTestApi class to handle more complex actions such
  * as clicking and dragging.
@@ -36,5 +39,110 @@
         mApi.buttonEvent.sendClickButtonEvent();
     }
 
+    /**
+     * Holds the home button to recenter the view using an arbitrary, but valid
+     * orientation quaternion.
+     */
+    public void recenterView() {
+        mApi.buttonEvent.sendHomeButtonToggleEvent();
+        // A valid position must be sent a short time after the home button
+        // is pressed in order for recentering to actually complete, and no
+        // way to be signalled that we should send the event, so sleep
+        SystemClock.sleep(500);
+        // We don't care where the controller is pointing when recentering occurs as long
+        // as it results in a successful recenter, so send an arbitrary, valid orientation
+        mApi.moveEvent.sendMoveEvent(0.0f, 0.0f, 0.0f, 1.0f);
+        mApi.buttonEvent.sendHomeButtonToggleEvent();
+    }
+
+    /**
+     * Performs a short home button press/release, which launches the Daydream Home app.
+     */
+    public void goToDaydreamHome() {
+        mApi.buttonEvent.sendShortHomeButtonEvent();
+    }
+
+    /**
+     * Simulates a touch down-drag-touch up sequence on the touchpad between two points.
+     *
+     * @param xStart the x coordinate to start the touch sequence at, in range [0.0f, 1.0f]
+     * @param yStart the y coordinate to start the touch sequence at, in range [0.0f, 1.0f]
+     * @param xEnd the x coordinate to end the touch sequence at, in range [0.0f, 1.0f]
+     * @param yEnd the y coordinate to end the touch sequence at, in range [0.0f, 1.0f]
+     * @param steps the number of steps the drag will have
+     * @param speed how long to wait between steps in the sequence. Generally, higher numbers
+     * result in faster movement, e.g. when used for scrolling, a higher number results in faster
+     * scrolling.
+     */
+    public void performLinearTouchpadMovement(
+            float xStart, float yStart, float xEnd, float yEnd, int steps, int speed) {
+        // Touchpad events have timestamps attached to them in nanoseconds - for smooth scrolling,
+        // the timestamps should increase at a similar rate to the amount of time we actually wait
+        // between sending events, which is determined by the given speed.
+        long simulatedDelay = TimeUnit.MILLISECONDS.toNanos(speed);
+        long timestamp = mApi.touchEvent.startTouchSequence(xStart, yStart, simulatedDelay, speed);
+        timestamp = mApi.touchEvent.dragFromTo(
+                xStart, yStart, xEnd, yEnd, steps, timestamp, simulatedDelay, speed);
+        mApi.touchEvent.endTouchSequence(xEnd, yEnd, timestamp, simulatedDelay, speed);
+    }
+
+    /**
+     * Performs an upward swipe on the touchpad, which scrolls down in VR Shell.
+     * Note that scrolling this way is not consistent, i.e. scrolling down then
+     * scrolling up at the same speed won't necessarily scroll back to the exact
+     * starting position on the page.
+     *
+     * @param steps the number of intermediate steps to send while scrolling
+     * @param speed how long to wait between steps in the scroll, with higher
+     * numbers resulting in a faster scroll
+     */
+    public void scrollDown(int steps, int speed) {
+        performLinearTouchpadMovement(0.5f, 0.9f, 0.5f, 0.1f, steps, speed);
+    }
+
+    /**
+     * Performs a downward swipe on the touchpad, which scrolls up in VR Shell.
+     * Note that scrolling this way is not consistent, i.e. scrolling down then
+     * scrolling up at the same speed won't necessarily scroll back to the exact
+     * starting position on the page.
+     *
+     * @param steps the number of intermediate steps to send while scrolling
+     * @param speed how long to wait between steps in the scroll, with higher
+     * numbers resulting in a faster scroll
+     */
+    public void scrollUp(int steps, int speed) {
+        performLinearTouchpadMovement(0.5f, 0.1f, 0.5f, 0.9f, steps, speed);
+    }
+
+    /**
+     * Instantly moves the controller to the specified quaternion coordinates.
+     *
+     * @param x the x component of the quaternion
+     * @param y the y component of the quaternion
+     * @param z the z component of the quaternion
+     * @param w the w component of the quaternion
+     */
+    public void moveControllerInstant(float x, float y, float z, float w) {
+        mApi.moveEvent.sendMoveEvent(x, y, z, w, 0);
+    }
+
+    /**
+     * Moves the controller from one position to another over a period of time.
+     *
+     * @param startAngles the x/y/z angles to start the controller at, in radians
+     * @param endAngles the x/y/z angles to end the controller at, in radians
+     * @param steps the number of steps the controller will take moving between the positions
+     * @param delayBetweenSteps how long to sleep between positions
+     */
+    public void moveControllerInterpolated(
+            float[] startAngles, float[] endAngles, int steps, int delayBetweenSteps) {
+        if (startAngles.length != 3 || endAngles.length != 3) {
+            throw new IllegalArgumentException("Angle arrays must be length 3");
+        }
+        mApi.moveEvent.sendMoveEvent(new float[] {startAngles[0], endAngles[0]},
+                new float[] {startAngles[1], endAngles[1]},
+                new float[] {startAngles[2], endAngles[2]}, steps, delayBetweenSteps);
+    }
+
     // TODO(bsheedy): Add support for more complex actions, e.g. click/drag/release
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/VrShellTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/VrShellTest.java
index db02139..453deab 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/VrShellTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr_shell/VrShellTest.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.vr_shell;
 
+import static org.chromium.chrome.browser.vr_shell.VrUtils.POLL_CHECK_INTERVAL_LONG_MS;
 import static org.chromium.chrome.browser.vr_shell.VrUtils.POLL_TIMEOUT_LONG_MS;
 import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_DEVICE_DAYDREAM;
 import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_DEVICE_NON_DAYDREAM;
@@ -192,6 +193,45 @@
     }
 
     /**
+     * Verifies that swiping up/down on the Daydream controller's touchpad scrolls
+     * the webpage while in the VR browser.
+     */
+    @Restriction({RESTRICTION_TYPE_DEVICE_DAYDREAM, RESTRICTION_TYPE_VIEWER_DAYDREAM})
+    @MediumTest
+    public void testControllerScrolling() throws InterruptedException {
+        // Load page in VR and make sure the controller is pointed at the content quad
+        loadUrl("chrome://credits", PAGE_LOAD_TIMEOUT_S);
+        VrUtils.forceEnterVr();
+        VrUtils.waitForVrSupported(POLL_TIMEOUT_LONG_MS);
+        EmulatedVrController controller = new EmulatedVrController(getActivity());
+        final ContentViewCore cvc = getActivity().getActivityTab().getActiveContentViewCore();
+        controller.recenterView();
+
+        // Wait for the page to be scrollable
+        CriteriaHelper.pollUiThread(new Criteria() {
+            @Override
+            public boolean isSatisfied() {
+                return cvc.computeVerticalScrollRange() > cvc.getContainerView().getHeight();
+            }
+        }, POLL_TIMEOUT_LONG_MS, POLL_CHECK_INTERVAL_LONG_MS);
+
+        // Test that scrolling down works
+        int startScrollY = cvc.getNativeScrollYForTest();
+        // Arbitrary, but valid values to scroll smoothly
+        int scrollSteps = 20;
+        int scrollSpeed = 60;
+        controller.scrollDown(scrollSteps, scrollSpeed);
+        int endScrollY = cvc.getNativeScrollYForTest();
+        assertTrue("Controller was able to scroll down", startScrollY < endScrollY);
+
+        // Test that scrolling up works
+        startScrollY = endScrollY;
+        controller.scrollUp(scrollSteps, scrollSpeed);
+        endScrollY = cvc.getNativeScrollYForTest();
+        assertTrue("Controller was able to scroll up", startScrollY > endScrollY);
+    }
+
+    /**
      * Verify that resizing the CompositorViewHolder does not cause the current tab to resize while
      * the CompositorViewHolder is detached from the TabModelSelector. See crbug.com/680240.
      * @throws InterruptedException
diff --git a/chrome/app/chromium_strings.grd b/chrome/app/chromium_strings.grd
index 76ebfc5f..3dcc6dc 100644
--- a/chrome/app/chromium_strings.grd
+++ b/chrome/app/chromium_strings.grd
@@ -764,13 +764,28 @@
         </message>
       </if>
 
-      <!-- SRT bubble messages -->
+      <!-- SRT bubble messages.
+           This string is for the old Chrome Cleanup version and should be
+           deleted as soon as all Chrome Cleanup UI is fully integrated into
+           Chrome. -->
       <if expr="is_win">
         <message name="IDS_SRT_BUBBLE_TITLE" desc="Text for the title of the software removal tool bubble view.">
             Chromium has detected unusual behavior
         </message>
       </if>
 
+      <!-- Chrome Cleanup prompt and web UI.
+           Note: these strings should only be used by Google Chrome, but
+                 omitting them brings up a hash collision error. -->
+      <if expr="is_win">
+        <message name="IDS_CHROME_CLEANUP_PROMPT_EXPLANATION" desc="Description in the Chrome Cleanup dialog that Chrome browser shows when unwanted software, like ad injectors or software that changes the user's settings without their knowledge, is found on the user's computer. Appears under the title asking 'Remove harmful software?' Actor is Chrome; we are asking, Do you want Chrome to remove harmful software? 'it' is harmful software. User has the option of clicking 'Remove' to proceed with a cleanup, or 'Details' to see more details. Preferrably, the translation for this string should have the same meaning as IDS_CHROME_CLEANUP_WEBUI_EXPLANATION.">
+          Chromium found harmful software on your computer. Chromium can remove it and restore your settings to make your browser work normally again.
+        </message>
+        <message name="IDS_CHROME_CLEANUP_WEBUI_EXPLANATION" desc="Description in the Chrome Cleanup web page that Chrome browser shows when unwanted software, like ad injectors or software that changes the user's settings without their knowledge, is found on the user's computer. Appears under the title asking 'Remove harmful software?' Actor is Chrome; we are asking, Do you want Chrome to remove harmful software? 'it' is harmful software. The user can click 'Remove' to proceed with a cleanup. Preferrably, the translation for this string should have the same meaning as IDS_CHROME_CLEANUP_PROMPT_EXPLANATION.">
+          Chromium found harmful software on your computer. Chromium can remove it and restore your settings to make your browser work normally again.
+        </message>
+      </if>
+
       <!-- Sync/sign-in error messages -->
       <if expr="not chromeos">
         <message name="IDS_SYNC_PASSPHRASE_ERROR_BUBBLE_VIEW_MESSAGE" desc="Message in the sync error bubble view when the user needs to update their sync passphrase.">
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 559fc69b..f2d48a5a 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -6358,7 +6358,10 @@
         Report an issue
       </message>
 
-      <!-- SRT bubble messages -->
+      <!-- SRT bubble messages.
+           These strings are for the old Chrome Cleanup version and should be
+           deleted as soon as all Chrome Cleanup UI is fully integrated into
+           Chrome. -->
       <message name="IDS_SRT_BUBBLE_DOWNLOAD_BUTTON_TEXT" desc="Download button of the Chrome Cleanup Tool bubble">
         Get the Chrome Cleanup Tool
       </message>
@@ -6385,6 +6388,49 @@
         </message>
       </if>
 
+      <!-- Chrome Cleanup prompt and web UI -->
+      <if expr="is_win">
+        <message name="IDS_CHROME_CLEANUP_PROMPT_DETAILS_BUTTON_LABEL" desc="The button in the Chrome Cleanup dialog that lets users see more details, such as what files will be deleted from their computer. Chrome browser shows the dialog when unwanted software, like ad injectors or software that changes the user's settings without their knowledge, is found on the user's computer. Noun; verb 'See' is omitted.">
+          Details
+        </message>
+        <message name="IDS_CHROME_CLEANUP_PROMPT_REMOVE_BUTTON_LABEL" desc="A button in the Chrome Cleanup dialog. When clicked, Chrome will begin to remove unwanted software and restore browser settings to default values. Appears next to a 'Cancel' button and a 'Details' button that opens a page that shows which unwanted software will be deleted. 'Remove' is imperative.">
+          Remove
+        </message>
+        <message name="IDS_CHROME_CLEANUP_PROMPT_TITLE" desc="Title of the Chrome Cleanup dialog. Chrome browser shows the dialog when unwanted software, like ad injectors or software that changes the user's settings without their knowledge, is found on the user's computer. Appears above a description of what the cleanup dialog does, along with a 'Remove', 'Cancel', and 'Details' button. 'Remove' is imperative, asking the user, do you want Chrome to remove harmful software?">
+          Remove harmful software?
+        </message>
+        <message name="IDS_CHROME_CLEANUP_WEBUI_DONE_BUTTON_LABEL" desc="A button on the web page for Chrome Cleanup. Clicking this button causes the card on the settings page to be dismissed.">
+          Done
+        </message>
+        <message name="IDS_CHROME_CLEANUP_WEBUI_FOOTER_POWERED_BY" desc="Branding footer on the web page for Chrome Cleanup. COMPANY_NAME is the name of a technology service that Chrome uses to remove harmful software from the user's computer. It is not necessary to translate 'powered by' literally; use an appropriate word for your language to convey the meaning of 'uses'. A similar use in other Google products is 'Powered by Google Translate.'">
+          Powered by <ph name="COMPANY_NAME">$1<ex>Google</ex></ph>
+        </message>
+        <message name="IDS_CHROME_CLEANUP_WEBUI_LINK_SHOW_FILES" desc="A link, appearing on the Chrome Cleanup web page, that user can click to show the files that will be removed. Imperative.">
+          Show files to be removed
+        </message>
+        <message name="IDS_CHROME_CLEANUP_WEBUI_REMOVE_BUTTON_LABEL" desc="Button on the Chrome Cleanup web page. Allows users to start a cleanup of unwanted software on their computer and restore browser settings to default values. 'Remove' is imperative.">
+          Remove
+        </message>
+        <message name="IDS_CHROME_CLEANUP_WEBUI_RESTART_BUTTON_LABEL" desc="Button on the web page for Chrome Cleanup. Clicking this button causes the user's computer to turn off (shut down) and turn back on again. Imperative. ">
+          Restart computer
+        </message>
+        <message name="IDS_CHROME_CLEANUP_WEBUI_TITLE_ERROR_CANT_REMOVE" desc="An error message, appearing on the Chrome Cleanup web page, that Chrome tried to clean up unwanted software, as requested by the user, but was unsuccessful. Omits subject, i.e. Chrome can't remove harmful software.">
+          Can't remove harmful software
+        </message>
+        <message name="IDS_CHROME_CLEANUP_WEBUI_TITLE_REMOVE" desc="Title of the Chrome Cleanup web page. Chrome browser shows the webpage when unwanted software, like ad injectors or software that changes the user's settings without their knowledge, is found on the user's computer. Appears above a description of what the cleanup does. 'Remove' is imperative.">
+          Remove harmful software
+        </message>
+        <message name="IDS_CHROME_CLEANUP_WEBUI_TITLE_REMOVED" desc="A confirmation, appearing on the Chrome Cleanup web page, that the cleanup of unwanted software is finished. Omits 'was', i.e. Harmful software was removed. Appears next to a Done button, which lets user dismiss the confirmation.">
+          Harmful software removed
+        </message>
+        <message name="IDS_CHROME_CLEANUP_WEBUI_TITLE_REMOVING" desc="A status message, appearing on the Chrome cleanup web page, that the cleanup of unwanted software initiated by the user is in progress. Omits subject, i.e. 'Chrome is removing harmful software.' If appropriate for your language, include ellipsis to show that the operation is in progress.">
+          Removing harmful software...
+        </message>
+        <message name="IDS_CHROME_CLEANUP_WEBUI_TITLE_RESTART" desc="A status message, appearing on the Chrome Cleanup web page, that the cleanup of unwanted software initiated by the user was performed but isn't finished. User must take additional action to finish the cleanup. 'Restart' is imperative. Short for 'To finish removing harmful software...'">
+          To finish, restart your computer
+        </message>
+      </if>
+
       <!-- Settings reset prompt dialog messages -->
       <message name="IDS_SETTINGS_RESET_PROMPT_TITLE_SEARCH_ENGINE" desc="The title for the settings reset dialog when the user's search engine setting has been modified. The dialog will ask users if they want to restore their modified setting to its original default value.">
         Restore default search engine?
diff --git a/chrome/app/google_chrome_strings.grd b/chrome/app/google_chrome_strings.grd
index 4e7e26b..cc07240 100644
--- a/chrome/app/google_chrome_strings.grd
+++ b/chrome/app/google_chrome_strings.grd
@@ -768,13 +768,26 @@
         </message>
       </if>
 
-      <!-- SRT bubble messages -->
+      <!-- SRT bubble messages.
+           This string is for the old Chrome Cleanup version and should be
+           deleted as soon as all Chrome Cleanup UI is fully integrated into
+           Chrome. -->
       <if expr="is_win">
         <message name="IDS_SRT_BUBBLE_TITLE" desc="Text for the title of the software removal tool bubble view.">
             Chrome has detected unusual behavior
         </message>
       </if>
 
+      <!-- Chrome Cleanup prompt and web UI -->
+      <if expr="is_win">
+        <message name="IDS_CHROME_CLEANUP_PROMPT_EXPLANATION" desc="Description in the Chrome Cleanup dialog that Chrome browser shows when unwanted software, like ad injectors or software that changes the user's settings without their knowledge, is found on the user's computer. Appears under the title asking 'Remove harmful software?' Actor is Chrome; we are asking, Do you want Chrome to remove harmful software? 'it' is harmful software. User has the option of clicking 'Remove' to proceed with a cleanup, or 'Details' to see more details. Preferrably, the translation for this string should match IDS_CHROME_CLEANUP_WEBUI_EXPLANATION.">
+          Chrome found harmful software on your computer. Chrome can remove it and restore your settings to make your browser work normally again.
+        </message>
+        <message name="IDS_CHROME_CLEANUP_WEBUI_EXPLANATION" desc="Description in the Chrome Cleanup web page that Chrome browser shows when unwanted software, like ad injectors or software that changes the user's settings without their knowledge, is found on the user's computer. Appears under the title asking 'Remove harmful software.' Actor is Chrome; we are asking, Do you want Chrome to remove harmful software? 'it' is harmful software. The user can click 'Remove' to proceed with a cleanup. Preferrably, the translation for this string should match IDS_CHROME_CLEANUP_PROMPT_EXPLANATION.">
+          Chrome found harmful software on your computer. Chrome can remove it and restore your settings to make your browser work normally again.
+        </message>
+      </if>
+
       <!-- Sync/sign-in error messages -->
       <if expr="not chromeos">
         <message name="IDS_SYNC_PASSPHRASE_ERROR_BUBBLE_VIEW_MESSAGE" desc="Message in the sync error bubble view when the user needs to update their sync passphrase.">
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index cdb9db1..6dcf752f0 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1149,12 +1149,14 @@
     "resource_delegate_mac.mm",
     "resources_util.cc",
     "resources_util.h",
+    "safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.cc",
+    "safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.h",
+    "safe_browsing/chrome_cleaner/reporter_runner_win.cc",
+    "safe_browsing/chrome_cleaner/reporter_runner_win.h",
     "safe_browsing/chrome_cleaner/srt_chrome_prompt_impl.cc",
     "safe_browsing/chrome_cleaner/srt_chrome_prompt_impl.h",
     "safe_browsing/chrome_cleaner/srt_client_info_win.cc",
     "safe_browsing/chrome_cleaner/srt_client_info_win.h",
-    "safe_browsing/chrome_cleaner/srt_fetcher_win.cc",
-    "safe_browsing/chrome_cleaner/srt_fetcher_win.h",
     "safe_browsing/chrome_cleaner/srt_field_trial_win.cc",
     "safe_browsing/chrome_cleaner/srt_field_trial_win.h",
     "safe_browsing/chrome_cleaner/srt_global_error_win.cc",
diff --git a/chrome/browser/android/vr_shell/BUILD.gn b/chrome/browser/android/vr_shell/BUILD.gn
index 4115f78..7823b77 100644
--- a/chrome/browser/android/vr_shell/BUILD.gn
+++ b/chrome/browser/android/vr_shell/BUILD.gn
@@ -110,6 +110,10 @@
       "//ui/vector_icons",
     ]
 
+    public_deps = [
+      "//device/vr:mojo_bindings",
+    ]
+
     libs = [
       "//third_party/gvr-android-sdk/libgvr_shim_static_${current_cpu}.a",
       "android",
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index ec142f36..f16ea78 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -1359,6 +1359,8 @@
     "system_logs/device_event_log_source.h",
     "system_logs/lsb_release_log_source.cc",
     "system_logs/lsb_release_log_source.h",
+    "system_logs/single_log_source.cc",
+    "system_logs/single_log_source.h",
     "system_logs/touch_log_source.h",
     "system_logs/touch_log_source_ozone.cc",
     "system_logs/touch_log_source_x11.cc",
@@ -1759,6 +1761,7 @@
     "status/data_promo_notification_unittest.cc",
     "system/automatic_reboot_manager_unittest.cc",
     "system/device_disabling_manager_unittest.cc",
+    "system_logs/single_log_source_unittest.cc",
     "tether/tether_service_unittest.cc",
     "ui/accessibility_focus_ring_controller_unittest.cc",
     "ui/idle_app_name_notification_view_unittest.cc",
diff --git a/chrome/browser/chromeos/system_logs/single_log_source.cc b/chrome/browser/chromeos/system_logs/single_log_source.cc
new file mode 100644
index 0000000..4359af8
--- /dev/null
+++ b/chrome/browser/chromeos/system_logs/single_log_source.cc
@@ -0,0 +1,88 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/system_logs/single_log_source.h"
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/task_scheduler/post_task.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace system_logs {
+
+namespace {
+
+// Converts a logs source type to the corresponding filename. In the future, if
+// non-file source types are added, this function should return an empty string.
+std::string GetLogFileSourceFilename(SingleLogSource::SupportedSource source) {
+  switch (source) {
+    case SingleLogSource::SupportedSource::kMessages:
+      return "/var/log/messages";
+    case SingleLogSource::SupportedSource::kUiLatest:
+      return "/var/log/ui/ui.LATEST";
+  }
+}
+
+}  // namespace
+
+SingleLogSource::SingleLogSource(SupportedSource source)
+    : SystemLogsSource(GetLogFileSourceFilename(source)),
+      num_bytes_read_(0),
+      weak_ptr_factory_(this) {}
+
+SingleLogSource::~SingleLogSource() {}
+
+void SingleLogSource::Fetch(const SysLogsSourceCallback& callback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK(!callback.is_null());
+
+  SystemLogsResponse* response = new SystemLogsResponse;
+  base::PostTaskWithTraitsAndReply(
+      FROM_HERE,
+      base::TaskTraits(base::MayBlock(), base::TaskPriority::BACKGROUND),
+      base::Bind(&SingleLogSource::ReadFile, weak_ptr_factory_.GetWeakPtr(),
+                 response),
+      base::Bind(callback, base::Owned(response)));
+}
+
+void SingleLogSource::ReadFile(SystemLogsResponse* result) {
+  // Attempt to open the file if it was not previously opened.
+  if (!file_.IsValid()) {
+    file_.Initialize(base::FilePath(source_name()),
+                     base::File::FLAG_OPEN | base::File::FLAG_READ);
+    if (!file_.IsValid())
+      return;
+  }
+
+  // Check for file size reset.
+  const size_t length = file_.GetLength();
+  if (length < num_bytes_read_) {
+    num_bytes_read_ = 0;
+    file_.Seek(base::File::FROM_BEGIN, 0);
+  }
+
+  // Read from file until end.
+  const size_t size_to_read = length - num_bytes_read_;
+  std::string result_string;
+  result_string.resize(size_to_read);
+  const size_t size_read =
+      file_.ReadAtCurrentPos(&result_string[0], size_to_read);
+  result_string.resize(size_read);
+
+  // The reader may only read complete lines.
+  if (result_string.empty() || result_string.back() != '\n') {
+    // If an incomplete line was read, reset the file read offset to before the
+    // most recent read.
+    file_.Seek(base::File::FROM_CURRENT, -size_read);
+    result->emplace(source_name(), "");
+    return;
+  }
+
+  num_bytes_read_ += size_read;
+
+  // Pass it back to the callback.
+  result->emplace(source_name(), anonymizer_.Anonymize(result_string));
+}
+
+}  // namespace system_logs
diff --git a/chrome/browser/chromeos/system_logs/single_log_source.h b/chrome/browser/chromeos/system_logs/single_log_source.h
new file mode 100644
index 0000000..18b4cc5f
--- /dev/null
+++ b/chrome/browser/chromeos/system_logs/single_log_source.h
@@ -0,0 +1,56 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_SYSTEM_LOGS_SINGLE_LOG_SOURCE_H_
+#define CHROME_BROWSER_CHROMEOS_SYSTEM_LOGS_SINGLE_LOG_SOURCE_H_
+
+#include <stddef.h>
+
+#include "base/files/file.h"
+#include "base/macros.h"
+#include "chrome/browser/feedback/system_logs/system_logs_fetcher_base.h"
+#include "components/feedback/anonymizer_tool.h"
+
+namespace system_logs {
+
+// Gathers log data from a single source, possibly incrementally.
+class SingleLogSource : public SystemLogsSource {
+ public:
+  enum class SupportedSource {
+    // For /var/log/messages.
+    kMessages,
+
+    // For /var/log/ui/ui.LATEST.
+    kUiLatest,
+  };
+
+  explicit SingleLogSource(SupportedSource source);
+  ~SingleLogSource() override;
+
+  // system_logs::SystemLogsSource:
+  void Fetch(const SysLogsSourceCallback& callback) override;
+
+ private:
+  friend class SingleLogSourceTest;
+
+  // Reads all available content from |file_| that has not already been read.
+  void ReadFile(SystemLogsResponse* result);
+
+  // Keeps track of how much data has been read from |file_|.
+  size_t num_bytes_read_;
+
+  // Handle for reading the log file that is source of logging data.
+  base::File file_;
+
+  // For removing PII from log results.
+  feedback::AnonymizerTool anonymizer_;
+
+  base::WeakPtrFactory<SingleLogSource> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(SingleLogSource);
+};
+
+}  // namespace system_logs
+
+#endif  // CHROME_BROWSER_CHROMEOS_SYSTEM_LOGS_SINGLE_LOG_SOURCE_H_
diff --git a/chrome/browser/chromeos/system_logs/single_log_source_unittest.cc b/chrome/browser/chromeos/system_logs/single_log_source_unittest.cc
new file mode 100644
index 0000000..2ccda01a9
--- /dev/null
+++ b/chrome/browser/chromeos/system_logs/single_log_source_unittest.cc
@@ -0,0 +1,248 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/system_logs/single_log_source.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace system_logs {
+
+class SingleLogSourceTest : public ::testing::Test {
+ public:
+  SingleLogSourceTest()
+      : scoped_task_environment_(
+            base::test::ScopedTaskEnvironment::MainThreadType::UI),
+        source_(SingleLogSource::SupportedSource::kMessages),
+        num_callback_calls_(0) {
+    CHECK(dir_.CreateUniqueTempDir());
+    log_file_path_ = dir_.GetPath().Append("log_file");
+
+    // Create the dummy log file for writing.
+    base::File new_file;
+    new_file.Initialize(log_file_path_, base::File::FLAG_CREATE_ALWAYS |
+                                            base::File::FLAG_WRITE);
+    new_file.Close();
+    CHECK(base::PathExists(log_file_path_));
+
+    // Open the dummy log file for reading from within the log source.
+    source_.file_.Initialize(log_file_path_,
+                             base::File::FLAG_OPEN | base::File::FLAG_READ);
+    CHECK(source_.file_.IsValid());
+  }
+
+  ~SingleLogSourceTest() override {}
+
+  // Writes a string to |log_file_path_|.
+  bool WriteFile(const std::string& input) {
+    return base::WriteFile(log_file_path_, input.data(), input.size());
+  }
+  // Appends a string to |log_file_path_|.
+  bool AppendToFile(const std::string& input) {
+    return base::AppendToFile(log_file_path_, input.data(), input.size());
+  }
+
+  // Calls source_.Fetch() to start a logs fetch operation. Passes in
+  // OnFileRead() as a callback. Runs until Fetch() has completed.
+  void FetchFromSource() {
+    source_.Fetch(
+        base::Bind(&SingleLogSourceTest::OnFileRead, base::Unretained(this)));
+    scoped_task_environment_.RunUntilIdle();
+  }
+
+  // Callback for fetching logs from |source_|. Overwrites the previous stored
+  // value of |latest_response_|.
+  void OnFileRead(SystemLogsResponse* response) {
+    ++num_callback_calls_;
+    if (response->empty())
+      return;
+
+    // Since |source_| represents a single log source, it should only return a
+    // single string result.
+    EXPECT_EQ(1U, response->size());
+    latest_response_ = std::move(response->begin()->second);
+  }
+
+  int num_callback_calls() const { return num_callback_calls_; }
+
+  const std::string& latest_response() const { return latest_response_; }
+
+  const base::FilePath& log_file_path() const { return log_file_path_; }
+
+ private:
+  // For running scheduled tasks.
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+
+  // Creates the necessary browser threads. Defined after
+  // |scoped_task_environment_| in order to use the MessageLoop it created.
+  content::TestBrowserThreadBundle browser_thread_bundle_;
+
+  // Unit under test.
+  SingleLogSource source_;
+
+  // Counts the number of times that |source_| has invoked the callback.
+  int num_callback_calls_;
+
+  // Stores the string response returned from |source_| the last time it invoked
+  // OnFileRead.
+  std::string latest_response_;
+
+  // Temporary dir for creating a dummy log file.
+  base::ScopedTempDir dir_;
+
+  // Path to the dummy log file in |dir_|.
+  base::FilePath log_file_path_;
+
+  DISALLOW_COPY_AND_ASSIGN(SingleLogSourceTest);
+};
+
+TEST_F(SingleLogSourceTest, EmptyFile) {
+  FetchFromSource();
+
+  EXPECT_EQ(1, num_callback_calls());
+  EXPECT_EQ("", latest_response());
+}
+
+TEST_F(SingleLogSourceTest, SingleRead) {
+  EXPECT_TRUE(AppendToFile("Hello world!\n"));
+  FetchFromSource();
+
+  EXPECT_EQ(1, num_callback_calls());
+  EXPECT_EQ("Hello world!\n", latest_response());
+}
+
+TEST_F(SingleLogSourceTest, IncrementalReads) {
+  EXPECT_TRUE(AppendToFile("Hello world!\n"));
+  FetchFromSource();
+
+  EXPECT_EQ(1, num_callback_calls());
+  EXPECT_EQ("Hello world!\n", latest_response());
+
+  EXPECT_TRUE(AppendToFile("The quick brown fox jumps over the lazy dog\n"));
+  FetchFromSource();
+
+  EXPECT_EQ(2, num_callback_calls());
+  EXPECT_EQ("The quick brown fox jumps over the lazy dog\n", latest_response());
+
+  EXPECT_TRUE(AppendToFile("Some like it hot.\nSome like it cold\n"));
+  FetchFromSource();
+
+  EXPECT_EQ(3, num_callback_calls());
+  EXPECT_EQ("Some like it hot.\nSome like it cold\n", latest_response());
+
+  // As a sanity check, read entire contents of file separately to make sure it
+  // was written incrementally, and hence read incrementally.
+  std::string file_contents;
+  EXPECT_TRUE(base::ReadFileToString(log_file_path(), &file_contents));
+  EXPECT_EQ(
+      "Hello world!\nThe quick brown fox jumps over the lazy dog\n"
+      "Some like it hot.\nSome like it cold\n",
+      file_contents);
+}
+
+// The log files read by SingleLogSource are not expected to be overwritten.
+// This test is just to ensure that the SingleLogSource class is robust enough
+// not to break in the event of an overwrite.
+TEST_F(SingleLogSourceTest, FileOverwrite) {
+  EXPECT_TRUE(AppendToFile("0123456789\n"));
+  FetchFromSource();
+
+  EXPECT_EQ(1, num_callback_calls());
+  EXPECT_EQ("0123456789\n", latest_response());
+
+  // Overwrite the file.
+  EXPECT_TRUE(WriteFile("abcdefg\n"));
+  FetchFromSource();
+
+  // Should re-read from the beginning.
+  EXPECT_EQ(2, num_callback_calls());
+  EXPECT_EQ("abcdefg\n", latest_response());
+
+  // Append to the file to make sure incremental read still works.
+  EXPECT_TRUE(AppendToFile("hijk\n"));
+  FetchFromSource();
+
+  EXPECT_EQ(3, num_callback_calls());
+  EXPECT_EQ("hijk\n", latest_response());
+
+  // Overwrite again, this time with a longer length than the existing file.
+  // Previous contents:
+  //   abcdefg~hijk~     <-- "~" is a single-char representation of newline.
+  // New contents:
+  //   lmnopqrstuvwxyz~  <-- excess text beyond end of prev contents: "yz~"
+  EXPECT_TRUE(WriteFile("lmnopqrstuvwxyz\n"));
+  FetchFromSource();
+
+  EXPECT_EQ(4, num_callback_calls());
+  EXPECT_EQ("yz\n", latest_response());
+}
+
+TEST_F(SingleLogSourceTest, IncompleteLines) {
+  EXPECT_TRUE(AppendToFile("0123456789"));
+  FetchFromSource();
+
+  EXPECT_EQ(1, num_callback_calls());
+  EXPECT_EQ("", latest_response());
+
+  EXPECT_TRUE(AppendToFile("abcdefg"));
+  FetchFromSource();
+
+  EXPECT_EQ(2, num_callback_calls());
+  EXPECT_EQ("", latest_response());
+
+  EXPECT_TRUE(AppendToFile("hijk\n"));
+  FetchFromSource();
+
+  EXPECT_EQ(3, num_callback_calls());
+  // All the previously written text should be read this time.
+  EXPECT_EQ("0123456789abcdefghijk\n", latest_response());
+
+  EXPECT_TRUE(AppendToFile("Hello world\n"));
+  EXPECT_TRUE(AppendToFile("Goodbye world"));
+  FetchFromSource();
+
+  // Partial whole-line reads are not supported. The last byte of the read must
+  // be a new line.
+  EXPECT_EQ(4, num_callback_calls());
+  EXPECT_EQ("", latest_response());
+
+  EXPECT_TRUE(AppendToFile("\n"));
+  FetchFromSource();
+
+  EXPECT_EQ(5, num_callback_calls());
+  EXPECT_EQ("Hello world\nGoodbye world\n", latest_response());
+}
+
+TEST_F(SingleLogSourceTest, Anonymize) {
+  EXPECT_TRUE(AppendToFile("My MAC address is: 11:22:33:44:55:66\n"));
+  FetchFromSource();
+
+  EXPECT_EQ(1, num_callback_calls());
+  EXPECT_EQ("My MAC address is: 11:22:33:00:00:01\n", latest_response());
+
+  // Suppose the write operation is not atomic, and the MAC address is written
+  // across two separate writes.
+  EXPECT_TRUE(AppendToFile("Your MAC address is: AB:88:C"));
+  FetchFromSource();
+
+  EXPECT_EQ(2, num_callback_calls());
+  EXPECT_EQ("", latest_response());
+
+  EXPECT_TRUE(AppendToFile("D:99:EF:77\n"));
+  FetchFromSource();
+
+  EXPECT_EQ(3, num_callback_calls());
+  EXPECT_EQ("Your MAC address is: ab:88:cd:00:00:02\n", latest_response());
+}
+
+}  // namespace system_logs
diff --git a/chrome/browser/component_updater/sw_reporter_installer_win.cc b/chrome/browser/component_updater/sw_reporter_installer_win.cc
index b5134c7..89c617c 100644
--- a/chrome/browser/component_updater/sw_reporter_installer_win.cc
+++ b/chrome/browser/component_updater/sw_reporter_installer_win.cc
@@ -34,7 +34,7 @@
 #include "base/win/registry.h"
 #include "base/win/windows_version.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.h"
+#include "chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.h"
 #include "chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.h"
 #include "components/chrome_cleaner/public/constants/constants.h"
 #include "components/component_updater/component_updater_paths.h"
diff --git a/chrome/browser/component_updater/sw_reporter_installer_win.h b/chrome/browser/component_updater/sw_reporter_installer_win.h
index 66fdf0e..83a6286 100644
--- a/chrome/browser/component_updater/sw_reporter_installer_win.h
+++ b/chrome/browser/component_updater/sw_reporter_installer_win.h
@@ -12,7 +12,7 @@
 
 #include "base/callback.h"
 #include "base/macros.h"
-#include "chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.h"
+#include "chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.h"
 #include "components/component_updater/default_component_installer.h"
 
 class PrefRegistrySimple;
diff --git a/chrome/browser/component_updater/sw_reporter_installer_win_unittest.cc b/chrome/browser/component_updater/sw_reporter_installer_win_unittest.cc
index c9759fe..509e179 100644
--- a/chrome/browser/component_updater/sw_reporter_installer_win_unittest.cc
+++ b/chrome/browser/component_updater/sw_reporter_installer_win_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <map>
 #include <memory>
+#include <set>
 #include <string>
 #include <utility>
 
@@ -22,7 +23,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/values.h"
 #include "base/version.h"
-#include "chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.h"
+#include "chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.h"
 #include "components/chrome_cleaner/public/constants/constants.h"
 #include "components/variations/variations_params_manager.h"
 #include "content/public/test/test_browser_thread_bundle.h"
diff --git a/chrome/browser/extensions/api/cookies/cookies_unittest.cc b/chrome/browser/extensions/api/cookies/cookies_unittest.cc
index 850dc97..a68804b 100644
--- a/chrome/browser/extensions/api/cookies/cookies_unittest.cc
+++ b/chrome/browser/extensions/api/cookies/cookies_unittest.cc
@@ -35,7 +35,6 @@
 
 struct DomainMatchCase {
   const char* filter;
-  const char* url;
   const char* domain;
   const bool matches;
 };
@@ -88,9 +87,9 @@
 TEST_F(ExtensionCookiesTest, ExtensionTypeCreation) {
   std::unique_ptr<net::CanonicalCookie> canonical_cookie1(
       net::CanonicalCookie::Create(
-          GURL("http://www.example.com"), "ABC", "DEF", std::string(), "/",
-          base::Time(), base::Time(), false, false,
-          net::CookieSameSite::DEFAULT_MODE, net::COOKIE_PRIORITY_DEFAULT));
+          "ABC", "DEF", "www.example.com", "/", base::Time(), base::Time(),
+          base::Time(), false, false, net::CookieSameSite::DEFAULT_MODE,
+          net::COOKIE_PRIORITY_DEFAULT));
   ASSERT_NE(nullptr, canonical_cookie1.get());
   Cookie cookie1 =
       cookies_helpers::CreateCookie(*canonical_cookie1, "some cookie store");
@@ -108,8 +107,8 @@
 
   std::unique_ptr<net::CanonicalCookie> canonical_cookie2(
       net::CanonicalCookie::Create(
-          GURL("http://example.com"), "ABC", "DEF", ".example.com", "/",
-          base::Time(), base::Time::FromDoubleT(10000), false, false,
+          "ABC", "DEF", ".example.com", "/", base::Time(),
+          base::Time::FromDoubleT(10000), base::Time(), false, false,
           net::CookieSameSite::STRICT_MODE, net::COOKIE_PRIORITY_DEFAULT));
   ASSERT_NE(nullptr, canonical_cookie2.get());
   Cookie cookie2 =
@@ -131,17 +130,17 @@
 
 TEST_F(ExtensionCookiesTest, GetURLFromCanonicalCookie) {
   std::unique_ptr<net::CanonicalCookie> cookie1(net::CanonicalCookie::Create(
-      GURL("http://example.com"), "ABC", "DEF", "example.com", "/",
-      base::Time(), base::Time(), false, false,
-      net::CookieSameSite::DEFAULT_MODE, net::COOKIE_PRIORITY_DEFAULT));
+      "ABC", "DEF", ".example.com", "/", base::Time(), base::Time(),
+      base::Time(), false, false, net::CookieSameSite::DEFAULT_MODE,
+      net::COOKIE_PRIORITY_DEFAULT));
   ASSERT_NE(nullptr, cookie1.get());
   EXPECT_EQ("http://example.com/",
             cookies_helpers::GetURLFromCanonicalCookie(*cookie1).spec());
 
   std::unique_ptr<net::CanonicalCookie> cookie2(net::CanonicalCookie::Create(
-      GURL("https://helloworld.com"), "ABC", "DEF", ".helloworld.com", "/",
-      base::Time(), base::Time(), true, false,
-      net::CookieSameSite::DEFAULT_MODE, net::COOKIE_PRIORITY_DEFAULT));
+      "ABC", "DEF", ".helloworld.com", "/", base::Time(), base::Time(),
+      base::Time(), true, false, net::CookieSameSite::DEFAULT_MODE,
+      net::COOKIE_PRIORITY_DEFAULT));
   ASSERT_NE(nullptr, cookie2.get());
   EXPECT_EQ("https://helloworld.com/",
             cookies_helpers::GetURLFromCanonicalCookie(*cookie2).spec());
@@ -159,13 +158,10 @@
 
 TEST_F(ExtensionCookiesTest, DomainMatching) {
   const DomainMatchCase tests[] = {
-      {"bar.com", "http://bar.com", "", true},
-      {".bar.com", "http://bar.com", "bar.com", true},
-      {"bar.com", "http://foo.bar.com", "", true},
-      {"bar.com", "http://bar.foo.com", "", false},
-      {".bar.com", "http://foo.bar.com", ".foo.bar.com", true},
-      {".bar.com", "http://baz.foo.bar.com", "", true},
-      {"foo.bar.com", "http://bar.com", ".bar.com", false}};
+      {"bar.com", "bar.com", true},       {".bar.com", "bar.com", true},
+      {"bar.com", "food.bar.com", true},  {"bar.com", "bar.foo.com", false},
+      {".bar.com", ".foo.bar.com", true}, {".bar.com", "baz.foo.bar.com", true},
+      {"foo.bar.com", ".bar.com", false}};
 
   for (size_t i = 0; i < arraysize(tests); ++i) {
     // Build up the Params struct.
@@ -177,11 +173,11 @@
 
     cookies_helpers::MatchFilter filter(&params->details);
     std::unique_ptr<net::CanonicalCookie> cookie(net::CanonicalCookie::Create(
-        GURL(tests[i].url), std::string(), std::string(), tests[i].domain,
-        std::string(), base::Time(), base::Time(), false, false,
-        net::CookieSameSite::DEFAULT_MODE, net::COOKIE_PRIORITY_DEFAULT));
+        "name", std::string(), tests[i].domain, "/", base::Time(), base::Time(),
+        base::Time(), false, false, net::CookieSameSite::DEFAULT_MODE,
+        net::COOKIE_PRIORITY_DEFAULT));
     ASSERT_NE(nullptr, cookie.get());
-    EXPECT_EQ(tests[i].matches, filter.MatchesCookie(*cookie));
+    EXPECT_EQ(tests[i].matches, filter.MatchesCookie(*cookie)) << " test " << i;
   }
 }
 
diff --git a/chrome/browser/extensions/api/feedback_private/feedback_private_api.cc b/chrome/browser/extensions/api/feedback_private/feedback_private_api.cc
index bfe6f6a..9ed38eb 100644
--- a/chrome/browser/extensions/api/feedback_private/feedback_private_api.cc
+++ b/chrome/browser/extensions/api/feedback_private/feedback_private_api.cc
@@ -5,7 +5,9 @@
 #include "chrome/browser/extensions/api/feedback_private/feedback_private_api.h"
 
 #include <memory>
+#include <string>
 #include <utility>
+#include <vector>
 
 #include "base/lazy_instance.h"
 #include "base/macros.h"
@@ -39,7 +41,7 @@
 
 #if defined(OS_WIN)
 #include "base/feature_list.h"
-#include "chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.h"
+#include "chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.h"
 #endif
 
 using extensions::api::feedback_private::SystemInformation;
diff --git a/chrome/browser/media/router/media_router_feature.cc b/chrome/browser/media/router/media_router_feature.cc
index 5e636a3..690a3c34 100644
--- a/chrome/browser/media/router/media_router_feature.cc
+++ b/chrome/browser/media/router/media_router_feature.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/media/router/media_router_feature.h"
 
+#include "base/feature_list.h"
 #include "build/build_config.h"
 #include "content/public/browser/browser_context.h"
 #include "extensions/features/features.h"
@@ -16,6 +17,16 @@
 
 namespace media_router {
 
+#if !defined(OS_ANDROID)
+// Controls if browser side DIAL device discovery is enabled.
+const base::Feature kEnableDialLocalDiscovery{
+    "EnableDialLocalDiscovery", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Controls if browser side Cast device discovery is enabled.
+const base::Feature kEnableCastDiscovery{"EnableCastDiscovery",
+                                         base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
 #if defined(OS_ANDROID) || BUILDFLAG(ENABLE_EXTENSIONS)
 namespace {
 const PrefService::Preference* GetMediaRouterPref(
@@ -41,4 +52,16 @@
 #endif  // defined(OS_ANDROID) || BUILDFLAG(ENABLE_EXTENSIONS)
 }
 
+#if !defined(OS_ANDROID)
+// Returns true if browser side DIAL discovery is enabled.
+bool DialLocalDiscoveryEnabled() {
+  return base::FeatureList::IsEnabled(kEnableDialLocalDiscovery);
+}
+
+// Returns true if browser side Cast discovery is enabled.
+bool CastDiscoveryEnabled() {
+  return base::FeatureList::IsEnabled(kEnableCastDiscovery);
+}
+#endif
+
 }  // namespace media_router
diff --git a/chrome/browser/media/router/media_router_feature.h b/chrome/browser/media/router/media_router_feature.h
index 07a1e87..d4bc4b9 100644
--- a/chrome/browser/media/router/media_router_feature.h
+++ b/chrome/browser/media/router/media_router_feature.h
@@ -14,6 +14,14 @@
 // Returns true if Media Router is enabled for |context|.
 bool MediaRouterEnabled(content::BrowserContext* context);
 
+#if !defined(OS_ANDROID)
+// Returns true if browser side DIAL discovery is enabled.
+bool DialLocalDiscoveryEnabled();
+
+// Returns true if browser side Cast discovery is enabled.
+bool CastDiscoveryEnabled();
+#endif
+
 }  // namespace media_router
 
 #endif  // CHROME_BROWSER_MEDIA_ROUTER_MEDIA_ROUTER_FEATURE_H_
diff --git a/chrome/browser/media/router/mojo/media_router_mojo_impl.cc b/chrome/browser/media/router/mojo/media_router_mojo_impl.cc
index 9b778029..d7cbb2a 100644
--- a/chrome/browser/media/router/mojo/media_router_mojo_impl.cc
+++ b/chrome/browser/media/router/mojo/media_router_mojo_impl.cc
@@ -17,6 +17,7 @@
 #include "base/strings/stringprintf.h"
 #include "chrome/browser/media/router/issues_observer.h"
 #include "chrome/browser/media/router/media_router_factory.h"
+#include "chrome/browser/media/router/media_router_feature.h"
 #include "chrome/browser/media/router/media_routes_observer.h"
 #include "chrome/browser/media/router/media_sinks_observer.h"
 #include "chrome/browser/media/router/mojo/media_route_controller.h"
@@ -152,7 +153,14 @@
   media_route_provider_ = std::move(media_route_provider_ptr);
   media_route_provider_.set_connection_error_handler(base::Bind(
       &MediaRouterMojoImpl::OnConnectionError, base::Unretained(this)));
-  callback.Run(instance_id_);
+
+  auto config = mojom::MediaRouteProviderConfig::New();
+  // Enabling browser side discovery means disabling extension side discovery.
+  // We are migrating discovery from the external Media Route Provider to the
+  // Media Router (crbug.com/687383), so we need to disable it in the provider.
+  config->enable_dial_discovery = !media_router::DialLocalDiscoveryEnabled();
+  config->enable_cast_discovery = !media_router::CastDiscoveryEnabled();
+  callback.Run(instance_id_, std::move(config));
   ExecutePendingRequests();
   SyncStateToMediaRouteProvider();
 
diff --git a/chrome/browser/media/router/mojo/media_router_mojo_impl_unittest.cc b/chrome/browser/media/router/mojo/media_router_mojo_impl_unittest.cc
index 75fa343..f887271 100644
--- a/chrome/browser/media/router/mojo/media_router_mojo_impl_unittest.cc
+++ b/chrome/browser/media/router/mojo/media_router_mojo_impl_unittest.cc
@@ -184,12 +184,6 @@
   DISALLOW_COPY_AND_ASSIGN(TestProcessManager);
 };
 
-// Mockable class for awaiting RegisterMediaRouteProvider callbacks.
-class RegisterMediaRouteProviderHandler {
- public:
-  MOCK_METHOD1(Invoke, void(const std::string& instance_id));
-};
-
 TEST_F(MediaRouterMojoImplTest, CreateRoute) {
   MediaSource media_source(kSource);
   MediaRoute expected_route(kRouteId, media_source, kSinkId, "", false, "",
@@ -1507,7 +1501,7 @@
   // itself via RegisterMediaRouteProvider().
   // Now that the |media_router| and |mojo_media_router| are fully initialized,
   // the queued DetachRoute() call should be executed.
-  EXPECT_CALL(provide_handler_, Invoke(testing::Not("")))
+  EXPECT_CALL(provide_handler_, InvokeInternal(testing::Not(""), _))
       .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
   EXPECT_CALL(*process_manager_, IsEventPageSuspended(extension_->id()))
       .WillOnce(Return(false));
@@ -1535,7 +1529,7 @@
   base::RunLoop run_loop4, run_loop5;
   // RegisterMediaRouteProvider() is called.
   // The queued DetachRoute(kRouteId2) call should be executed.
-  EXPECT_CALL(provide_handler_, Invoke(testing::Not("")))
+  EXPECT_CALL(provide_handler_, InvokeInternal(testing::Not(""), _))
       .WillOnce(InvokeWithoutArgs([&run_loop4]() { run_loop4.Quit(); }));
   EXPECT_CALL(*process_manager_, IsEventPageSuspended(extension_->id()))
       .WillOnce(Return(false));
@@ -1645,7 +1639,7 @@
   // The oldest request should have been dropped, so we don't expect to see
   // DetachRoute(kRouteId) here.
   BindMediaRouteProvider();
-  EXPECT_CALL(provide_handler_, Invoke(testing::Not("")))
+  EXPECT_CALL(provide_handler_, InvokeInternal(testing::Not(""), _))
       .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
   EXPECT_CALL(*process_manager_, IsEventPageSuspended(extension_->id()));
   EXPECT_CALL(mock_media_route_provider_, EnableMdnsDiscovery())
@@ -1672,10 +1666,8 @@
 
   base::RunLoop run_loop;
   base::RunLoop run_loop2;
-  EXPECT_CALL(provide_handler_, Invoke(testing::Not("")))
-      .WillOnce(InvokeWithoutArgs([&run_loop]() {
-                  run_loop.Quit();
-                }));
+  EXPECT_CALL(provide_handler_, InvokeInternal(testing::Not(""), _))
+      .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
   EXPECT_CALL(*process_manager_, IsEventPageSuspended(extension_->id()))
       .WillOnce(Return(false)).WillOnce(Return(false));
   EXPECT_CALL(mock_media_route_provider_,
@@ -1712,10 +1704,8 @@
   base::RunLoop run_loop6;
   // RegisterMediaRouteProvider() is called.
   // The queued DetachRoute(kRouteId) call should be executed.
-  EXPECT_CALL(provide_handler_, Invoke(testing::Not("")))
-      .WillOnce(InvokeWithoutArgs([&run_loop5]() {
-                  run_loop5.Quit();
-                }));
+  EXPECT_CALL(provide_handler_, InvokeInternal(testing::Not(""), _))
+      .WillOnce(InvokeWithoutArgs([&run_loop5]() { run_loop5.Quit(); }));
   EXPECT_CALL(*process_manager_, IsEventPageSuspended(extension_->id()))
       .WillOnce(Return(false)).WillOnce(Return(false));
   // Expected because it was used to wake up the page.
@@ -1742,10 +1732,8 @@
   BindMediaRouteProvider();
 
   base::RunLoop run_loop;
-  EXPECT_CALL(provide_handler_, Invoke(testing::Not("")))
-      .WillOnce(InvokeWithoutArgs([&run_loop]() {
-                  run_loop.Quit();
-                }));
+  EXPECT_CALL(provide_handler_, InvokeInternal(testing::Not(""), _))
+      .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
   EXPECT_CALL(*process_manager_, IsEventPageSuspended(extension_->id()))
 #if defined(OS_WIN)
       // Windows calls once for EnableMdnsDiscovery
@@ -1785,7 +1773,7 @@
   std::unique_ptr<NullMessageObserver> messages_observer;
 
   {
-    EXPECT_CALL(provide_handler_, Invoke(testing::Not("")));
+    EXPECT_CALL(provide_handler_, InvokeInternal(testing::Not(""), _));
     BindMediaRouteProvider();
     RegisterMediaRouteProvider();
     base::RunLoop().RunUntilIdle();
@@ -1815,7 +1803,7 @@
   }
 
   {
-    EXPECT_CALL(provide_handler_, Invoke(testing::Not("")));
+    EXPECT_CALL(provide_handler_, InvokeInternal(testing::Not(""), _));
     EXPECT_CALL(mock_media_route_provider_,
                 StartObservingMediaSinks(media_source.id()));
     EXPECT_CALL(mock_media_route_provider_,
diff --git a/chrome/browser/media/router/mojo/media_router_mojo_test.cc b/chrome/browser/media/router/mojo/media_router_mojo_test.cc
index 567c72e..97bb467f 100644
--- a/chrome/browser/media/router/mojo/media_router_mojo_test.cc
+++ b/chrome/browser/media/router/mojo/media_router_mojo_test.cc
@@ -11,14 +11,7 @@
 
 namespace media_router {
 namespace {
-
 const char kInstanceId[] = "instance123";
-
-void ExpectEqualStrings(const std::string& expected,
-                        const std::string& actual) {
-  EXPECT_EQ(expected, actual);
-}
-
 }  // namespace
 
 MockMediaRouteProvider::MockMediaRouteProvider() {}
@@ -33,6 +26,10 @@
 
 MockMediaController::~MockMediaController() {}
 
+RegisterMediaRouteProviderHandler::RegisterMediaRouteProviderHandler() {}
+
+RegisterMediaRouteProviderHandler::~RegisterMediaRouteProviderHandler() {}
+
 void MockMediaController::Bind(mojom::MediaControllerRequest request) {
   binding_.Bind(std::move(request));
 }
@@ -80,9 +77,11 @@
   mojom::MediaRouteProviderPtr mojo_media_router;
   binding_.reset(new mojo::Binding<mojom::MediaRouteProvider>(
       &mock_media_route_provider_, mojo::MakeRequest(&mojo_media_router)));
+  EXPECT_CALL(provide_handler_, InvokeInternal(kInstanceId, testing::_));
   media_router_proxy_->RegisterMediaRouteProvider(
       std::move(mojo_media_router),
-      base::Bind(&ExpectEqualStrings, kInstanceId));
+      base::Bind(&RegisterMediaRouteProviderHandler::Invoke,
+                 base::Unretained(&provide_handler_)));
 }
 
 void MediaRouterMojoTest::SetUp() {
diff --git a/chrome/browser/media/router/mojo/media_router_mojo_test.h b/chrome/browser/media/router/mojo/media_router_mojo_test.h
index 21935d7..f568d21 100644
--- a/chrome/browser/media/router/mojo/media_router_mojo_test.h
+++ b/chrome/browser/media/router/mojo/media_router_mojo_test.h
@@ -174,6 +174,23 @@
   MOCK_METHOD0(OnControllerInvalidated, void());
 };
 
+// Mockable class for awaiting RegisterMediaRouteProvider callbacks.
+class RegisterMediaRouteProviderHandler {
+ public:
+  RegisterMediaRouteProviderHandler();
+  ~RegisterMediaRouteProviderHandler();
+
+  // A wrapper function to deal with move only parameter |config|.
+  void Invoke(const std::string& instance_id,
+              mojom::MediaRouteProviderConfigPtr config) {
+    InvokeInternal(instance_id, config.get());
+  }
+
+  MOCK_METHOD2(InvokeInternal,
+               void(const std::string& instance_id,
+                    mojom::MediaRouteProviderConfig* config));
+};
+
 // Tests the API call flow between the MediaRouterMojoImpl and the Media Router
 // Mojo service in both directions.
 class MediaRouterMojoTest : public ::testing::Test {
@@ -200,6 +217,8 @@
   // Mojo proxy object for |mock_media_router_|
   media_router::mojom::MediaRouterPtr media_router_proxy_;
 
+  RegisterMediaRouteProviderHandler provide_handler_;
+
  private:
   content::TestBrowserThreadBundle test_thread_bundle_;
   scoped_refptr<extensions::Extension> extension_;
diff --git a/chrome/browser/metrics/chrome_metrics_service_accessor.h b/chrome/browser/metrics/chrome_metrics_service_accessor.h
index 12b0423..db9c035 100644
--- a/chrome/browser/metrics/chrome_metrics_service_accessor.h
+++ b/chrome/browser/metrics/chrome_metrics_service_accessor.h
@@ -64,7 +64,6 @@
 class ReporterRunner;
 class SafeBrowsingService;
 class SafeBrowsingUIManager;
-class SRTFetcher;
 class SRTGlobalError;
 }
 
@@ -124,7 +123,6 @@
   friend class safe_browsing::DownloadUrlSBClient;
   friend class safe_browsing::IncidentReportingService;
   friend class safe_browsing::ReporterRunner;
-  friend class safe_browsing::SRTFetcher;
   friend class safe_browsing::SRTGlobalError;
   friend class safe_browsing::SafeBrowsingService;
   friend class safe_browsing::SafeBrowsingUIManager;
diff --git a/chrome/browser/ntp_tiles/chrome_most_visited_sites_factory.cc b/chrome/browser/ntp_tiles/chrome_most_visited_sites_factory.cc
index 01ebd60..bdc276f 100644
--- a/chrome/browser/ntp_tiles/chrome_most_visited_sites_factory.cc
+++ b/chrome/browser/ntp_tiles/chrome_most_visited_sites_factory.cc
@@ -11,6 +11,7 @@
 #include "base/callback.h"
 #include "base/memory/ptr_util.h"
 #include "chrome/browser/favicon/favicon_service_factory.h"
+#include "chrome/browser/favicon/large_icon_service_factory.h"
 #include "chrome/browser/history/top_sites_factory.h"
 #include "chrome/browser/ntp_tiles/chrome_popular_sites_factory.h"
 #include "chrome/browser/profiles/profile.h"
@@ -124,6 +125,7 @@
       base::MakeUnique<ntp_tiles::IconCacherImpl>(
           FaviconServiceFactory::GetForProfile(
               profile, ServiceAccessType::IMPLICIT_ACCESS),
+          LargeIconServiceFactory::GetForBrowserContext(profile),
           base::MakeUnique<image_fetcher::ImageFetcherImpl>(
               base::MakeUnique<suggestions::ImageDecoderImpl>(),
               profile->GetRequestContext())),
diff --git a/chrome/browser/previews/previews_service.cc b/chrome/browser/previews/previews_service.cc
index 22d879a..ead4e6e 100644
--- a/chrome/browser/previews/previews_service.cc
+++ b/chrome/browser/previews/previews_service.cc
@@ -8,7 +8,7 @@
 #include "base/files/file_path.h"
 #include "base/memory/ptr_util.h"
 #include "base/sequenced_task_runner.h"
-#include "base/threading/sequenced_worker_pool.h"
+#include "base/task_scheduler/post_task.h"
 #include "chrome/common/chrome_constants.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h"
 #include "components/previews/core/previews_experiments.h"
@@ -90,11 +90,10 @@
     const base::FilePath& profile_path) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  base::SequencedWorkerPool* blocking_pool =
-      content::BrowserThread::GetBlockingPool();
   // Get the background thread to run SQLite on.
   scoped_refptr<base::SequencedTaskRunner> background_task_runner =
-      blocking_pool->GetSequencedTaskRunner(blocking_pool->GetSequenceToken());
+      base::CreateSequencedTaskRunnerWithTraits(
+          {base::MayBlock(), base::TaskPriority::BACKGROUND});
 
   previews_ui_service_ = base::MakeUnique<previews::PreviewsUIService>(
       previews_io_data, io_task_runner,
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.cc
new file mode 100644
index 0000000..f6138161
--- /dev/null
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.cc
@@ -0,0 +1,101 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.h"
+#include "components/data_use_measurement/core/data_use_user_data.h"
+#include "components/variations/net/variations_http_headers.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "url/gurl.h"
+
+namespace safe_browsing {
+
+namespace {
+
+// Class that will attempt to download the Chrome Cleaner executable and
+// call a given callback when done. Instances of ChromeCleanerFetcher own
+// themselves and will self-delete on completion of the network request.
+class ChromeCleanerFetcher : public net::URLFetcherDelegate {
+ public:
+  explicit ChromeCleanerFetcher(ChromeCleanerFetchedCallback fetched_callback);
+
+ protected:
+  ~ChromeCleanerFetcher() override;
+
+ private:
+  // net::URLFetcherDelegate overrides.
+  void OnURLFetchComplete(const net::URLFetcher* source) override;
+
+  ChromeCleanerFetchedCallback fetched_callback_;
+  // The underlying URL fetcher. The instance is alive from construction through
+  // OnURLFetchComplete.
+  std::unique_ptr<net::URLFetcher> url_fetcher_;
+
+  DISALLOW_COPY_AND_ASSIGN(ChromeCleanerFetcher);
+};
+
+ChromeCleanerFetcher::ChromeCleanerFetcher(
+    ChromeCleanerFetchedCallback fetched_callback)
+    : fetched_callback_(std::move(fetched_callback)),
+      url_fetcher_(net::URLFetcher::Create(0,
+                                           GURL(GetSRTDownloadURL()),
+                                           net::URLFetcher::GET,
+                                           this)) {
+  data_use_measurement::DataUseUserData::AttachToFetcher(
+      url_fetcher_.get(), data_use_measurement::DataUseUserData::SAFE_BROWSING);
+  url_fetcher_->SetLoadFlags(net::LOAD_DISABLE_CACHE);
+  url_fetcher_->SetMaxRetriesOn5xx(3);
+  url_fetcher_->SaveResponseToTemporaryFile(
+      content::BrowserThread::GetTaskRunnerForThread(
+          content::BrowserThread::FILE));
+  url_fetcher_->SetRequestContext(g_browser_process->system_request_context());
+  url_fetcher_->Start();
+}
+
+ChromeCleanerFetcher::~ChromeCleanerFetcher() = default;
+
+void ChromeCleanerFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
+  // Take ownership of the fetcher in this scope (source == url_fetcher_).
+  DCHECK_EQ(url_fetcher_.get(), source);
+
+  base::FilePath download_path;
+  if (source->GetStatus().is_success() &&
+      source->GetResponseCode() == net::HTTP_OK &&
+      source->GetResponseAsFilePath(true, &download_path)) {
+    DCHECK(!download_path.empty());
+  }
+
+  DCHECK(fetched_callback_);
+  std::move(fetched_callback_)
+      .Run(std::move(download_path), source->GetResponseCode());
+
+  // Since url_fetcher_ is passed a pointer to this object during construction,
+  // explicitly destroy the url_fetcher_ to avoid potential destruction races.
+  url_fetcher_.reset();
+
+  // At this point, the url_fetcher_ is gone and this ChromeCleanerFetcher
+  // instance is no longer needed.
+  delete this;
+}
+
+}  // namespace
+
+void FetchChromeCleaner(ChromeCleanerFetchedCallback fetched_callback) {
+  new ChromeCleanerFetcher(std::move(fetched_callback));
+}
+
+}  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.h b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.h
new file mode 100644
index 0000000..b12caaea
--- /dev/null
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.h
@@ -0,0 +1,23 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_CHROME_CLEANER_FETCHER_WIN_H_
+#define CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_CHROME_CLEANER_FETCHER_WIN_H_
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+
+namespace safe_browsing {
+
+// Type of callback that is called when the network request to fetch the Chrome
+// Cleaner binary has been completed. The callback will be passed the filepath
+// and http response code as returned by net::URLFetcher.
+using ChromeCleanerFetchedCallback =
+    base::OnceCallback<void(base::FilePath, int /*http response code*/)>;
+
+void FetchChromeCleaner(ChromeCleanerFetchedCallback fetched_callback);
+
+}  // namespace safe_browsing
+
+#endif  // CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_CHROME_CLEANER_FETCHER_WIN_H_
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win_unittest.cc b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win_unittest.cc
new file mode 100644
index 0000000..dc80d573
--- /dev/null
+++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win_unittest.cc
@@ -0,0 +1,94 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.h"
+
+#include <memory>
+
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/memory/ptr_util.h"
+#include "chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request_status.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace safe_browsing {
+namespace {
+
+class ChromeCleanerFetcherTest : public ::testing::Test {
+ public:
+  void SetUp() override {
+    FetchChromeCleaner(base::Bind(&ChromeCleanerFetcherTest::FetchedCallback,
+                                  base::Unretained(this)));
+
+    // Get the TestURLFetcher instance used by FetchChromeCleaner.
+    fetcher_ = factory_.GetFetcherByID(0);
+    ASSERT_TRUE(fetcher_);
+  }
+
+  void FetchedCallback(base::FilePath downloaded_path, int http_response_code) {
+    callback_called_ = true;
+    downloaded_path_ = downloaded_path;
+    http_response_code_ = http_response_code;
+  }
+
+ protected:
+  // TestURLFetcher requires a MessageLoop and an IO thread to release
+  // URLRequestContextGetter in URLFetcher::Core.
+  content::TestBrowserThreadBundle thread_bundle_;
+  // TestURLFetcherFactory automatically sets itself as URLFetcher's factory
+  net::TestURLFetcherFactory factory_;
+
+  // The TestURLFetcher instance used by the FetchChromeCleaner.
+  net::TestURLFetcher* fetcher_ = nullptr;
+  // Variables set by FetchedCallback().
+  bool callback_called_ = false;
+  base::FilePath downloaded_path_;
+  int http_response_code_ = -1;
+};
+
+TEST_F(ChromeCleanerFetcherTest, FetchSuccess) {
+  EXPECT_EQ(GURL(fetcher_->GetOriginalURL()), GURL(GetSRTDownloadURL()));
+
+  // Set up the fetcher to return success.
+  fetcher_->set_status(net::URLRequestStatus{});
+  fetcher_->set_response_code(net::HTTP_OK);
+  // We need to save the file path where the response will be saved for later
+  // because ChromeCleanerFetcher takes ownership of the file after
+  // OnURLFetchComplete() has been called and we can't call
+  // GetResponseAsFilePath() after that..
+  base::FilePath response_file;
+  fetcher_->GetResponseAsFilePath(/*take_ownership=*/false, &response_file);
+
+  // After this call, the ChromeCleanerFetcher instance will delete itself.
+  fetcher_->delegate()->OnURLFetchComplete(fetcher_);
+
+  EXPECT_TRUE(callback_called_);
+  EXPECT_EQ(downloaded_path_, response_file);
+  EXPECT_EQ(http_response_code_, net::HTTP_OK);
+}
+
+TEST_F(ChromeCleanerFetcherTest, FetchFailure) {
+  // Set up the fetcher to return failure.
+  fetcher_->set_status(net::URLRequestStatus::FromError(net::ERR_FAILED));
+  fetcher_->set_response_code(net::HTTP_NOT_FOUND);
+
+  // After this call, the ChromeCleanerFetcher instance will delete itself.
+  fetcher_->delegate()->OnURLFetchComplete(fetcher_);
+
+  EXPECT_TRUE(callback_called_);
+  EXPECT_TRUE(downloaded_path_.empty());
+  EXPECT_EQ(http_response_code_, net::HTTP_NOT_FOUND);
+}
+
+}  // namespace
+}  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_browsertest_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_browsertest_win.cc
similarity index 95%
rename from chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_browsertest_win.cc
rename to chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_browsertest_win.cc
index 6285f16a..36d420c 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_browsertest_win.cc
+++ b/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_browsertest_win.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.h"
+#include "chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.h"
 
 #include <initializer_list>
 #include <set>
@@ -299,13 +299,13 @@
 //    cleanup.
 //  - MockedReporterFailure expected_reporter_failure: indicates errors that
 //    should be simulated in the reporter process.
-class SRTFetcherTest
+class ReporterRunnerTest
     : public InProcessBrowserTest,
       public SwReporterTestingDelegate,
       public ::testing::WithParamInterface<
           std::tuple<bool, ElevationStatus, MockedReporterFailure>> {
  public:
-  SRTFetcherTest() = default;
+  ReporterRunnerTest() = default;
 
   void SetUpInProcessBrowserTestFixture() override {
     SetSwReporterTestingDelegate(this);
@@ -416,7 +416,7 @@
       chrome_cleaner::mojom::ChromePromptRequest request) override {
     return base::MakeUnique<ReportBadMessageChromePromptImpl>(
         std::move(request), bad_message_expected_,
-        base::Bind(&SRTFetcherTest::OnConnectionClosed,
+        base::Bind(&ReporterRunnerTest::OnConnectionClosed,
                    base::Unretained(this)));
   }
 
@@ -611,10 +611,11 @@
 
   base::test::ScopedFeatureList scoped_feature_list_;
 
-  DISALLOW_COPY_AND_ASSIGN(SRTFetcherTest);
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ReporterRunnerTest);
 };
 
-class SRTFetcherPromptTest : public DialogBrowserTest {
+class ReporterRunnerPromptTest : public DialogBrowserTest {
  public:
   void ShowDialog(const std::string& name) override {
     if (name == "SRTErrorNoFile")
@@ -629,19 +630,19 @@
 
 }  // namespace
 
-IN_PROC_BROWSER_TEST_P(SRTFetcherTest, NothingFound) {
+IN_PROC_BROWSER_TEST_P(ReporterRunnerTest, NothingFound) {
   RunReporter(chrome_cleaner::kSwReporterNothingFound);
   ExpectReporterLaunches(0, 1, false);
   ExpectToRunAgain(kDaysBetweenSuccessfulSwReporterRuns);
 }
 
-IN_PROC_BROWSER_TEST_P(SRTFetcherTest, CleanupNeeded) {
+IN_PROC_BROWSER_TEST_P(ReporterRunnerTest, CleanupNeeded) {
   RunReporter(chrome_cleaner::kSwReporterCleanupNeeded);
   ExpectReporterLaunches(0, 1, true);
   ExpectToRunAgain(kDaysBetweenSuccessfulSwReporterRuns);
 }
 
-IN_PROC_BROWSER_TEST_P(SRTFetcherTest, RanRecently) {
+IN_PROC_BROWSER_TEST_P(ReporterRunnerTest, RanRecently) {
   constexpr int kDaysLeft = 1;
   SetDaysSinceLastTriggered(kDaysBetweenSuccessfulSwReporterRuns - kDaysLeft);
   RunReporter(chrome_cleaner::kSwReporterNothingFound);
@@ -652,7 +653,7 @@
 }
 
 // Test is flaky. crbug.com/705608
-IN_PROC_BROWSER_TEST_P(SRTFetcherTest, DISABLED_WaitForBrowser) {
+IN_PROC_BROWSER_TEST_P(ReporterRunnerTest, DISABLED_WaitForBrowser) {
   Profile* profile = browser()->profile();
 
   // Ensure that even though we're closing the last browser, we don't enter the
@@ -692,13 +693,13 @@
   ExpectToRunAgain(kDaysBetweenSuccessfulSwReporterRuns);
 }
 
-IN_PROC_BROWSER_TEST_P(SRTFetcherTest, Failure) {
+IN_PROC_BROWSER_TEST_P(ReporterRunnerTest, Failure) {
   RunReporter(kReporterNotLaunchedExitCode);
   ExpectReporterLaunches(0, 1, false);
   ExpectToRunAgain(kDaysBetweenSuccessfulSwReporterRuns);
 }
 
-IN_PROC_BROWSER_TEST_P(SRTFetcherTest, RunDaily) {
+IN_PROC_BROWSER_TEST_P(ReporterRunnerTest, RunDaily) {
   PrefService* local_state = g_browser_process->local_state();
   local_state->SetBoolean(prefs::kSwReporterPendingPrompt, true);
   SetDaysSinceLastTriggered(kDaysBetweenSuccessfulSwReporterRuns - 1);
@@ -727,7 +728,7 @@
   ExpectToRunAgain(kDaysBetweenSuccessfulSwReporterRuns);
 }
 
-IN_PROC_BROWSER_TEST_P(SRTFetcherTest, ParameterChange) {
+IN_PROC_BROWSER_TEST_P(ReporterRunnerTest, ParameterChange) {
   // If the reporter is run several times with different parameters, it should
   // only be launched once, with the last parameter set.
   const base::FilePath path1(L"path1");
@@ -784,7 +785,7 @@
   ExpectToRunAgain(kDaysBetweenSuccessfulSwReporterRuns);
 }
 
-IN_PROC_BROWSER_TEST_P(SRTFetcherTest, MultipleLaunches) {
+IN_PROC_BROWSER_TEST_P(ReporterRunnerTest, MultipleLaunches) {
   const base::FilePath path1(L"path1");
   const base::FilePath path2(L"path2");
   const base::FilePath path3(L"path3");
@@ -818,7 +819,7 @@
     SCOPED_TRACE("Add third launch while running");
     invocations.push(SwReporterInvocation::FromFilePath(path3));
     first_launch_callback_ = base::BindOnce(
-        &SRTFetcherTest::RunReporterQueue, base::Unretained(this),
+        &ReporterRunnerTest::RunReporterQueue, base::Unretained(this),
         chrome_cleaner::kSwReporterNothingFound, invocations);
 
     // Only the first two elements should execute since the third was added
@@ -852,7 +853,8 @@
   }
 }
 
-IN_PROC_BROWSER_TEST_P(SRTFetcherTest, ReporterLogging_NoSBExtendedReporting) {
+IN_PROC_BROWSER_TEST_P(ReporterRunnerTest,
+                       ReporterLogging_NoSBExtendedReporting) {
   RunReporter(chrome_cleaner::kSwReporterNothingFound);
   ExpectReporterLaunches(0, 1, false);
   ExpectLoggingSwitches(reporter_launch_parameters_.front(), false);
@@ -860,7 +862,7 @@
   ExpectToRunAgain(kDaysBetweenSuccessfulSwReporterRuns);
 }
 
-IN_PROC_BROWSER_TEST_P(SRTFetcherTest, ReporterLogging_EnabledFirstRun) {
+IN_PROC_BROWSER_TEST_P(ReporterRunnerTest, ReporterLogging_EnabledFirstRun) {
   EnableSBExtendedReporting();
   // Note: don't set last time sent logs in the local state.
   // SBER is enabled and there is no record in the local state of the last time
@@ -872,7 +874,8 @@
   ExpectToRunAgain(kDaysBetweenSuccessfulSwReporterRuns);
 }
 
-IN_PROC_BROWSER_TEST_P(SRTFetcherTest, ReporterLogging_EnabledNoRecentLogging) {
+IN_PROC_BROWSER_TEST_P(ReporterRunnerTest,
+                       ReporterLogging_EnabledNoRecentLogging) {
   // SBER is enabled and last time logs were sent was more than
   // |kDaysBetweenReporterLogsSent| day ago, so we should send logs in this run.
   EnableSBExtendedReporting();
@@ -884,7 +887,8 @@
   ExpectToRunAgain(kDaysBetweenSuccessfulSwReporterRuns);
 }
 
-IN_PROC_BROWSER_TEST_P(SRTFetcherTest, ReporterLogging_EnabledRecentlyLogged) {
+IN_PROC_BROWSER_TEST_P(ReporterRunnerTest,
+                       ReporterLogging_EnabledRecentlyLogged) {
   // SBER is enabled, but logs have been sent less than
   // |kDaysBetweenReporterLogsSent| day ago, so we shouldn't send any logs in
   // this run.
@@ -898,7 +902,7 @@
   ExpectToRunAgain(kDaysBetweenSuccessfulSwReporterRuns);
 }
 
-IN_PROC_BROWSER_TEST_P(SRTFetcherTest, ReporterLogging_MultipleLaunches) {
+IN_PROC_BROWSER_TEST_P(ReporterRunnerTest, ReporterLogging_MultipleLaunches) {
   EnableSBExtendedReporting();
   SetLastTimeSentReport(kDaysBetweenReporterLogsSent + 3);
 
@@ -918,7 +922,7 @@
   {
     SCOPED_TRACE("first launch");
     first_launch_callback_ =
-        base::BindOnce(&SRTFetcherTest::ExpectLastReportSentInTheLastHour,
+        base::BindOnce(&ReporterRunnerTest::ExpectLastReportSentInTheLastHour,
                        base::Unretained(this));
     ExpectReporterLaunches(0, {path1, path2}, false);
     ExpectLoggingSwitches(reporter_launch_parameters_[0], true);
@@ -936,7 +940,7 @@
 
 INSTANTIATE_TEST_CASE_P(
     NoInBrowserCleanerUI,
-    SRTFetcherTest,
+    ReporterRunnerTest,
     testing::Combine(testing::Values(false),
                      testing::Values(ElevationStatus::NOT_REQUIRED),
                      testing::Values(MockedReporterFailure::kNone,
@@ -944,7 +948,7 @@
 
 INSTANTIATE_TEST_CASE_P(
     InBrowserCleanerUI,
-    SRTFetcherTest,
+    ReporterRunnerTest,
     testing::Combine(
         testing::Values(true),
         testing::Values(ElevationStatus::NOT_REQUIRED,
@@ -961,11 +965,11 @@
 // useful for checking dialog layout or any other interactive functionality
 // tests. See docs/testing/test_browser_dialog.md for description of the
 // testing framework.
-IN_PROC_BROWSER_TEST_F(SRTFetcherPromptTest, InvokeDialog_SRTErrorNoFile) {
+IN_PROC_BROWSER_TEST_F(ReporterRunnerPromptTest, InvokeDialog_SRTErrorNoFile) {
   RunDialog();
 }
 
-IN_PROC_BROWSER_TEST_F(SRTFetcherPromptTest, InvokeDialog_SRTErrorFile) {
+IN_PROC_BROWSER_TEST_F(ReporterRunnerPromptTest, InvokeDialog_SRTErrorFile) {
   RunDialog();
 }
 
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.cc
similarity index 91%
rename from chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.cc
rename to chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.cc
index 57e910f..1378db4 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.cc
+++ b/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.h"
+#include "chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.h"
 
 #include <stdint.h>
 
@@ -34,7 +34,7 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_io_data.h"
+#include "chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.h"
 #include "chrome/browser/safe_browsing/chrome_cleaner/srt_chrome_prompt_impl.h"
 #include "chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.h"
 #include "chrome/browser/safe_browsing/chrome_cleaner/srt_global_error_win.h"
@@ -46,20 +46,14 @@
 #include "chrome/common/pref_names.h"
 #include "components/chrome_cleaner/public/constants/constants.h"
 #include "components/component_updater/pref_names.h"
-#include "components/data_use_measurement/core/data_use_user_data.h"
 #include "components/prefs/pref_service.h"
-#include "components/variations/net/variations_http_headers.h"
 #include "components/version_info/version_info.h"
 #include "content/public/browser/browser_thread.h"
 #include "mojo/edk/embedder/connection_params.h"
 #include "mojo/edk/embedder/outgoing_broker_client_invitation.h"
 #include "mojo/edk/embedder/platform_channel_pair.h"
 #include "mojo/public/cpp/system/message_pipe.h"
-#include "net/base/load_flags.h"
 #include "net/http/http_status_code.h"
-#include "net/url_request/url_fetcher.h"
-#include "net/url_request/url_fetcher_delegate.h"
-#include "net/url_request/url_request_context_getter.h"
 
 using content::BrowserThread;
 
@@ -495,7 +489,19 @@
   uma.RecordReporterStep(value);
 }
 
-void DisplaySRTPrompt(const base::FilePath& download_path) {
+void DisplaySRTPrompt(base::FilePath download_path, int http_response_code) {
+  // As long as the fetch didn't fail due to HTTP_NOT_FOUND, show a prompt
+  // (either offering the tool directly or pointing to the download page).
+  // If the fetch failed to find the file, don't prompt the user since the
+  // tool is not currently available.
+  // TODO(csharp): In the event the browser is closed before the prompt
+  //               displays, we will wait until the next scanner run to
+  //               re-display it.  Improve this. http://crbug.com/460295
+  if (http_response_code == net::HTTP_NOT_FOUND) {
+    RecordSRTPromptHistogram(SRT_PROMPT_DOWNLOAD_UNAVAILABLE);
+    return;
+  }
+
   // Find the last active browser, which may be NULL, in which case we won't
   // show the prompt this time and will wait until the next run of the
   // reporter. We can't use other ways of finding a browser because we don't
@@ -739,93 +745,9 @@
 }  // namespace
 
 void DisplaySRTPromptForTesting(const base::FilePath& download_path) {
-  DisplaySRTPrompt(download_path);
+  DisplaySRTPrompt(download_path, net::HTTP_OK);
 }
 
-// Class that will attempt to download the SRT, showing the SRT notification
-// bubble when the download operation is complete. Instances of SRTFetcher own
-// themselves, they will self-delete on completion of the network request when
-// OnURLFetchComplete is called.
-class SRTFetcher : public net::URLFetcherDelegate {
- public:
-  explicit SRTFetcher(Profile* profile)
-      : profile_(profile),
-        url_fetcher_(net::URLFetcher::Create(0,
-                                             GURL(GetSRTDownloadURL()),
-                                             net::URLFetcher::GET,
-                                             this)) {
-    data_use_measurement::DataUseUserData::AttachToFetcher(
-        url_fetcher_.get(),
-        data_use_measurement::DataUseUserData::SAFE_BROWSING);
-    url_fetcher_->SetLoadFlags(net::LOAD_DISABLE_CACHE);
-    url_fetcher_->SetMaxRetriesOn5xx(3);
-    url_fetcher_->SaveResponseToTemporaryFile(
-        BrowserThread::GetTaskRunnerForThread(BrowserThread::FILE));
-    url_fetcher_->SetRequestContext(
-        g_browser_process->system_request_context());
-    // Adds the UMA bit to the download request if the user is enrolled in UMA.
-    ProfileIOData* io_data = ProfileIOData::FromResourceContext(
-        profile_->GetResourceContext());
-    net::HttpRequestHeaders headers;
-    // Note: It's OK to pass |is_signed_in| false if it's unknown, as it does
-    // not affect transmission of experiments coming from the variations server.
-    bool is_signed_in = false;
-    variations::AppendVariationHeaders(
-        url_fetcher_->GetOriginalURL(), io_data->IsOffTheRecord(),
-        ChromeMetricsServiceAccessor::IsMetricsAndCrashReportingEnabled(),
-        is_signed_in, &headers);
-    url_fetcher_->SetExtraRequestHeaders(headers.ToString());
-    url_fetcher_->Start();
-  }
-
-  // net::URLFetcherDelegate:
-  void OnURLFetchComplete(const net::URLFetcher* source) override {
-    // Take ownership of the fetcher in this scope (source == url_fetcher_).
-    DCHECK_EQ(url_fetcher_.get(), source);
-
-    base::FilePath download_path;
-    if (source->GetStatus().is_success() &&
-        source->GetResponseCode() == net::HTTP_OK) {
-      if (source->GetResponseAsFilePath(true, &download_path)) {
-        DCHECK(!download_path.empty());
-      }
-    }
-
-    // As long as the fetch didn't fail due to HTTP_NOT_FOUND, show a prompt
-    // (either offering the tool directly or pointing to the download page).
-    // If the fetch failed to find the file, don't prompt the user since the
-    // tool is not currently available.
-    // TODO(mad): Consider implementing another layer of retries / alternate
-    //            fetching mechanisms. http://crbug.com/460293
-    // TODO(mad): In the event the browser is closed before the prompt displays,
-    //            we will wait until the next scanner run to re-display it.
-    //            Improve this. http://crbug.com/460295
-    if (source->GetResponseCode() != net::HTTP_NOT_FOUND)
-      DisplaySRTPrompt(download_path);
-    else
-      RecordSRTPromptHistogram(SRT_PROMPT_DOWNLOAD_UNAVAILABLE);
-
-    // Explicitly destroy the url_fetcher_ to avoid destruction races.
-    url_fetcher_.reset();
-
-    // At this point, the url_fetcher_ is gone and this SRTFetcher instance is
-    // no longer needed.
-    delete this;
-  }
-
- private:
-  ~SRTFetcher() override {}
-
-  // The user profile.
-  Profile* profile_;
-
-  // The underlying URL fetcher. The instance is alive from construction through
-  // OnURLFetchComplete.
-  std::unique_ptr<net::URLFetcher> url_fetcher_;
-
-  DISALLOW_COPY_AND_ASSIGN(SRTFetcher);
-};
-
 namespace {
 
 // Try to fetch the SRT, and on success, show the prompt to run it.
@@ -867,7 +789,7 @@
   RecordReporterStepHistogram(SW_REPORTER_DOWNLOAD_START);
 
   // All the work happens in the self-deleting class below.
-  new SRTFetcher(profile);
+  FetchChromeCleaner(base::Bind(DisplaySRTPrompt));
 }
 
 base::Time Now() {
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.h b/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.h
similarity index 93%
rename from chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.h
rename to chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.h
index 1a0e53d..c0b068b 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.h
+++ b/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.h
@@ -2,12 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_SRT_FETCHER_WIN_H_
-#define CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_SRT_FETCHER_WIN_H_
+#ifndef CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_REPORTER_RUNNER_WIN_H_
+#define CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_REPORTER_RUNNER_WIN_H_
 
 #include <limits.h>
 #include <stdint.h>
 
+#include <memory>
 #include <queue>
 #include <string>
 
@@ -124,7 +125,7 @@
                              const std::string& reporter_version) = 0;
 
   // Invoked by tests to override the current time.
-  // See Now() in srt_fetcher_win.cc.
+  // See Now() in reporter_runner_win.cc.
   virtual base::Time Now() const = 0;
 
   // A task runner used to spawn the reporter process (which blocks).
@@ -139,7 +140,7 @@
       chrome_cleaner::mojom::ChromePromptRequest request) = 0;
 
   // Connection closed callback defined by tests in place of the default
-  // error handler. See SRTFetcherTest::CreateChromePromptImpl().
+  // error handler. See ReporterRunnerTest::CreateChromePromptImpl().
   virtual void OnConnectionClosed() = 0;
 
   // Bad message handler callback defined by tests in place of the default
@@ -157,4 +158,4 @@
 
 }  // namespace safe_browsing
 
-#endif  // CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_SRT_FETCHER_WIN_H_
+#endif  // CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_REPORTER_RUNNER_WIN_H_
diff --git a/chrome/browser/safe_browsing/local_database_manager.cc b/chrome/browser/safe_browsing/local_database_manager.cc
index aba7ade8..33c0044 100644
--- a/chrome/browser/safe_browsing/local_database_manager.cc
+++ b/chrome/browser/safe_browsing/local_database_manager.cc
@@ -17,7 +17,7 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/single_thread_task_runner.h"
 #include "base/strings/string_util.h"
-#include "base/threading/sequenced_worker_pool.h"
+#include "base/task_scheduler/post_task.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
@@ -689,11 +689,9 @@
   // Only get a new task runner if there isn't one already. If the service has
   // previously been started and stopped, a task runner could already exist.
   if (!safe_browsing_task_runner_) {
-    base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool();
-    safe_browsing_task_runner_ =
-        pool->GetSequencedTaskRunnerWithShutdownBehavior(
-            pool->GetSequenceToken(),
-            base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
+    safe_browsing_task_runner_ = base::CreateSequencedTaskRunnerWithTraits(
+        {base::MayBlock(), base::TaskPriority::BACKGROUND,
+         base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
   }
 
   enabled_ = true;
diff --git a/chrome/browser/safe_browsing/notification_image_reporter.cc b/chrome/browser/safe_browsing/notification_image_reporter.cc
index 93cc487..00f7a12 100644
--- a/chrome/browser/safe_browsing/notification_image_reporter.cc
+++ b/chrome/browser/safe_browsing/notification_image_reporter.cc
@@ -14,7 +14,7 @@
 #include "base/memory/ref_counted_memory.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/rand_util.h"
-#include "base/threading/sequenced_worker_pool.h"
+#include "base/task_scheduler/post_task.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
@@ -150,9 +150,8 @@
                        weak_this_on_io));
     return;
   }
-
-  BrowserThread::GetBlockingPool()->PostWorkerTask(
-      FROM_HERE,
+  base::PostTaskWithTraits(
+      FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
       base::BindOnce(
           &NotificationImageReporter::DownscaleNotificationImageOnBlockingPool,
           weak_this_on_io, origin, image));
@@ -163,8 +162,6 @@
     const base::WeakPtr<NotificationImageReporter>& weak_this_on_io,
     const GURL& origin,
     const SkBitmap& image) {
-  DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
-
   // Downscale to fit within 512x512. TODO(johnme): Get this from Finch.
   const double MAX_SIZE = 512;
   SkBitmap downscaled_image = image;
diff --git a/chrome/browser/task_profiler/task_profiler_data_serializer.cc b/chrome/browser/task_profiler/task_profiler_data_serializer.cc
index ffc07fc..e9444bfd 100644
--- a/chrome/browser/task_profiler/task_profiler_data_serializer.cc
+++ b/chrome/browser/task_profiler/task_profiler_data_serializer.cc
@@ -68,10 +68,12 @@
 
   dictionary->SetInteger("alloc_ops", death_data.alloc_ops);
   dictionary->SetInteger("free_ops", death_data.free_ops);
-  dictionary->SetInteger("allocated_bytes", death_data.allocated_bytes);
-  dictionary->SetInteger("freed_bytes", death_data.freed_bytes);
-  dictionary->SetInteger("alloc_overhead_bytes",
-                         death_data.alloc_overhead_bytes);
+  // The byte counts are 64 bit integers, pass them through as doubles, as
+  // integer values truncate to 32 bits.
+  dictionary->SetDouble("allocated_bytes", death_data.allocated_bytes);
+  dictionary->SetDouble("freed_bytes", death_data.freed_bytes);
+  dictionary->SetDouble("alloc_overhead_bytes",
+                        death_data.alloc_overhead_bytes);
   dictionary->SetInteger("max_allocated_bytes", death_data.max_allocated_bytes);
 }
 
diff --git a/chrome/browser/task_profiler/task_profiler_data_serializer_unittest.cc b/chrome/browser/task_profiler/task_profiler_data_serializer_unittest.cc
index 5ff71d6..c97ff6b 100644
--- a/chrome/browser/task_profiler/task_profiler_data_serializer_unittest.cc
+++ b/chrome/browser/task_profiler/task_profiler_data_serializer_unittest.cc
@@ -112,11 +112,11 @@
                         "\"birth_thread\":\"CrBrowserMain\","
                         "\"death_data\":{"
                         "\"alloc_ops\":127,"
-                        "\"alloc_overhead_bytes\":201,"
-                        "\"allocated_bytes\":2013,"
+                        "\"alloc_overhead_bytes\":201.0,"
+                        "\"allocated_bytes\":2013.0,"
                         "\"count\":37,"
                         "\"free_ops\":120,"
-                        "\"freed_bytes\":1092,"
+                        "\"freed_bytes\":1092.0,"
                         "\"max_allocated_bytes\":1500,"
                         "\"queue_ms\":79,"
                         "\"queue_ms_max\":53,"
@@ -135,11 +135,11 @@
                         "\"birth_thread\":\"Chrome_IOThread\","
                         "\"death_data\":{"
                         "\"alloc_ops\":1207,"
-                        "\"alloc_overhead_bytes\":2001,"
-                        "\"allocated_bytes\":20013,"
+                        "\"alloc_overhead_bytes\":2001.0,"
+                        "\"allocated_bytes\":20013.0,"
                         "\"count\":41,"
                         "\"free_ops\":1200,"
-                        "\"freed_bytes\":10092,"
+                        "\"freed_bytes\":10092.0,"
                         "\"max_allocated_bytes\":15000,"
                         "\"queue_ms\":2079,"
                         "\"queue_ms_max\":2053,"
diff --git a/chrome/browser/ui/cocoa/browser_window_cocoa.mm b/chrome/browser/ui/cocoa/browser_window_cocoa.mm
index 1e9353d..cb61d3a 100644
--- a/chrome/browser/ui/cocoa/browser_window_cocoa.mm
+++ b/chrome/browser/ui/cocoa/browser_window_cocoa.mm
@@ -735,10 +735,10 @@
     return [BrowserWindowUtils handleKeyboardEvent:event.os_event
                                           inWindow:window()]
                ? Result::HANDLED
-               : Result::NOT_HANDLED_IS_SHORTCUT;
+               : Result::NOT_HANDLED;
   }
 
-  return content::KeyboardEventProcessingResult::NOT_HANDLED;
+  return content::KeyboardEventProcessingResult::NOT_HANDLED_IS_SHORTCUT;
 }
 
 void BrowserWindowCocoa::HandleKeyboardEvent(
diff --git a/chrome/browser/ui/views/first_run_bubble_unittest.cc b/chrome/browser/ui/views/first_run_bubble_unittest.cc
index c3b0add..91c4bd0 100644
--- a/chrome/browser/ui/views/first_run_bubble_unittest.cc
+++ b/chrome/browser/ui/views/first_run_bubble_unittest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/ui/views/first_run_bubble.h"
+
 #include "base/macros.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "chrome/browser/ui/views/harmony/chrome_layout_provider.h"
@@ -17,44 +18,11 @@
 #include "ui/events/event_sink.h"
 #include "ui/events/event_utils.h"
 #include "ui/views/test/views_test_base.h"
+#include "ui/views/test/widget_test.h"
 #include "ui/views/view.h"
 #include "ui/views/widget/widget.h"
 
-// Provides functionality to observe the widget passed in the constructor for
-// the widget closing event.
-class WidgetClosingObserver : public views::WidgetObserver {
- public:
-  explicit WidgetClosingObserver(views::Widget* widget)
-      : widget_(widget),
-        widget_destroyed_(false) {
-    widget_->AddObserver(this);
-  }
-
-  ~WidgetClosingObserver() override {
-    if (widget_)
-      widget_->RemoveObserver(this);
-  }
-
-  void OnWidgetClosing(views::Widget* widget) override {
-    DCHECK(widget == widget_);
-    widget_->RemoveObserver(this);
-    widget_destroyed_ = true;
-    widget_ = nullptr;
-  }
-
-  bool widget_destroyed() const {
-    return widget_destroyed_;
-  }
-
- private:
-  views::Widget* widget_;
-  bool widget_destroyed_;
-
-  DISALLOW_COPY_AND_ASSIGN(WidgetClosingObserver);
-};
-
-class FirstRunBubbleTest : public views::ViewsTestBase,
-                           views::WidgetObserver {
+class FirstRunBubbleTest : public views::ViewsTestBase {
  public:
   FirstRunBubbleTest();
   ~FirstRunBubbleTest() override;
@@ -98,27 +66,26 @@
   views::Widget::InitParams params =
       CreateParams(views::Widget::InitParams::TYPE_WINDOW);
   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
-  std::unique_ptr<views::Widget> anchor_widget(new views::Widget);
-  anchor_widget->Init(params);
-  anchor_widget->SetBounds(gfx::Rect(10, 10, 500, 500));
-  anchor_widget->Show();
+  views::Widget anchor_widget;
+  anchor_widget.Init(params);
+  anchor_widget.SetBounds(gfx::Rect(10, 10, 500, 500));
+  anchor_widget.Show();
 
   FirstRunBubble* delegate =
-      FirstRunBubble::ShowBubble(NULL, anchor_widget->GetContentsView());
-  EXPECT_TRUE(delegate != NULL);
+      FirstRunBubble::ShowBubble(nullptr, anchor_widget.GetContentsView());
+  EXPECT_TRUE(delegate);
 
-  anchor_widget->GetContentsView()->RequestFocus();
+  anchor_widget.GetContentsView()->RequestFocus();
 
-  std::unique_ptr<WidgetClosingObserver> widget_observer(
-      new WidgetClosingObserver(delegate->GetWidget()));
+  views::test::WidgetClosingObserver widget_observer(delegate->GetWidget());
 
-  ui::EventDispatchDetails details = anchor_widget->GetNativeWindow()
+  ui::EventDispatchDetails details = anchor_widget.GetNativeWindow()
                                          ->GetHost()
                                          ->event_sink()
                                          ->OnEventFromSource(event);
   EXPECT_FALSE(details.dispatcher_destroyed);
 
-  EXPECT_TRUE(widget_observer->widget_destroyed());
+  EXPECT_TRUE(widget_observer.widget_closed());
 }
 
 TEST_F(FirstRunBubbleTest, CreateAndClose) {
@@ -126,13 +93,13 @@
   views::Widget::InitParams params =
       CreateParams(views::Widget::InitParams::TYPE_WINDOW);
   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
-  std::unique_ptr<views::Widget> anchor_widget(new views::Widget);
-  anchor_widget->Init(params);
-  anchor_widget->Show();
+  views::Widget anchor_widget;
+  anchor_widget.Init(params);
+  anchor_widget.Show();
 
   FirstRunBubble* delegate =
-      FirstRunBubble::ShowBubble(NULL, anchor_widget->GetContentsView());
-  EXPECT_TRUE(delegate != NULL);
+      FirstRunBubble::ShowBubble(nullptr, anchor_widget.GetContentsView());
+  EXPECT_TRUE(delegate);
   delegate->GetWidget()->CloseNow();
 }
 
diff --git a/chrome/browser/ui/views/passwords/password_dialog_view_browsertest.cc b/chrome/browser/ui/views/passwords/password_dialog_view_browsertest.cc
index 0d4c7d5..f473e93 100644
--- a/chrome/browser/ui/views/passwords/password_dialog_view_browsertest.cc
+++ b/chrome/browser/ui/views/passwords/password_dialog_view_browsertest.cc
@@ -20,6 +20,7 @@
 #include "components/prefs/pref_service.h"
 #include "net/url_request/test_url_fetcher_factory.h"
 #include "testing/gmock/include/gmock/gmock.h"
+#include "ui/views/test/widget_test.h"
 #include "ui/views/widget/widget.h"
 
 using ::testing::Field;
@@ -43,39 +44,6 @@
   MOCK_METHOD1(OnRequestDone, void(const GURL&));
 };
 
-// A Widget observer class used to observe bubbles closing.
-class BubbleCloseObserver : public views::WidgetObserver {
- public:
-  explicit BubbleCloseObserver(views::DialogDelegateView* bubble);
-  ~BubbleCloseObserver() override;
-
-  bool widget_closed() const { return !widget_; }
-
- private:
-  // WidgetObserver:
-  void OnWidgetClosing(views::Widget* widget) override;
-
-  views::Widget* widget_;
-
-  DISALLOW_COPY_AND_ASSIGN(BubbleCloseObserver);
-};
-
-BubbleCloseObserver::BubbleCloseObserver(views::DialogDelegateView* bubble)
-    : widget_(bubble->GetWidget()) {
-  widget_->AddObserver(this);
-}
-
-BubbleCloseObserver::~BubbleCloseObserver() {
-  if (widget_)
-    widget_->RemoveObserver(this);
-}
-
-void BubbleCloseObserver::OnWidgetClosing(views::Widget* widget) {
-  DCHECK_EQ(widget_, widget);
-  widget_->RemoveObserver(this);
-  widget_ = nullptr;
-}
-
 // ManagePasswordsUIController subclass to capture the dialog instance
 class TestManagePasswordsUIController : public ManagePasswordsUIController {
  public:
@@ -220,8 +188,8 @@
   // Prepare to capture the network request.
   TestURLFetcherCallback url_callback;
   net::FakeURLFetcherFactory factory(
-      NULL, base::Bind(&TestURLFetcherCallback::CreateURLFetcher,
-                       base::Unretained(&url_callback)));
+      nullptr, base::Bind(&TestURLFetcherCallback::CreateURLFetcher,
+                          base::Unretained(&url_callback)));
   factory.SetFakeResponse(icon_url, std::string(), net::HTTP_OK,
                           net::URLRequestStatus::FAILED);
   EXPECT_CALL(url_callback, OnRequestDone(icon_url));
@@ -302,7 +270,7 @@
 
   EXPECT_TRUE(controller()->current_account_chooser());
   views::DialogDelegateView* dialog = controller()->current_account_chooser();
-  BubbleCloseObserver bubble_observer(dialog);
+  views::test::WidgetClosingObserver bubble_observer(dialog->GetWidget());
   EXPECT_CALL(*this, OnChooseCredential(testing::Pointee(form)));
   dialog->Accept();
   EXPECT_TRUE(bubble_observer.widget_closed());
@@ -408,7 +376,7 @@
   EXPECT_EQ(password_manager::ui::INACTIVE_STATE, controller()->GetState());
   AutoSigninFirstRunDialogView* dialog =
       controller()->current_autosignin_prompt();
-  BubbleCloseObserver bubble_observer(dialog);
+  views::test::WidgetClosingObserver bubble_observer(dialog->GetWidget());
   ui::Accelerator esc(ui::VKEY_ESCAPE, 0);
   EXPECT_CALL(*controller(), OnDialogClosed());
   EXPECT_TRUE(dialog->GetWidget()->client_view()->AcceleratorPressed(esc));
diff --git a/chrome/common/media_router/mojo/media_router.mojom b/chrome/common/media_router/mojo/media_router.mojom
index f1c6576..d339c5c7 100644
--- a/chrome/common/media_router/mojo/media_router.mojom
+++ b/chrome/common/media_router/mojo/media_router.mojom
@@ -187,6 +187,16 @@
 // New values must be added here.
 };
 
+// Used to pass feature configuration data from the Browser to Media Route
+// Provider.
+struct MediaRouteProviderConfig {
+  // If the MRP should enable DIAL discovery.
+  bool enable_dial_discovery;
+
+  // If the MRP should enable Cast discovery.
+  bool enable_cast_discovery;
+};
+
 // Modeled after the MediaRouter interface defined in
 // chrome/browser/media/router/media_router.h
 interface MediaRouteProvider {
@@ -423,7 +433,7 @@
   // Returns a string that uniquely identifies the Media Router browser
   // process.
   RegisterMediaRouteProvider(MediaRouteProvider media_router_provider) =>
-      (string instance_id);
+      (string instance_id, MediaRouteProviderConfig config);
 
   // Called when the Media Route Manager receives a new list of |sinks|
   // compatible with |media_source|. The result is only valid for |origins|. If
diff --git a/chrome/renderer/resources/extensions/media_router_bindings.js b/chrome/renderer/resources/extensions/media_router_bindings.js
index c6554c1..264918331 100644
--- a/chrome/renderer/resources/extensions/media_router_bindings.js
+++ b/chrome/renderer/resources/extensions/media_router_bindings.js
@@ -295,14 +295,11 @@
 
   /**
    * Registers the Media Router Provider Manager with the Media Router.
-   * @return {!Promise<string>} Instance ID for the Media Router.
+   * @return {!Promise<Object>} Instance ID and config for the Media Router.
    */
   MediaRouter.prototype.start = function() {
     return this.service_.registerMediaRouteProvider(
-        this.mediaRouteProviderBinding_.createInterfacePtrAndBind()).then(
-            function(result) {
-      return result.instance_id;
-    }.bind(this));
+        this.mediaRouteProviderBinding_.createInterfacePtrAndBind());
   }
 
   /**
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index d4c30117..8610688 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1521,7 +1521,7 @@
       "../browser/renderer_context_menu/spelling_menu_observer_browsertest.cc",
       "../browser/renderer_host/render_process_host_chrome_browsertest.cc",
       "../browser/repost_form_warning_browsertest.cc",
-      "../browser/safe_browsing/chrome_cleaner/srt_fetcher_browsertest_win.cc",
+      "../browser/safe_browsing/chrome_cleaner/reporter_runner_browsertest_win.cc",
       "../browser/safe_json_parser_browsertest.cc",
       "../browser/search/hotword_installer_browsertest.cc",
       "../browser/search/suggestions/image_fetcher_impl_browsertest.cc",
@@ -3498,6 +3498,7 @@
       "../browser/profile_resetter/profile_resetter_unittest.cc",
       "../browser/profile_resetter/triggered_profile_resetter_win_unittest.cc",
       "../browser/renderer_context_menu/render_view_context_menu_unittest.cc",
+      "../browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win_unittest.cc",
       "../browser/search/instant_service_unittest.cc",
       "../browser/search/instant_unittest_base.cc",
       "../browser/search/instant_unittest_base.h",
diff --git a/components/dom_distiller/standalone/content_extractor_browsertest.cc b/components/dom_distiller/standalone/content_extractor_browsertest.cc
index 9047ce71..86aae71 100644
--- a/components/dom_distiller/standalone/content_extractor_browsertest.cc
+++ b/components/dom_distiller/standalone/content_extractor_browsertest.cc
@@ -16,6 +16,7 @@
 #include "base/single_thread_task_runner.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
+#include "base/task_scheduler/post_task.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "components/dom_distiller/content/browser/distiller_javascript_utils.h"
 #include "components/dom_distiller/content/browser/distiller_page_web_contents.h"
@@ -31,7 +32,6 @@
 #include "components/leveldb_proto/proto_database_impl.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "content/public/browser/browser_context.h"
-#include "content/public/browser/browser_thread.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/common/isolated_world_ids.h"
 #include "content/public/test/content_browser_test.h"
@@ -126,10 +126,8 @@
     content::BrowserContext* context,
     const base::FilePath& db_path,
     const FileToUrlMap& file_to_url_map) {
-  base::SequencedWorkerPool* blocking_pool =
-      content::BrowserThread::GetBlockingPool();
   scoped_refptr<base::SequencedTaskRunner> background_task_runner =
-      blocking_pool->GetSequencedTaskRunner(blocking_pool->GetSequenceToken());
+      base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()});
 
   // TODO(cjhopman): use an in-memory database instead of an on-disk one with
   // temporary directory.
diff --git a/components/favicon/core/large_icon_service.h b/components/favicon/core/large_icon_service.h
index bfea676..d07888a 100644
--- a/components/favicon/core/large_icon_service.h
+++ b/components/favicon/core/large_icon_service.h
@@ -82,6 +82,8 @@
   // WARNING: This function will share the |page_url| with a Google server. This
   // Can be used only for urls that are not privacy sensitive or for users that
   // sync their history with Google servers.
+  // TODO(jkrcal): It is not clear from the name of this function, that it
+  // actually adds the icon to the local cache. Maybe "StoreLargeIcon..."?
   void GetLargeIconOrFallbackStyleFromGoogleServerSkippingLocalCache(
       const GURL& page_url,
       int min_source_size_in_pixel,
diff --git a/components/feature_engagement_tracker/internal/condition_validator.h b/components/feature_engagement_tracker/internal/condition_validator.h
index 0e65e03ae..46eb68ec 100644
--- a/components/feature_engagement_tracker/internal/condition_validator.h
+++ b/components/feature_engagement_tracker/internal/condition_validator.h
@@ -17,6 +17,7 @@
 }  // namespace base
 
 namespace feature_engagement_tracker {
+struct FeatureConfig;
 class Model;
 
 // A ConditionValidator checks the requred conditions for a given feature,
@@ -60,8 +61,15 @@
 
   // Returns a Result object that describes whether each condition has been met.
   virtual Result MeetsConditions(const base::Feature& feature,
+                                 const FeatureConfig& config,
                                  const Model& model,
-                                 uint32_t current_day) = 0;
+                                 uint32_t current_day) const = 0;
+
+  // Must be called to notify that the |feature| is currently showing.
+  virtual void NotifyIsShowing(const base::Feature& feature) = 0;
+
+  // Must be called to notify that the |feature| is no longer showing.
+  virtual void NotifyDismissed(const base::Feature& feature) = 0;
 
  protected:
   ConditionValidator() = default;
diff --git a/components/feature_engagement_tracker/internal/editable_configuration_unittest.cc b/components/feature_engagement_tracker/internal/editable_configuration_unittest.cc
index da2c6e2..ee347bdd 100644
--- a/components/feature_engagement_tracker/internal/editable_configuration_unittest.cc
+++ b/components/feature_engagement_tracker/internal/editable_configuration_unittest.cc
@@ -7,8 +7,6 @@
 #include <string>
 
 #include "base/feature_list.h"
-#include "base/metrics/field_trial.h"
-#include "base/test/scoped_feature_list.h"
 #include "components/feature_engagement_tracker/internal/configuration.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -32,15 +30,12 @@
   }
 
  protected:
-  base::test::ScopedFeatureList scoped_feature_list_;
   EditableConfiguration configuration_;
 };
 
 }  // namespace
 
 TEST_F(EditableConfigurationTest, SingleConfigAddAndGet) {
-  scoped_feature_list_.InitWithFeatures({kTestFeatureFoo}, {});
-
   FeatureConfig foo_config = CreateFeatureConfig("foo", true);
   configuration_.SetConfiguration(&kTestFeatureFoo, foo_config);
   const FeatureConfig& foo_config_result =
@@ -50,8 +45,6 @@
 }
 
 TEST_F(EditableConfigurationTest, TwoConfigAddAndGet) {
-  scoped_feature_list_.InitWithFeatures({kTestFeatureFoo, kTestFeatureBar}, {});
-
   FeatureConfig foo_config = CreateFeatureConfig("foo", true);
   configuration_.SetConfiguration(&kTestFeatureFoo, foo_config);
   FeatureConfig bar_config = CreateFeatureConfig("bar", true);
@@ -67,8 +60,6 @@
 }
 
 TEST_F(EditableConfigurationTest, ConfigShouldBeEditable) {
-  scoped_feature_list_.InitWithFeatures({kTestFeatureFoo}, {});
-
   FeatureConfig valid_foo_config = CreateFeatureConfig("foo", true);
   configuration_.SetConfiguration(&kTestFeatureFoo, valid_foo_config);
 
diff --git a/components/feature_engagement_tracker/internal/feature_config_storage_validator.cc b/components/feature_engagement_tracker/internal/feature_config_storage_validator.cc
index fe08b24..4cc0be74 100644
--- a/components/feature_engagement_tracker/internal/feature_config_storage_validator.cc
+++ b/components/feature_engagement_tracker/internal/feature_config_storage_validator.cc
@@ -7,6 +7,7 @@
 #include <unordered_map>
 #include <unordered_set>
 
+#include "base/feature_list.h"
 #include "components/feature_engagement_tracker/internal/configuration.h"
 #include "components/feature_engagement_tracker/public/feature_list.h"
 
@@ -45,8 +46,12 @@
 void FeatureConfigStorageValidator::InitializeFeatures(
     FeatureVector features,
     const Configuration& configuration) {
-  for (const auto* feature : features)
+  for (const auto* feature : features) {
+    if (!base::FeatureList::IsEnabled(*feature))
+      continue;
+
     InitializeFeatureConfig(configuration.GetFeatureConfig(*feature));
+  }
 }
 
 void FeatureConfigStorageValidator::ClearForTesting() {
diff --git a/components/feature_engagement_tracker/internal/feature_config_storage_validator_unittest.cc b/components/feature_engagement_tracker/internal/feature_config_storage_validator_unittest.cc
index 6e973bd..3e7f802 100644
--- a/components/feature_engagement_tracker/internal/feature_config_storage_validator_unittest.cc
+++ b/components/feature_engagement_tracker/internal/feature_config_storage_validator_unittest.cc
@@ -171,6 +171,7 @@
  protected:
   FeatureConfigStorageValidator validator_;
   uint32_t current_day_;
+  base::test::ScopedFeatureList scoped_feature_list_;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(FeatureConfigStorageValidatorTest);
@@ -179,7 +180,24 @@
 }  // namespace
 
 TEST_F(FeatureConfigStorageValidatorTest,
+       ShouldOnlyUseConfigFromEnabledFeatures) {
+  scoped_feature_list_.InitWithFeatures({kTestFeatureFoo}, {kTestFeatureBar});
+
+  FeatureConfig foo_config = kNeverStored;
+  foo_config.used = EventConfig("fooevent", Comparator(ANY, 0), 0, 1);
+  FeatureConfig bar_config = kNeverStored;
+  bar_config.used = EventConfig("barevent", Comparator(ANY, 0), 0, 1);
+  UseConfigs(foo_config, bar_config);
+
+  EXPECT_FALSE(validator_.ShouldStore("myevent"));
+  EXPECT_TRUE(validator_.ShouldStore("fooevent"));
+  EXPECT_FALSE(validator_.ShouldStore("barevent"));
+}
+
+TEST_F(FeatureConfigStorageValidatorTest,
        ShouldStoreIfSingleConfigHasMinimum1DayStorage) {
+  scoped_feature_list_.InitWithFeatures({kTestFeatureFoo}, {});
+
   UseConfig(kNeverStored);
   EXPECT_FALSE(validator_.ShouldStore("myevent"));
 
@@ -197,6 +215,8 @@
 
 TEST_F(FeatureConfigStorageValidatorTest,
        ShouldStoreIfAnyConfigHasMinimum1DayStorage) {
+  scoped_feature_list_.InitWithFeatures({kTestFeatureFoo, kTestFeatureBar}, {});
+
   UseConfigs(kNeverStored, kNeverStored);
   EXPECT_FALSE(validator_.ShouldStore("myevent"));
 
@@ -214,6 +234,8 @@
 
 TEST_F(FeatureConfigStorageValidatorTest,
        ShouldKeepIfSingleConfigMeetsEventAge) {
+  scoped_feature_list_.InitWithFeatures({kTestFeatureFoo}, {});
+
   UseConfig(kNeverStored);
   VerifyNeverKeep();
 
@@ -241,6 +263,8 @@
 }
 
 TEST_F(FeatureConfigStorageValidatorTest, ShouldKeepIfAnyConfigMeetsEventAge) {
+  scoped_feature_list_.InitWithFeatures({kTestFeatureFoo, kTestFeatureBar}, {});
+
   UseConfigs(kNeverStored, kNeverStored);
   VerifyNeverKeep();
 
diff --git a/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.cc b/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.cc
index 2c93fa2c..0be0a9f 100644
--- a/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.cc
+++ b/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.cc
@@ -86,12 +86,13 @@
     std::unique_ptr<ConditionValidator> condition_validator,
     std::unique_ptr<StorageValidator> storage_validator,
     std::unique_ptr<TimeProvider> time_provider)
-    : condition_validator_(std::move(condition_validator)),
+    : configuration_(std::move(configuration)),
+      condition_validator_(std::move(condition_validator)),
       time_provider_(std::move(time_provider)),
       initialization_finished_(false),
       weak_ptr_factory_(this) {
-  model_ = base::MakeUnique<ModelImpl>(
-      std::move(store), std::move(configuration), std::move(storage_validator));
+  model_ = base::MakeUnique<ModelImpl>(std::move(store),
+                                       std::move(storage_validator));
   model_->Initialize(
       base::Bind(&FeatureEngagementTrackerImpl::OnModelInitializationFinished,
                  weak_ptr_factory_.GetWeakPtr()));
@@ -109,16 +110,18 @@
   // TODO(nyquist): Track this event in UMA.
   bool result =
       condition_validator_
-          ->MeetsConditions(feature, *model_, time_provider_->GetCurrentDay())
+          ->MeetsConditions(feature, configuration_->GetFeatureConfig(feature),
+                            *model_, time_provider_->GetCurrentDay())
           .NoErrors();
   if (result)
-    model_->SetIsCurrentlyShowing(true);
+    condition_validator_->NotifyIsShowing(feature);
+
   return result;
 }
 
 void FeatureEngagementTrackerImpl::Dismissed(const base::Feature& feature) {
   // TODO(nyquist): Track this event in UMA.
-  model_->SetIsCurrentlyShowing(false);
+  condition_validator_->NotifyDismissed(feature);
 }
 
 bool FeatureEngagementTrackerImpl::IsInitialized() {
diff --git a/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.h b/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.h
index 0a8be3e..701030b 100644
--- a/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.h
+++ b/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl.h
@@ -48,6 +48,9 @@
   // The current model.
   std::unique_ptr<Model> model_;
 
+  // The current configuration for all features.
+  std::unique_ptr<Configuration> configuration_;
+
   // The ConditionValidator provides functionality for knowing when to trigger
   // help UI.
   std::unique_ptr<ConditionValidator> condition_validator_;
diff --git a/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl_unittest.cc b/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl_unittest.cc
index fffb0947..e5427267 100644
--- a/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl_unittest.cc
+++ b/components/feature_engagement_tracker/internal/feature_engagement_tracker_impl_unittest.cc
@@ -6,12 +6,11 @@
 
 #include <memory>
 
+#include "base/bind.h"
 #include "base/feature_list.h"
 #include "base/memory/ptr_util.h"
 #include "base/message_loop/message_loop.h"
-#include "base/metrics/field_trial.h"
 #include "base/run_loop.h"
-#include "base/test/scoped_feature_list.h"
 #include "components/feature_engagement_tracker/internal/editable_configuration.h"
 #include "components/feature_engagement_tracker/internal/in_memory_store.h"
 #include "components/feature_engagement_tracker/internal/never_storage_validator.h"
@@ -104,9 +103,6 @@
     RegisterFeatureConfig(configuration.get(), kTestFeatureBar, true);
     RegisterFeatureConfig(configuration.get(), kTestFeatureQux, false);
 
-    scoped_feature_list_.InitWithFeatures(
-        {kTestFeatureFoo, kTestFeatureBar, kTestFeatureQux}, {});
-
     tracker_.reset(new FeatureEngagementTrackerImpl(
         CreateStore(), std::move(configuration),
         base::MakeUnique<OnceConditionValidator>(),
@@ -122,7 +118,6 @@
 
   base::MessageLoop message_loop_;
   std::unique_ptr<FeatureEngagementTrackerImpl> tracker_;
-  base::test::ScopedFeatureList scoped_feature_list_;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(FeatureEngagementTrackerImplTest);
diff --git a/components/feature_engagement_tracker/internal/model.h b/components/feature_engagement_tracker/internal/model.h
index 1fcd8f68..47173238 100644
--- a/components/feature_engagement_tracker/internal/model.h
+++ b/components/feature_engagement_tracker/internal/model.h
@@ -11,13 +11,8 @@
 #include "base/callback.h"
 #include "base/macros.h"
 
-namespace base {
-struct Feature;
-}  // namespace base
-
 namespace feature_engagement_tracker {
 class Event;
-struct FeatureConfig;
 
 // A Model provides all necessary runtime state.
 class Model {
@@ -36,16 +31,6 @@
   // initialized.
   virtual bool IsReady() const = 0;
 
-  // Returns the FeatureConfig for the given |feature|.
-  virtual const FeatureConfig& GetFeatureConfig(
-      const base::Feature& feature) const = 0;
-
-  // Update the state of whether any in-product help is currently showing.
-  virtual void SetIsCurrentlyShowing(bool is_showing) = 0;
-
-  // Returns whether any in-product help is currently showing.
-  virtual bool IsCurrentlyShowing() const = 0;
-
   // Retrieves the Event object for the event with the given name. If the event
   // is not found, a nullptr will be returned. Calling this before the
   // Model has finished initializing will result in undefined behavior.
diff --git a/components/feature_engagement_tracker/internal/model_impl.cc b/components/feature_engagement_tracker/internal/model_impl.cc
index d5f52f7..e3bf8b3 100644
--- a/components/feature_engagement_tracker/internal/model_impl.cc
+++ b/components/feature_engagement_tracker/internal/model_impl.cc
@@ -14,7 +14,6 @@
 #include "base/sequenced_task_runner.h"
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
-#include "components/feature_engagement_tracker/internal/configuration.h"
 #include "components/feature_engagement_tracker/internal/model.h"
 #include "components/feature_engagement_tracker/internal/storage_validator.h"
 #include "components/feature_engagement_tracker/internal/store.h"
@@ -22,14 +21,11 @@
 namespace feature_engagement_tracker {
 
 ModelImpl::ModelImpl(std::unique_ptr<Store> store,
-                     std::unique_ptr<Configuration> configuration,
                      std::unique_ptr<StorageValidator> storage_validator)
     : Model(),
       store_(std::move(store)),
-      configuration_(std::move(configuration)),
       storage_validator_(std::move(storage_validator)),
       ready_(false),
-      currently_showing_(false),
       weak_factory_(this) {}
 
 ModelImpl::~ModelImpl() = default;
@@ -43,19 +39,6 @@
   return ready_;
 }
 
-const FeatureConfig& ModelImpl::GetFeatureConfig(
-    const base::Feature& feature) const {
-  return configuration_->GetFeatureConfig(feature);
-}
-
-void ModelImpl::SetIsCurrentlyShowing(bool is_showing) {
-  currently_showing_ = is_showing;
-}
-
-bool ModelImpl::IsCurrentlyShowing() const {
-  return currently_showing_;
-}
-
 const Event* ModelImpl::GetEvent(const std::string& event_name) const {
   auto search = events_.find(event_name);
   if (search == events_.end())
diff --git a/components/feature_engagement_tracker/internal/model_impl.h b/components/feature_engagement_tracker/internal/model_impl.h
index 6c48672f..315cfbbc 100644
--- a/components/feature_engagement_tracker/internal/model_impl.h
+++ b/components/feature_engagement_tracker/internal/model_impl.h
@@ -15,12 +15,7 @@
 #include "components/feature_engagement_tracker/internal/model.h"
 #include "components/feature_engagement_tracker/internal/proto/event.pb.h"
 
-namespace base {
-struct Feature;
-}
-
 namespace feature_engagement_tracker {
-class Configuration;
 class StorageValidator;
 class Store;
 
@@ -28,17 +23,12 @@
 class ModelImpl : public Model {
  public:
   ModelImpl(std::unique_ptr<Store> store,
-            std::unique_ptr<Configuration> configuration,
             std::unique_ptr<StorageValidator> storage_validator);
   ~ModelImpl() override;
 
   // Model implementation.
   void Initialize(const OnModelInitializationFinished& callback) override;
   bool IsReady() const override;
-  const FeatureConfig& GetFeatureConfig(
-      const base::Feature& feature) const override;
-  void SetIsCurrentlyShowing(bool is_showing) override;
-  bool IsCurrentlyShowing() const override;
   const Event* GetEvent(const std::string& event_name) const override;
   void IncrementEvent(const std::string& event_name,
                       uint32_t current_day) override;
@@ -56,9 +46,6 @@
   // The underlying store for all events.
   std::unique_ptr<Store> store_;
 
-  // The current configuration for all features.
-  std::unique_ptr<Configuration> configuration_;
-
   // A utility for checking whether new events should be stored and for whether
   // old events should be kept.
   std::unique_ptr<StorageValidator> storage_validator_;
@@ -69,9 +56,6 @@
   // Whether the model has been fully initialized.
   bool ready_;
 
-  // Whether the model is currently showing an in-product help.
-  bool currently_showing_;
-
   base::WeakPtrFactory<ModelImpl> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(ModelImpl);
diff --git a/components/feature_engagement_tracker/internal/model_impl_unittest.cc b/components/feature_engagement_tracker/internal/model_impl_unittest.cc
index b94d2a4..3a35ae5 100644
--- a/components/feature_engagement_tracker/internal/model_impl_unittest.cc
+++ b/components/feature_engagement_tracker/internal/model_impl_unittest.cc
@@ -11,9 +11,7 @@
 #include "base/feature_list.h"
 #include "base/memory/ptr_util.h"
 #include "base/message_loop/message_loop.h"
-#include "base/metrics/field_trial.h"
 #include "base/run_loop.h"
-#include "base/test/scoped_feature_list.h"
 #include "components/feature_engagement_tracker/internal/editable_configuration.h"
 #include "components/feature_engagement_tracker/internal/in_memory_store.h"
 #include "components/feature_engagement_tracker/internal/never_storage_validator.h"
@@ -24,19 +22,6 @@
 namespace feature_engagement_tracker {
 
 namespace {
-const base::Feature kTestFeatureFoo{"test_foo",
-                                    base::FEATURE_DISABLED_BY_DEFAULT};
-const base::Feature kTestFeatureBar{"test_bar",
-                                    base::FEATURE_DISABLED_BY_DEFAULT};
-
-void RegisterFeatureConfig(EditableConfiguration* configuration,
-                           const base::Feature& feature,
-                           bool valid) {
-  FeatureConfig config;
-  config.valid = valid;
-  config.used.name = feature.name;
-  configuration->SetConfiguration(&feature, config);
-}
 
 // A test-only implementation of InMemoryStore that tracks calls to
 // WriteEvent(...).
@@ -99,19 +84,10 @@
       : got_initialize_callback_(false), initialize_callback_result_(false) {}
 
   void SetUp() override {
-    std::unique_ptr<EditableConfiguration> configuration =
-        base::MakeUnique<EditableConfiguration>();
-
-    RegisterFeatureConfig(configuration.get(), kTestFeatureFoo, true);
-    RegisterFeatureConfig(configuration.get(), kTestFeatureBar, true);
-
-    scoped_feature_list_.InitWithFeatures({kTestFeatureFoo, kTestFeatureBar},
-                                          {});
-
     std::unique_ptr<TestInMemoryStore> store = CreateStore();
     store_ = store.get();
 
-    model_.reset(new ModelImpl(std::move(store), std::move(configuration),
+    model_.reset(new ModelImpl(std::move(store),
                                base::MakeUnique<NeverStorageValidator>()));
   }
 
@@ -132,7 +108,6 @@
 
  private:
   base::MessageLoop message_loop_;
-  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 class LoadFailingModelImplTest : public ModelImplTest {
@@ -300,24 +275,6 @@
   test::VerifyEventsEqual(bar_event2, store_->GetLastWrittenEvent());
 }
 
-TEST_F(ModelImplTest, ShowState) {
-  model_->Initialize(base::Bind(&ModelImplTest::OnModelInitializationFinished,
-                                base::Unretained(this)));
-  base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(model_->IsReady());
-
-  EXPECT_FALSE(model_->IsCurrentlyShowing());
-
-  model_->SetIsCurrentlyShowing(false);
-  EXPECT_FALSE(model_->IsCurrentlyShowing());
-
-  model_->SetIsCurrentlyShowing(true);
-  EXPECT_TRUE(model_->IsCurrentlyShowing());
-
-  model_->SetIsCurrentlyShowing(false);
-  EXPECT_FALSE(model_->IsCurrentlyShowing());
-}
-
 TEST_F(LoadFailingModelImplTest, FailedInitializeInformsCaller) {
   model_->Initialize(base::Bind(&ModelImplTest::OnModelInitializationFinished,
                                 base::Unretained(this)));
diff --git a/components/feature_engagement_tracker/internal/never_condition_validator.cc b/components/feature_engagement_tracker/internal/never_condition_validator.cc
index 7d01664..fb8b336 100644
--- a/components/feature_engagement_tracker/internal/never_condition_validator.cc
+++ b/components/feature_engagement_tracker/internal/never_condition_validator.cc
@@ -12,9 +12,14 @@
 
 ConditionValidator::Result NeverConditionValidator::MeetsConditions(
     const base::Feature& feature,
+    const FeatureConfig& config,
     const Model& model,
-    uint32_t current_day) {
+    uint32_t current_day) const {
   return ConditionValidator::Result(false);
 }
 
+void NeverConditionValidator::NotifyIsShowing(const base::Feature& feature) {}
+
+void NeverConditionValidator::NotifyDismissed(const base::Feature& feature) {}
+
 }  // namespace feature_engagement_tracker
diff --git a/components/feature_engagement_tracker/internal/never_condition_validator.h b/components/feature_engagement_tracker/internal/never_condition_validator.h
index 4753637..15140c7 100644
--- a/components/feature_engagement_tracker/internal/never_condition_validator.h
+++ b/components/feature_engagement_tracker/internal/never_condition_validator.h
@@ -24,9 +24,13 @@
   ~NeverConditionValidator() override;
 
   // ConditionValidator implementation.
-  ConditionValidator::Result MeetsConditions(const base::Feature& feature,
-                                             const Model& model,
-                                             uint32_t current_day) override;
+  ConditionValidator::Result MeetsConditions(
+      const base::Feature& feature,
+      const FeatureConfig& config,
+      const Model& model,
+      uint32_t current_day) const override;
+  void NotifyIsShowing(const base::Feature& feature) override;
+  void NotifyDismissed(const base::Feature& feature) override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(NeverConditionValidator);
diff --git a/components/feature_engagement_tracker/internal/never_condition_validator_unittest.cc b/components/feature_engagement_tracker/internal/never_condition_validator_unittest.cc
index d923554a..7991302f1 100644
--- a/components/feature_engagement_tracker/internal/never_condition_validator_unittest.cc
+++ b/components/feature_engagement_tracker/internal/never_condition_validator_unittest.cc
@@ -7,8 +7,6 @@
 #include <string>
 
 #include "base/feature_list.h"
-#include "base/metrics/field_trial.h"
-#include "base/test/scoped_feature_list.h"
 #include "components/feature_engagement_tracker/internal/configuration.h"
 #include "components/feature_engagement_tracker/internal/model.h"
 #include "components/feature_engagement_tracker/internal/proto/event.pb.h"
@@ -26,24 +24,12 @@
 // A Model that is always postive to show in-product help.
 class TestModel : public Model {
  public:
-  TestModel() {
-    feature_config_.valid = true;
-    feature_config_.used.name = "foobar";
-  }
+  TestModel() = default;
 
   void Initialize(const OnModelInitializationFinished& callback) override {}
 
   bool IsReady() const override { return true; }
 
-  const FeatureConfig& GetFeatureConfig(
-      const base::Feature& feature) const override {
-    return feature_config_;
-  }
-
-  void SetIsCurrentlyShowing(bool is_showing) override {}
-
-  bool IsCurrentlyShowing() const override { return false; }
-
   const Event* GetEvent(const std::string& event_name) const override {
     return nullptr;
   }
@@ -51,8 +37,6 @@
   void IncrementEvent(const std::string& event_name, uint32_t day) override {}
 
  private:
-  FeatureConfig feature_config_;
-
   DISALLOW_COPY_AND_ASSIGN(TestModel);
 };
 
@@ -61,7 +45,6 @@
   NeverConditionValidatorTest() = default;
 
  protected:
-  base::test::ScopedFeatureList scoped_feature_list_;
   TestModel model_;
   NeverConditionValidator validator_;
 
@@ -72,11 +55,12 @@
 }  // namespace
 
 TEST_F(NeverConditionValidatorTest, ShouldNeverMeetConditions) {
-  scoped_feature_list_.InitWithFeatures({kTestFeatureFoo, kTestFeatureBar}, {});
   EXPECT_FALSE(
-      validator_.MeetsConditions(kTestFeatureFoo, model_, 0u).NoErrors());
+      validator_.MeetsConditions(kTestFeatureFoo, FeatureConfig(), model_, 0u)
+          .NoErrors());
   EXPECT_FALSE(
-      validator_.MeetsConditions(kTestFeatureBar, model_, 0u).NoErrors());
+      validator_.MeetsConditions(kTestFeatureBar, FeatureConfig(), model_, 0u)
+          .NoErrors());
 }
 
 }  // namespace feature_engagement_tracker
diff --git a/components/feature_engagement_tracker/internal/once_condition_validator.cc b/components/feature_engagement_tracker/internal/once_condition_validator.cc
index e74e9b34..ed4c347 100644
--- a/components/feature_engagement_tracker/internal/once_condition_validator.cc
+++ b/components/feature_engagement_tracker/internal/once_condition_validator.cc
@@ -9,31 +9,39 @@
 
 namespace feature_engagement_tracker {
 
-OnceConditionValidator::OnceConditionValidator() = default;
+OnceConditionValidator::OnceConditionValidator()
+    : currently_showing_feature_(nullptr) {}
 
 OnceConditionValidator::~OnceConditionValidator() = default;
 
 ConditionValidator::Result OnceConditionValidator::MeetsConditions(
     const base::Feature& feature,
+    const FeatureConfig& config,
     const Model& model,
-    uint32_t current_day) {
+    uint32_t current_day) const {
   ConditionValidator::Result result(true);
   result.model_ready_ok = model.IsReady();
 
-  result.currently_showing_ok = !model.IsCurrentlyShowing();
+  result.currently_showing_ok = currently_showing_feature_ == nullptr;
 
-  const FeatureConfig& config = model.GetFeatureConfig(feature);
   result.config_ok = config.valid;
 
   result.session_rate_ok =
       shown_features_.find(&feature) == shown_features_.end();
 
-  // Only show if there are no other errors.
-  if (result.NoErrors() && result.session_rate_ok) {
-    shown_features_.insert(&feature);
-  }
-
   return result;
 }
 
+void OnceConditionValidator::NotifyIsShowing(const base::Feature& feature) {
+  DCHECK(currently_showing_feature_ == nullptr);
+  DCHECK(shown_features_.find(&feature) == shown_features_.end());
+  shown_features_.insert(&feature);
+  currently_showing_feature_ = &feature;
+}
+
+void OnceConditionValidator::NotifyDismissed(const base::Feature& feature) {
+  DCHECK(&feature == currently_showing_feature_);
+  currently_showing_feature_ = nullptr;
+}
+
 }  // namespace feature_engagement_tracker
diff --git a/components/feature_engagement_tracker/internal/once_condition_validator.h b/components/feature_engagement_tracker/internal/once_condition_validator.h
index 10738eb..4ede10ca 100644
--- a/components/feature_engagement_tracker/internal/once_condition_validator.h
+++ b/components/feature_engagement_tracker/internal/once_condition_validator.h
@@ -37,14 +37,22 @@
   ~OnceConditionValidator() override;
 
   // ConditionValidator implementation.
-  ConditionValidator::Result MeetsConditions(const base::Feature& feature,
-                                             const Model& model,
-                                             uint32_t current_day) override;
+  ConditionValidator::Result MeetsConditions(
+      const base::Feature& feature,
+      const FeatureConfig& config,
+      const Model& model,
+      uint32_t current_day) const override;
+  void NotifyIsShowing(const base::Feature& feature) override;
+  void NotifyDismissed(const base::Feature& feature) override;
 
  private:
   // Contains all features that have met conditions within the current session.
   std::unordered_set<const base::Feature*> shown_features_;
 
+  // Which feature that is currently being shown, or nullptr if nothing is
+  // currently showing.
+  const base::Feature* currently_showing_feature_;
+
   DISALLOW_COPY_AND_ASSIGN(OnceConditionValidator);
 };
 
diff --git a/components/feature_engagement_tracker/internal/once_condition_validator_unittest.cc b/components/feature_engagement_tracker/internal/once_condition_validator_unittest.cc
index c85b790e..cce2bf1 100644
--- a/components/feature_engagement_tracker/internal/once_condition_validator_unittest.cc
+++ b/components/feature_engagement_tracker/internal/once_condition_validator_unittest.cc
@@ -7,8 +7,6 @@
 #include <string>
 
 #include "base/feature_list.h"
-#include "base/metrics/field_trial.h"
-#include "base/test/scoped_feature_list.h"
 #include "components/feature_engagement_tracker/internal/editable_configuration.h"
 #include "components/feature_engagement_tracker/internal/model.h"
 #include "components/feature_engagement_tracker/internal/proto/event.pb.h"
@@ -23,10 +21,13 @@
 const base::Feature kTestFeatureBar{"test_bar",
                                     base::FEATURE_DISABLED_BY_DEFAULT};
 
+FeatureConfig kValidFeatureConfig;
+FeatureConfig kInvalidFeatureConfig;
+
 // A Model that is easily configurable at runtime.
 class TestModel : public Model {
  public:
-  TestModel() : ready_(false), is_showing_(false) {}
+  TestModel() : ready_(false) { kValidFeatureConfig.valid = true; }
 
   void Initialize(const OnModelInitializationFinished& callback) override {}
 
@@ -34,19 +35,6 @@
 
   void SetIsReady(bool ready) { ready_ = ready; }
 
-  const FeatureConfig& GetFeatureConfig(
-      const base::Feature& feature) const override {
-    return configuration_.GetFeatureConfig(feature);
-  }
-
-  void SetIsCurrentlyShowing(bool is_showing) override {
-    is_showing_ = is_showing;
-  }
-
-  bool IsCurrentlyShowing() const override { return is_showing_; }
-
-  EditableConfiguration& GetConfiguration() { return configuration_; }
-
   const Event* GetEvent(const std::string& event_name) const override {
     return nullptr;
   }
@@ -54,9 +42,7 @@
   void IncrementEvent(const std::string& event_name, uint32_t day) override {}
 
  private:
-  EditableConfiguration configuration_;
   bool ready_;
-  bool is_showing_;
 };
 
 class OnceConditionValidatorTest : public ::testing::Test {
@@ -66,14 +52,8 @@
     model_.SetIsReady(true);
   }
 
-  void AddFeature(const base::Feature& feature, bool valid) {
-    FeatureConfig feature_config;
-    feature_config.valid = valid;
-    model_.GetConfiguration().SetConfiguration(&feature, feature_config);
-  }
-
  protected:
-  base::test::ScopedFeatureList scoped_feature_list_;
+  EditableConfiguration configuration_;
   TestModel model_;
   OnceConditionValidator validator_;
 
@@ -84,99 +64,84 @@
 }  // namespace
 
 TEST_F(OnceConditionValidatorTest, EnabledFeatureShouldTriggerOnce) {
-  scoped_feature_list_.InitWithFeatures({kTestFeatureFoo}, {});
-
-  // Initialize validator with one enabled and valid feature.
-  AddFeature(kTestFeatureFoo, true);
-
   // Only the first call to MeetsConditions() should lead to enlightenment.
   EXPECT_TRUE(
-      validator_.MeetsConditions(kTestFeatureFoo, model_, 0u).NoErrors());
-  ConditionValidator::Result result =
-      validator_.MeetsConditions(kTestFeatureFoo, model_, 0u);
+      validator_
+          .MeetsConditions(kTestFeatureFoo, kValidFeatureConfig, model_, 0u)
+          .NoErrors());
+  validator_.NotifyIsShowing(kTestFeatureFoo);
+  ConditionValidator::Result result = validator_.MeetsConditions(
+      kTestFeatureFoo, kValidFeatureConfig, model_, 0u);
   EXPECT_FALSE(result.NoErrors());
   EXPECT_FALSE(result.session_rate_ok);
 }
 
 TEST_F(OnceConditionValidatorTest,
        BothEnabledAndDisabledFeaturesShouldTrigger) {
-  scoped_feature_list_.InitWithFeatures({kTestFeatureFoo}, {kTestFeatureBar});
-
-  // Initialize validator with one enabled and one disabled feature, both valid.
-  AddFeature(kTestFeatureFoo, true);
-  AddFeature(kTestFeatureBar, true);
-
   // Only the kTestFeatureFoo feature should lead to enlightenment, since
   // kTestFeatureBar is disabled. Ordering disabled feature first to ensure this
   // captures a different behavior than the
   // OnlyOneFeatureShouldTriggerPerSession test below.
   EXPECT_TRUE(
-      validator_.MeetsConditions(kTestFeatureBar, model_, 0u).NoErrors());
+      validator_
+          .MeetsConditions(kTestFeatureBar, kValidFeatureConfig, model_, 0u)
+          .NoErrors());
   EXPECT_TRUE(
-      validator_.MeetsConditions(kTestFeatureFoo, model_, 0u).NoErrors());
+      validator_
+          .MeetsConditions(kTestFeatureFoo, kValidFeatureConfig, model_, 0u)
+          .NoErrors());
 }
 
 TEST_F(OnceConditionValidatorTest, StillTriggerWhenAllFeaturesDisabled) {
-  scoped_feature_list_.InitWithFeatures({}, {kTestFeatureFoo, kTestFeatureBar});
-
-  // Initialize validator with two enabled features, both valid.
-  AddFeature(kTestFeatureFoo, true);
-  AddFeature(kTestFeatureBar, true);
-
   // No features should get to show enlightenment.
   EXPECT_TRUE(
-      validator_.MeetsConditions(kTestFeatureFoo, model_, 0u).NoErrors());
+      validator_
+          .MeetsConditions(kTestFeatureFoo, kValidFeatureConfig, model_, 0u)
+          .NoErrors());
   EXPECT_TRUE(
-      validator_.MeetsConditions(kTestFeatureBar, model_, 0u).NoErrors());
+      validator_
+          .MeetsConditions(kTestFeatureBar, kValidFeatureConfig, model_, 0u)
+          .NoErrors());
 }
 
 TEST_F(OnceConditionValidatorTest, OnlyTriggerWhenModelIsReady) {
-  scoped_feature_list_.InitWithFeatures({kTestFeatureFoo}, {});
-
-  // Initialize validator with a single valid feature.
-  AddFeature(kTestFeatureFoo, true);
-
   model_.SetIsReady(false);
-  ConditionValidator::Result result =
-      validator_.MeetsConditions(kTestFeatureFoo, model_, 0u);
+  ConditionValidator::Result result = validator_.MeetsConditions(
+      kTestFeatureFoo, kValidFeatureConfig, model_, 0u);
   EXPECT_FALSE(result.NoErrors());
   EXPECT_FALSE(result.model_ready_ok);
 
   model_.SetIsReady(true);
   EXPECT_TRUE(
-      validator_.MeetsConditions(kTestFeatureFoo, model_, 0u).NoErrors());
+      validator_
+          .MeetsConditions(kTestFeatureFoo, kValidFeatureConfig, model_, 0u)
+          .NoErrors());
 }
 
 TEST_F(OnceConditionValidatorTest, OnlyTriggerIfNothingElseIsShowing) {
-  scoped_feature_list_.InitWithFeatures({kTestFeatureFoo}, {});
-
-  // Initialize validator with a single valid feature.
-  AddFeature(kTestFeatureFoo, true);
-
-  model_.SetIsCurrentlyShowing(true);
-  ConditionValidator::Result result =
-      validator_.MeetsConditions(kTestFeatureFoo, model_, 0u);
+  validator_.NotifyIsShowing(kTestFeatureBar);
+  ConditionValidator::Result result = validator_.MeetsConditions(
+      kTestFeatureFoo, kValidFeatureConfig, model_, 0u);
   EXPECT_FALSE(result.NoErrors());
   EXPECT_FALSE(result.currently_showing_ok);
 
-  model_.SetIsCurrentlyShowing(false);
+  validator_.NotifyDismissed(kTestFeatureBar);
   EXPECT_TRUE(
-      validator_.MeetsConditions(kTestFeatureFoo, model_, 0u).NoErrors());
+      validator_
+          .MeetsConditions(kTestFeatureFoo, kValidFeatureConfig, model_, 0u)
+          .NoErrors());
 }
 
 TEST_F(OnceConditionValidatorTest, DoNotTriggerForInvalidConfig) {
-  scoped_feature_list_.InitWithFeatures({kTestFeatureFoo}, {});
-
-  AddFeature(kTestFeatureFoo, false);
-  ConditionValidator::Result result =
-      validator_.MeetsConditions(kTestFeatureFoo, model_, 0u);
+  ConditionValidator::Result result = validator_.MeetsConditions(
+      kTestFeatureFoo, kInvalidFeatureConfig, model_, 0u);
   EXPECT_FALSE(result.NoErrors());
   EXPECT_FALSE(result.config_ok);
 
-  // Override config to be valid.
-  AddFeature(kTestFeatureFoo, true);
   EXPECT_TRUE(
-      validator_.MeetsConditions(kTestFeatureFoo, model_, 0u).NoErrors());
+      validator_
+          .MeetsConditions(kTestFeatureFoo, kValidFeatureConfig, model_, 0u)
+          .NoErrors());
 }
 
 }  // namespace feature_engagement_tracker
diff --git a/components/feature_engagement_tracker/internal/single_invalid_configuration_unittest.cc b/components/feature_engagement_tracker/internal/single_invalid_configuration_unittest.cc
index d4c2666..ed6ebba 100644
--- a/components/feature_engagement_tracker/internal/single_invalid_configuration_unittest.cc
+++ b/components/feature_engagement_tracker/internal/single_invalid_configuration_unittest.cc
@@ -5,8 +5,6 @@
 #include "components/feature_engagement_tracker/internal/single_invalid_configuration.h"
 
 #include "base/feature_list.h"
-#include "base/metrics/field_trial.h"
-#include "base/test/scoped_feature_list.h"
 #include "components/feature_engagement_tracker/internal/configuration.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -24,7 +22,6 @@
   SingleInvalidConfigurationTest() = default;
 
  protected:
-  base::test::ScopedFeatureList scoped_feature_list_;
   SingleInvalidConfiguration configuration_;
 
  private:
@@ -34,8 +31,6 @@
 }  // namespace
 
 TEST_F(SingleInvalidConfigurationTest, AllConfigurationsAreInvalid) {
-  scoped_feature_list_.InitWithFeatures({kTestFeatureFoo, kTestFeatureBar}, {});
-
   FeatureConfig foo_config = configuration_.GetFeatureConfig(kTestFeatureFoo);
   EXPECT_FALSE(foo_config.valid);
 
diff --git a/components/ntp_tiles/icon_cacher.h b/components/ntp_tiles/icon_cacher.h
index bfea296..8b5a2d3 100644
--- a/components/ntp_tiles/icon_cacher.h
+++ b/components/ntp_tiles/icon_cacher.h
@@ -10,12 +10,12 @@
 
 namespace ntp_tiles {
 
-// Ensures that a Popular Sites icon is cached, downloading and saving it if
-// not.
+// Ensures that Popular Sites icons and MostLikely icons are cached, downloading
+// and saving them if not.
 //
-// Does not provide any way to get a fetched favicon; use the FaviconService for
-// that. All this interface guarantees is that FaviconService will be able to
-// get you an icon (if it exists).
+// Does not provide any way to get a fetched favicon; use the FaviconService /
+// LargeIconService for that. All this interface guarantees is that
+// FaviconService will be able to get you an icon (if it exists).
 class IconCacher {
  public:
   virtual ~IconCacher() = default;
@@ -25,9 +25,15 @@
   // If there are preliminary icons (e.g. provided by static resources), the
   // optional |preliminary_icon_available| callback will be invoked in addition.
   // TODO(fhorschig): In case we keep these, make them OnceClosures.
-  virtual void StartFetch(PopularSites::Site site,
-                          const base::Closure& icon_available,
-                          const base::Closure& preliminary_icon_available) = 0;
+  virtual void StartFetchPopularSites(
+      PopularSites::Site site,
+      const base::Closure& icon_available,
+      const base::Closure& preliminary_icon_available) = 0;
+
+  // Fetches the icon if necessary, then invokes |done| with true if it was
+  // newly fetched (false if it was already cached or could not be fetched).
+  virtual void StartFetchMostLikely(const GURL& page_url,
+                                    const base::Closure& icon_available) = 0;
 };
 
 }  // namespace ntp_tiles
diff --git a/components/ntp_tiles/icon_cacher_impl.cc b/components/ntp_tiles/icon_cacher_impl.cc
index f8480de..d2b3e5fc 100644
--- a/components/ntp_tiles/icon_cacher_impl.cc
+++ b/components/ntp_tiles/icon_cacher_impl.cc
@@ -9,6 +9,8 @@
 #include "base/memory/ptr_util.h"
 #include "components/favicon/core/favicon_service.h"
 #include "components/favicon/core/favicon_util.h"
+#include "components/favicon/core/large_icon_service.h"
+#include "components/favicon_base/fallback_icon_style.h"
 #include "components/favicon_base/favicon_types.h"
 #include "components/favicon_base/favicon_util.h"
 #include "components/image_fetcher/core/image_decoder.h"
@@ -24,6 +26,12 @@
 
 constexpr int kDesiredFrameSize = 128;
 
+// TODO(jkrcal): Make the size in dip and the scale factor be passed as
+// arguments from the UI so that we desire for the right size on a given device.
+// See crbug.com/696563.
+constexpr int kTileIconMinSizePx = 48;
+constexpr int kTileIconDesiredSizePx = 96;
+
 favicon_base::IconType IconType(const PopularSites::Site& site) {
   return site.large_icon_url.is_valid() ? favicon_base::TOUCH_ICON
                                         : favicon_base::FAVICON;
@@ -34,12 +42,22 @@
                                         : site.favicon_url;
 }
 
+bool HasResultDefaultBackgroundColor(
+    const favicon_base::LargeIconResult& result) {
+  if (!result.fallback_icon_style) {
+    return false;
+  }
+  return result.fallback_icon_style->is_default_background_color;
+}
+
 }  // namespace
 
 IconCacherImpl::IconCacherImpl(
     favicon::FaviconService* favicon_service,
+    favicon::LargeIconService* large_icon_service,
     std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher)
     : favicon_service_(favicon_service),
+      large_icon_service_(large_icon_service),
       image_fetcher_(std::move(image_fetcher)),
       weak_ptr_factory_(this) {
   image_fetcher_->SetDataUseServiceName(
@@ -51,7 +69,7 @@
 
 IconCacherImpl::~IconCacherImpl() = default;
 
-void IconCacherImpl::StartFetch(
+void IconCacherImpl::StartFetchPopularSites(
     PopularSites::Site site,
     const base::Closure& icon_available,
     const base::Closure& preliminary_icon_available) {
@@ -80,12 +98,13 @@
 
   image_fetcher_->StartOrQueueNetworkRequest(
       std::string(), IconURL(site),
-      base::Bind(&IconCacherImpl::OnFaviconDownloaded, base::Unretained(this),
-                 site, base::Passed(std::move(preliminary_callback)),
+      base::Bind(&IconCacherImpl::OnPopularSitesFaviconDownloaded,
+                 base::Unretained(this), site,
+                 base::Passed(std::move(preliminary_callback)),
                  icon_available));
 }
 
-void IconCacherImpl::OnFaviconDownloaded(
+void IconCacherImpl::OnPopularSitesFaviconDownloaded(
     PopularSites::Site site,
     std::unique_ptr<CancelableImageCallback> preliminary_callback,
     const base::Closure& icon_available,
@@ -140,4 +159,43 @@
   return preliminary_callback;
 }
 
+void IconCacherImpl::StartFetchMostLikely(const GURL& page_url,
+                                          const base::Closure& icon_available) {
+  // Desired size 0 means that we do not want the service to resize the image
+  // (as we will not use it anyway).
+  large_icon_service_->GetLargeIconOrFallbackStyle(
+      page_url, kTileIconMinSizePx, /*desired_size_in_pixel=*/0,
+      base::Bind(&IconCacherImpl::OnGetLargeIconOrFallbackStyleFinished,
+                 weak_ptr_factory_.GetWeakPtr(), page_url, icon_available),
+      &tracker_);
+}
+
+void IconCacherImpl::OnGetLargeIconOrFallbackStyleFinished(
+    const GURL& page_url,
+    const base::Closure& icon_available,
+    const favicon_base::LargeIconResult& result) {
+  if (!HasResultDefaultBackgroundColor(result)) {
+    // We should only fetch for default "gray" tiles so that we never overrite
+    // any favicon of any size.
+    return;
+  }
+
+  large_icon_service_
+      ->GetLargeIconOrFallbackStyleFromGoogleServerSkippingLocalCache(
+          page_url, kTileIconMinSizePx, kTileIconDesiredSizePx,
+          base::Bind(&IconCacherImpl::OnMostLikelyFaviconDownloaded,
+                     weak_ptr_factory_.GetWeakPtr(), icon_available));
+}
+
+void IconCacherImpl::OnMostLikelyFaviconDownloaded(
+    const base::Closure& icon_available,
+    bool success) {
+  if (!success) {
+    return;
+  }
+  if (icon_available) {
+    icon_available.Run();
+  }
+}
+
 }  // namespace ntp_tiles
diff --git a/components/ntp_tiles/icon_cacher_impl.h b/components/ntp_tiles/icon_cacher_impl.h
index 58ed735..52bd3fd 100644
--- a/components/ntp_tiles/icon_cacher_impl.h
+++ b/components/ntp_tiles/icon_cacher_impl.h
@@ -17,10 +17,12 @@
 
 namespace favicon {
 class FaviconService;
+class LargeIconService;
 }  // namespace favicon
 
 namespace favicon_base {
 struct FaviconImageResult;
+struct LargeIconResult;
 }  // namespace favicon_base
 
 namespace gfx {
@@ -36,13 +38,19 @@
 
 class IconCacherImpl : public IconCacher {
  public:
+  // TODO(jkrcal): Make this eventually use only LargeIconService.
+  // crbug.com/696563
   IconCacherImpl(favicon::FaviconService* favicon_service,
+                 favicon::LargeIconService* large_icon_service,
                  std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher);
   ~IconCacherImpl() override;
 
-  void StartFetch(PopularSites::Site site,
-                  const base::Closure& icon_available,
-                  const base::Closure& preliminary_icon_available) override;
+  void StartFetchPopularSites(
+      PopularSites::Site site,
+      const base::Closure& icon_available,
+      const base::Closure& preliminary_icon_available) override;
+  void StartFetchMostLikely(const GURL& page_url,
+                            const base::Closure& icon_available) override;
 
  private:
   using CancelableImageCallback =
@@ -54,7 +62,7 @@
       const base::Closure& preliminary_icon_available,
       const favicon_base::FaviconImageResult& result);
 
-  void OnFaviconDownloaded(
+  void OnPopularSitesFaviconDownloaded(
       PopularSites::Site site,
       std::unique_ptr<CancelableImageCallback> preliminary_callback,
       const base::Closure& icon_available,
@@ -69,8 +77,17 @@
                                 const base::Closure& icon_available,
                                 const gfx::Image& image);
 
+  void OnGetLargeIconOrFallbackStyleFinished(
+      const GURL& page_url,
+      const base::Closure& icon_available,
+      const favicon_base::LargeIconResult& result);
+
+  void OnMostLikelyFaviconDownloaded(const base::Closure& icon_available,
+                                     bool success);
+
   base::CancelableTaskTracker tracker_;
   favicon::FaviconService* const favicon_service_;
+  favicon::LargeIconService* const large_icon_service_;
   std::unique_ptr<image_fetcher::ImageFetcher> const image_fetcher_;
 
   base::WeakPtrFactory<IconCacherImpl> weak_ptr_factory_;
diff --git a/components/ntp_tiles/icon_cacher_impl_unittest.cc b/components/ntp_tiles/icon_cacher_impl_unittest.cc
index 4a2ddc2..8ad37b1 100644
--- a/components/ntp_tiles/icon_cacher_impl_unittest.cc
+++ b/components/ntp_tiles/icon_cacher_impl_unittest.cc
@@ -18,6 +18,7 @@
 #include "components/favicon/core/favicon_client.h"
 #include "components/favicon/core/favicon_service_impl.h"
 #include "components/favicon/core/favicon_util.h"
+#include "components/favicon/core/large_icon_service.h"
 #include "components/history/core/browser/history_database_params.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/image_fetcher/core/image_decoder.h"
@@ -94,56 +95,39 @@
   MOCK_METHOD2(GetLocalizedString, bool(int message_id, base::string16* value));
 };
 
-class IconCacherTest : public ::testing::Test {
+ACTION(FailFetch) {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::Bind(arg2, arg0, gfx::Image(), image_fetcher::RequestMetadata()));
+}
+
+ACTION_P2(DecodeSuccessfully, width, height) {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::Bind(arg2, gfx::test::CreateImage(width, height)));
+}
+
+ACTION_P2(PassFetch, width, height) {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::Bind(arg2, arg0, gfx::test::CreateImage(width, height),
+                            image_fetcher::RequestMetadata()));
+}
+
+ACTION_P(Quit, run_loop) {
+  run_loop->Quit();
+}
+
+// TODO(jkrcal): Split off large_icon_service.h and large_icon_service_impl.h.
+// Use then mocks of FaviconService and LargeIconService instead of the real
+// things.
+class IconCacherTestBase : public ::testing::Test {
  protected:
-  IconCacherTest()
-      : site_(base::string16(),  // title, unused
-              GURL("http://url.google/"),
-              GURL("http://url.google/icon.png"),
-              GURL("http://url.google/favicon.ico"),
-              GURL()),  // thumbnail, unused
-        image_fetcher_(new ::testing::StrictMock<MockImageFetcher>),
-        image_decoder_(new ::testing::StrictMock<MockImageDecoder>),
-        favicon_service_(/*favicon_client=*/nullptr, &history_service_),
-        task_runner_(new base::TestSimpleTaskRunner()) {
+  IconCacherTestBase()
+      : favicon_service_(/*favicon_client=*/nullptr, &history_service_) {
     CHECK(history_dir_.CreateUniqueTempDir());
     CHECK(history_service_.Init(
         history::HistoryDatabaseParams(history_dir_.GetPath(), 0, 0)));
   }
 
-  void SetUp() override {
-    if (ui::ResourceBundle::HasSharedInstance()) {
-      ui::ResourceBundle::CleanupSharedInstance();
-    }
-    ON_CALL(mock_resource_delegate_, GetPathForResourcePack(_, _))
-        .WillByDefault(ReturnArg<0>());
-    ON_CALL(mock_resource_delegate_, GetPathForLocalePack(_, _))
-        .WillByDefault(ReturnArg<0>());
-    ui::ResourceBundle::InitSharedInstanceWithLocale(
-        "en-US", &mock_resource_delegate_,
-        ui::ResourceBundle::LOAD_COMMON_RESOURCES);
-  }
-
-  void TearDown() override {
-    if (ui::ResourceBundle::HasSharedInstance()) {
-      ui::ResourceBundle::CleanupSharedInstance();
-    }
-    base::FilePath pak_path;
-#if defined(OS_ANDROID)
-    PathService::Get(ui::DIR_RESOURCE_PAKS_ANDROID, &pak_path);
-#else
-    PathService::Get(base::DIR_MODULE, &pak_path);
-#endif
-
-    base::FilePath ui_test_pak_path;
-    ASSERT_TRUE(PathService::Get(ui::UI_TEST_PAK, &ui_test_pak_path));
-    ui::ResourceBundle::InitSharedInstanceWithPakPath(ui_test_pak_path);
-
-    ui::ResourceBundle::GetSharedInstance().AddDataPackFromPath(
-        pak_path.AppendASCII("components_tests_resources.pak"),
-        ui::SCALE_FACTOR_NONE);
-  }
-
   void PreloadIcon(const GURL& url,
                    const GURL& icon_url,
                    favicon_base::IconType icon_type,
@@ -176,41 +160,76 @@
     return image;
   }
 
-  void WaitForTasksToFinish() { task_runner_->RunUntilIdle(); }
+  void WaitForHistoryThreadTasksToFinish() {
+    base::RunLoop loop;
+    base::MockCallback<base::Closure> done;
+    EXPECT_CALL(done, Run()).WillOnce(Quit(&loop));
+    history_service_.FlushForTest(done.Get());
+    loop.Run();
+  }
+
+  void WaitForMainThreadTasksToFinish() {
+    base::RunLoop loop;
+    loop.RunUntilIdle();
+  }
 
   base::test::ScopedTaskEnvironment scoped_task_environment_;
-  PopularSites::Site site_;
-  std::unique_ptr<MockImageFetcher> image_fetcher_;
-  std::unique_ptr<MockImageDecoder> image_decoder_;
   base::ScopedTempDir history_dir_;
   history::HistoryService history_service_;
   favicon::FaviconServiceImpl favicon_service_;
-  scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+};
+
+class IconCacherTestPopularSites : public IconCacherTestBase {
+ protected:
+  IconCacherTestPopularSites()
+      : site_(base::string16(),  // title, unused
+              GURL("http://url.google/"),
+              GURL("http://url.google/icon.png"),
+              GURL("http://url.google/favicon.ico"),
+              GURL()),  // thumbnail, unused
+        image_fetcher_(new ::testing::StrictMock<MockImageFetcher>),
+        image_decoder_(new ::testing::StrictMock<MockImageDecoder>) {}
+
+  void SetUp() override {
+    if (ui::ResourceBundle::HasSharedInstance()) {
+      ui::ResourceBundle::CleanupSharedInstance();
+    }
+    ON_CALL(mock_resource_delegate_, GetPathForResourcePack(_, _))
+        .WillByDefault(ReturnArg<0>());
+    ON_CALL(mock_resource_delegate_, GetPathForLocalePack(_, _))
+        .WillByDefault(ReturnArg<0>());
+    ui::ResourceBundle::InitSharedInstanceWithLocale(
+        "en-US", &mock_resource_delegate_,
+        ui::ResourceBundle::LOAD_COMMON_RESOURCES);
+  }
+
+  void TearDown() override {
+    if (ui::ResourceBundle::HasSharedInstance()) {
+      ui::ResourceBundle::CleanupSharedInstance();
+    }
+    base::FilePath pak_path;
+#if defined(OS_ANDROID)
+    PathService::Get(ui::DIR_RESOURCE_PAKS_ANDROID, &pak_path);
+#else
+    PathService::Get(base::DIR_MODULE, &pak_path);
+#endif
+
+    base::FilePath ui_test_pak_path;
+    ASSERT_TRUE(PathService::Get(ui::UI_TEST_PAK, &ui_test_pak_path));
+    ui::ResourceBundle::InitSharedInstanceWithPakPath(ui_test_pak_path);
+
+    ui::ResourceBundle::GetSharedInstance().AddDataPackFromPath(
+        pak_path.AppendASCII("components_tests_resources.pak"),
+        ui::SCALE_FACTOR_NONE);
+  }
+
+  PopularSites::Site site_;
+  std::unique_ptr<MockImageFetcher> image_fetcher_;
+  std::unique_ptr<MockImageDecoder> image_decoder_;
   NiceMock<MockResourceDelegate> mock_resource_delegate_;
 };
 
-ACTION(FailFetch) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE,
-      base::Bind(arg2, arg0, gfx::Image(), image_fetcher::RequestMetadata()));
-}
-
-ACTION_P2(DecodeSuccessfully, width, height) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::Bind(arg2, gfx::test::CreateImage(width, height)));
-}
-
-ACTION_P2(PassFetch, width, height) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::Bind(arg2, arg0, gfx::test::CreateImage(width, height),
-                            image_fetcher::RequestMetadata()));
-}
-
-ACTION_P(Quit, run_loop) {
-  run_loop->Quit();
-}
-
-TEST_F(IconCacherTest, LargeCached) {
+TEST_F(IconCacherTestPopularSites, LargeCached) {
   base::MockCallback<base::Closure> done;
   EXPECT_CALL(done, Run()).Times(0);
   base::RunLoop loop;
@@ -223,14 +242,14 @@
   }
   PreloadIcon(site_.url, site_.large_icon_url, favicon_base::TOUCH_ICON, 128,
               128);
-  IconCacherImpl cacher(&favicon_service_, std::move(image_fetcher_));
-  cacher.StartFetch(site_, done.Get(), done.Get());
-  WaitForTasksToFinish();
+  IconCacherImpl cacher(&favicon_service_, nullptr, std::move(image_fetcher_));
+  cacher.StartFetchPopularSites(site_, done.Get(), done.Get());
+  WaitForMainThreadTasksToFinish();
   EXPECT_FALSE(IconIsCachedFor(site_.url, favicon_base::FAVICON));
   EXPECT_TRUE(IconIsCachedFor(site_.url, favicon_base::TOUCH_ICON));
 }
 
-TEST_F(IconCacherTest, LargeNotCachedAndFetchSucceeded) {
+TEST_F(IconCacherTestPopularSites, LargeNotCachedAndFetchSucceeded) {
   base::MockCallback<base::Closure> done;
   base::RunLoop loop;
   {
@@ -245,14 +264,14 @@
     EXPECT_CALL(done, Run()).WillOnce(Quit(&loop));
   }
 
-  IconCacherImpl cacher(&favicon_service_, std::move(image_fetcher_));
-  cacher.StartFetch(site_, done.Get(), done.Get());
+  IconCacherImpl cacher(&favicon_service_, nullptr, std::move(image_fetcher_));
+  cacher.StartFetchPopularSites(site_, done.Get(), done.Get());
   loop.Run();
   EXPECT_FALSE(IconIsCachedFor(site_.url, favicon_base::FAVICON));
   EXPECT_TRUE(IconIsCachedFor(site_.url, favicon_base::TOUCH_ICON));
 }
 
-TEST_F(IconCacherTest, SmallNotCachedAndFetchSucceeded) {
+TEST_F(IconCacherTestPopularSites, SmallNotCachedAndFetchSucceeded) {
   site_.large_icon_url = GURL();
 
   base::MockCallback<base::Closure> done;
@@ -269,14 +288,14 @@
     EXPECT_CALL(done, Run()).WillOnce(Quit(&loop));
   }
 
-  IconCacherImpl cacher(&favicon_service_, std::move(image_fetcher_));
-  cacher.StartFetch(site_, done.Get(), done.Get());
+  IconCacherImpl cacher(&favicon_service_, nullptr, std::move(image_fetcher_));
+  cacher.StartFetchPopularSites(site_, done.Get(), done.Get());
   loop.Run();
   EXPECT_TRUE(IconIsCachedFor(site_.url, favicon_base::FAVICON));
   EXPECT_FALSE(IconIsCachedFor(site_.url, favicon_base::TOUCH_ICON));
 }
 
-TEST_F(IconCacherTest, LargeNotCachedAndFetchFailed) {
+TEST_F(IconCacherTestPopularSites, LargeNotCachedAndFetchFailed) {
   base::MockCallback<base::Closure> done;
   EXPECT_CALL(done, Run()).Times(0);
   {
@@ -290,24 +309,28 @@
         .WillOnce(FailFetch());
   }
 
-  IconCacherImpl cacher(&favicon_service_, std::move(image_fetcher_));
-  cacher.StartFetch(site_, done.Get(), done.Get());
-  WaitForTasksToFinish();
+  IconCacherImpl cacher(&favicon_service_, nullptr, std::move(image_fetcher_));
+  cacher.StartFetchPopularSites(site_, done.Get(), done.Get());
+  WaitForMainThreadTasksToFinish();
   EXPECT_FALSE(IconIsCachedFor(site_.url, favicon_base::FAVICON));
   EXPECT_FALSE(IconIsCachedFor(site_.url, favicon_base::TOUCH_ICON));
 }
 
-TEST_F(IconCacherTest, HandlesEmptyCallbacksNicely) {
+TEST_F(IconCacherTestPopularSites, HandlesEmptyCallbacksNicely) {
   EXPECT_CALL(*image_fetcher_, SetDataUseServiceName(_));
   EXPECT_CALL(*image_fetcher_, SetDesiredImageFrameSize(_));
-  ON_CALL(*image_fetcher_, StartOrQueueNetworkRequest(_, _, _))
-      .WillByDefault(PassFetch(128, 128));
-  IconCacherImpl cacher(&favicon_service_, std::move(image_fetcher_));
-  cacher.StartFetch(site_, base::Closure(), base::Closure());
-  WaitForTasksToFinish();
+  EXPECT_CALL(*image_fetcher_, StartOrQueueNetworkRequest(_, _, _))
+      .WillOnce(PassFetch(128, 128));
+  IconCacherImpl cacher(&favicon_service_, nullptr, std::move(image_fetcher_));
+  cacher.StartFetchPopularSites(site_, base::Closure(), base::Closure());
+  WaitForHistoryThreadTasksToFinish();  // Writing the icon into the DB.
+  WaitForMainThreadTasksToFinish();     // Finishing tasks after the DB write.
+  // Even though the callbacks are not called, the icon gets written out.
+  EXPECT_FALSE(IconIsCachedFor(site_.url, favicon_base::FAVICON));
+  EXPECT_TRUE(IconIsCachedFor(site_.url, favicon_base::TOUCH_ICON));
 }
 
-TEST_F(IconCacherTest, ProvidesDefaultIconAndSucceedsWithFetching) {
+TEST_F(IconCacherTestPopularSites, ProvidesDefaultIconAndSucceedsWithFetching) {
   // The returned data string is not used by the mocked decoder.
   ON_CALL(mock_resource_delegate_, GetRawDataResource(12345, _, _))
       .WillByDefault(Return(""));
@@ -338,10 +361,10 @@
     EXPECT_CALL(icon_available, Run()).WillOnce(Quit(&fetch_loop));
   }
 
-  IconCacherImpl cacher(&favicon_service_, std::move(image_fetcher_));
+  IconCacherImpl cacher(&favicon_service_, nullptr, std::move(image_fetcher_));
   site_.default_icon_resource = 12345;
-  cacher.StartFetch(site_, icon_available.Get(),
-                    preliminary_icon_available.Get());
+  cacher.StartFetchPopularSites(site_, icon_available.Get(),
+                                preliminary_icon_available.Get());
 
   default_loop.Run();  // Wait for the default image.
   EXPECT_THAT(GetCachedIconFor(site_.url, favicon_base::TOUCH_ICON).Size(),
@@ -353,5 +376,141 @@
               Eq(gfx::Size(128, 128)));  // Compares dimensions, not objects.
 }
 
+class IconCacherTestMostLikely : public IconCacherTestBase {
+ protected:
+  IconCacherTestMostLikely()
+      : large_icon_service_background_task_runner_(
+            new base::TestSimpleTaskRunner()),
+        fetcher_for_large_icon_service_(
+            base::MakeUnique<::testing::StrictMock<MockImageFetcher>>()),
+        fetcher_for_icon_cacher_(
+            base::MakeUnique<::testing::StrictMock<MockImageFetcher>>()) {
+    // Expect uninteresting calls here, |fetcher_for_icon_cacher_| is not
+    // related to these tests. Keep it strict to make sure we do not use it in
+    // any other way.
+    EXPECT_CALL(*fetcher_for_icon_cacher_,
+                SetDataUseServiceName(
+                    data_use_measurement::DataUseUserData::NTP_TILES));
+    EXPECT_CALL(*fetcher_for_icon_cacher_,
+                SetDesiredImageFrameSize(gfx::Size(128, 128)));
+  }
+
+  scoped_refptr<base::TestSimpleTaskRunner>
+      large_icon_service_background_task_runner_;
+  std::unique_ptr<MockImageFetcher> fetcher_for_large_icon_service_;
+  std::unique_ptr<MockImageFetcher> fetcher_for_icon_cacher_;
+};
+
+TEST_F(IconCacherTestMostLikely, Cached) {
+  GURL page_url("http://www.site.com");
+  GURL icon_url("http://www.site.com/favicon.png");
+  PreloadIcon(page_url, icon_url, favicon_base::TOUCH_ICON, 128, 128);
+
+  favicon::LargeIconService large_icon_service(
+      &favicon_service_, large_icon_service_background_task_runner_,
+      std::move(fetcher_for_large_icon_service_));
+  IconCacherImpl cacher(&favicon_service_, &large_icon_service,
+                        std::move(fetcher_for_icon_cacher_));
+
+  base::MockCallback<base::Closure> done;
+  EXPECT_CALL(done, Run()).Times(0);
+  cacher.StartFetchMostLikely(page_url, done.Get());
+  WaitForMainThreadTasksToFinish();
+
+  EXPECT_FALSE(IconIsCachedFor(page_url, favicon_base::FAVICON));
+  EXPECT_TRUE(IconIsCachedFor(page_url, favicon_base::TOUCH_ICON));
+}
+
+TEST_F(IconCacherTestMostLikely, NotCachedAndFetchSucceeded) {
+  GURL page_url("http://www.site.com");
+
+  base::MockCallback<base::Closure> done;
+  base::RunLoop loop;
+  {
+    InSequence s;
+    EXPECT_CALL(*fetcher_for_large_icon_service_,
+                SetDataUseServiceName(
+                    data_use_measurement::DataUseUserData::LARGE_ICON_SERVICE));
+    EXPECT_CALL(*fetcher_for_large_icon_service_,
+                StartOrQueueNetworkRequest(_, _, _))
+        .WillOnce(PassFetch(128, 128));
+    EXPECT_CALL(done, Run()).WillOnce(Quit(&loop));
+  }
+
+  favicon::LargeIconService large_icon_service(
+      &favicon_service_, large_icon_service_background_task_runner_,
+      std::move(fetcher_for_large_icon_service_));
+  IconCacherImpl cacher(&favicon_service_, &large_icon_service,
+                        std::move(fetcher_for_icon_cacher_));
+
+  cacher.StartFetchMostLikely(page_url, done.Get());
+  // Both these task runners need to be flushed in order to get |done| called by
+  // running the main loop.
+  WaitForHistoryThreadTasksToFinish();
+  large_icon_service_background_task_runner_->RunUntilIdle();
+
+  loop.Run();
+  EXPECT_FALSE(IconIsCachedFor(page_url, favicon_base::FAVICON));
+  EXPECT_TRUE(IconIsCachedFor(page_url, favicon_base::TOUCH_ICON));
+}
+
+TEST_F(IconCacherTestMostLikely, NotCachedAndFetchFailed) {
+  GURL page_url("http://www.site.com");
+
+  base::MockCallback<base::Closure> done;
+  {
+    InSequence s;
+    EXPECT_CALL(*fetcher_for_large_icon_service_,
+                SetDataUseServiceName(
+                    data_use_measurement::DataUseUserData::LARGE_ICON_SERVICE));
+    EXPECT_CALL(*fetcher_for_large_icon_service_,
+                StartOrQueueNetworkRequest(_, _, _))
+        .WillOnce(FailFetch());
+    EXPECT_CALL(done, Run()).Times(0);
+  }
+
+  favicon::LargeIconService large_icon_service(
+      &favicon_service_, large_icon_service_background_task_runner_,
+      std::move(fetcher_for_large_icon_service_));
+  IconCacherImpl cacher(&favicon_service_, &large_icon_service,
+                        std::move(fetcher_for_icon_cacher_));
+
+  cacher.StartFetchMostLikely(page_url, done.Get());
+  // Both these task runners need to be flushed before flushing the main thread
+  // queue in order to finish the work.
+  WaitForHistoryThreadTasksToFinish();
+  large_icon_service_background_task_runner_->RunUntilIdle();
+  WaitForMainThreadTasksToFinish();
+
+  EXPECT_FALSE(IconIsCachedFor(page_url, favicon_base::FAVICON));
+  EXPECT_FALSE(IconIsCachedFor(page_url, favicon_base::TOUCH_ICON));
+}
+
+TEST_F(IconCacherTestMostLikely, HandlesEmptyCallbacksNicely) {
+  GURL page_url("http://www.site.com");
+
+  EXPECT_CALL(*fetcher_for_large_icon_service_, SetDataUseServiceName(_));
+  EXPECT_CALL(*fetcher_for_large_icon_service_,
+              StartOrQueueNetworkRequest(_, _, _))
+      .WillOnce(PassFetch(128, 128));
+
+  favicon::LargeIconService large_icon_service(
+      &favicon_service_, large_icon_service_background_task_runner_,
+      std::move(fetcher_for_large_icon_service_));
+  IconCacherImpl cacher(&favicon_service_, &large_icon_service,
+                        std::move(fetcher_for_icon_cacher_));
+
+  cacher.StartFetchMostLikely(page_url, base::Closure());
+  // Both these task runners need to be flushed before flushing the main thread
+  // queue in order to finish the work.
+  WaitForHistoryThreadTasksToFinish();
+  large_icon_service_background_task_runner_->RunUntilIdle();
+  WaitForMainThreadTasksToFinish();
+
+  // Even though the callbacks are not called, the icon gets written out.
+  EXPECT_FALSE(IconIsCachedFor(page_url, favicon_base::FAVICON));
+  EXPECT_TRUE(IconIsCachedFor(page_url, favicon_base::TOUCH_ICON));
+}
+
 }  // namespace
 }  // namespace ntp_tiles
diff --git a/components/ntp_tiles/most_visited_sites.cc b/components/ntp_tiles/most_visited_sites.cc
index b09aa4c8..a492c92 100644
--- a/components/ntp_tiles/most_visited_sites.cc
+++ b/components/ntp_tiles/most_visited_sites.cc
@@ -276,6 +276,11 @@
     tile.whitelist_icon_path = GetWhitelistLargeIconPath(url);
     tile.thumbnail_url = GURL(suggestion_pb.thumbnail());
     tile.favicon_url = GURL(suggestion_pb.favicon_url());
+    if (AreNtpMostLikelyFaviconsFromServerEnabled()) {
+      icon_cacher_->StartFetchMostLikely(
+          url, base::Bind(&MostVisitedSites::OnIconMadeAvailable,
+                          base::Unretained(this), url));
+    }
 
     tiles.push_back(std::move(tile));
   }
@@ -368,7 +373,8 @@
       base::Closure icon_available =
           base::Bind(&MostVisitedSites::OnIconMadeAvailable,
                      base::Unretained(this), popular_site.url);
-      icon_cacher_->StartFetch(popular_site, icon_available, icon_available);
+      icon_cacher_->StartFetchPopularSites(popular_site, icon_available,
+                                           icon_available);
       if (popular_sites_tiles.size() >= num_popular_sites_tiles)
         break;
     }
@@ -430,7 +436,8 @@
 
   for (const PopularSites::Site& popular_site : popular_sites_->sites()) {
     // Ignore callback; these icons will be seen on the *next* NTP.
-    icon_cacher_->StartFetch(popular_site, base::Closure(), base::Closure());
+    icon_cacher_->StartFetchPopularSites(popular_site, base::Closure(),
+                                         base::Closure());
   }
 }
 
diff --git a/components/ntp_tiles/most_visited_sites_unittest.cc b/components/ntp_tiles/most_visited_sites_unittest.cc
index 650b10e..9b74264 100644
--- a/components/ntp_tiles/most_visited_sites_unittest.cc
+++ b/components/ntp_tiles/most_visited_sites_unittest.cc
@@ -20,10 +20,12 @@
 #include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/cancelable_task_tracker.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/sequenced_worker_pool_owner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "components/history/core/browser/top_sites.h"
 #include "components/history/core/browser/top_sites_observer.h"
+#include "components/ntp_tiles/constants.h"
 #include "components/ntp_tiles/icon_cacher.h"
 #include "components/ntp_tiles/json_unsafe_parser.h"
 #include "components/ntp_tiles/popular_sites_impl.h"
@@ -194,10 +196,12 @@
 
 class MockIconCacher : public IconCacher {
  public:
-  MOCK_METHOD3(StartFetch,
+  MOCK_METHOD3(StartFetchPopularSites,
                void(PopularSites::Site site,
                     const base::Closure& icon_available,
                     const base::Closure& preliminary_icon_available));
+  MOCK_METHOD2(StartFetchMostLikely,
+               void(const GURL& page_url, const base::Closure& icon_available));
 };
 
 class PopularSitesFactoryForTest {
@@ -287,9 +291,14 @@
           switches::kDisableNTPPopularSites);
     }
 
+    // Disable in most tests, this is overriden in a specific test.
+    feature_list_.InitAndDisableFeature(
+        kNtpMostLikelyFaviconsFromServerFeature);
+
     // We use StrictMock to make sure the object is not used unless Popular
     // Sites is enabled.
     auto icon_cacher = base::MakeUnique<StrictMock<MockIconCacher>>();
+    icon_cacher_ = icon_cacher.get();
 
     if (IsPopularSitesEnabledViaVariations()) {
       // Populate Popular Sites' internal cache by mimicking a past usage of
@@ -313,7 +322,8 @@
           .WillRepeatedly(Return(false));
       // Mock icon cacher never replies, and we also don't verify whether the
       // code uses it correctly.
-      EXPECT_CALL(*icon_cacher, StartFetch(_, _, _)).Times(AtLeast(0));
+      EXPECT_CALL(*icon_cacher, StartFetchPopularSites(_, _, _))
+          .Times(AtLeast(0));
     }
 
     most_visited_sites_ = base::MakeUnique<MostVisitedSites>(
@@ -349,6 +359,8 @@
   StrictMock<MockSuggestionsService> mock_suggestions_service_;
   StrictMock<MockMostVisitedSitesObserver> mock_observer_;
   std::unique_ptr<MostVisitedSites> most_visited_sites_;
+  base::test::ScopedFeatureList feature_list_;
+  MockIconCacher* icon_cacher_;
 };
 
 TEST_P(MostVisitedSitesTest, ShouldStartNoCallInConstructor) {
@@ -540,6 +552,18 @@
   base::RunLoop().RunUntilIdle();
 }
 
+TEST_P(MostVisitedSitesWithCacheHitTest, ShouldFetchFaviconsIfEnabled) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(kNtpMostLikelyFaviconsFromServerFeature);
+
+  EXPECT_CALL(mock_observer_, OnMostVisitedURLsAvailable(_));
+  EXPECT_CALL(*icon_cacher_, StartFetchMostLikely(GURL("http://site4/"), _));
+
+  suggestions_service_callbacks_.Notify(
+      MakeProfile({MakeSuggestion("Site 4", "http://site4/")}));
+  base::RunLoop().RunUntilIdle();
+}
+
 INSTANTIATE_TEST_CASE_P(MostVisitedSitesWithCacheHitTest,
                         MostVisitedSitesWithCacheHitTest,
                         ::testing::Bool());
diff --git a/components/signin/core/browser/gaia_cookie_manager_service.cc b/components/signin/core/browser/gaia_cookie_manager_service.cc
index a3aa65f..58556ca 100644
--- a/components/signin/core/browser/gaia_cookie_manager_service.cc
+++ b/components/signin/core/browser/gaia_cookie_manager_service.cc
@@ -403,8 +403,8 @@
 void GaiaCookieManagerService::ForceOnCookieChangedProcessing() {
   GURL google_url = GaiaUrls::GetInstance()->google_url();
   std::unique_ptr<net::CanonicalCookie> cookie(net::CanonicalCookie::Create(
-      google_url, kGaiaCookieName, std::string(), "." + google_url.host(),
-      std::string(), base::Time(), base::Time(), false, false,
+      kGaiaCookieName, std::string(), "." + google_url.host(), "/",
+      base::Time(), base::Time(), base::Time(), false, false,
       net::CookieSameSite::DEFAULT_MODE, net::COOKIE_PRIORITY_DEFAULT));
   OnCookieChanged(*cookie, net::CookieStore::ChangeCause::UNKNOWN_DELETION);
 }
diff --git a/components/sync/protocol/proto_visitors.h b/components/sync/protocol/proto_visitors.h
index 9f89e8b..31b343b39 100644
--- a/components/sync/protocol/proto_visitors.h
+++ b/components/sync/protocol/proto_visitors.h
@@ -850,6 +850,7 @@
 VISIT_PROTO_FIELDS(const sync_pb::UserEventSpecifics& proto) {
   VISIT(event_time_usec);
   VISIT(navigation_id);
+  VISIT(session_id);
 }
 
 VISIT_PROTO_FIELDS(const sync_pb::WalletMaskedCreditCard& proto) {
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index fc63bcd..c108d08 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -84,6 +84,7 @@
     "//device/power_save_blocker",
     "//device/screen_orientation/public/interfaces",
     "//device/vr",
+    "//device/vr:mojo_bindings",
     "//device/vr/features",
     "//device/wake_lock/public/interfaces",
     "//google_apis",
diff --git a/content/browser/accessibility/browser_accessibility.cc b/content/browser/accessibility/browser_accessibility.cc
index 7d50b00..4e96eb2 100644
--- a/content/browser/accessibility/browser_accessibility.cc
+++ b/content/browser/accessibility/browser_accessibility.cc
@@ -1028,7 +1028,7 @@
          ui::AX_NAME_FROM_ATTRIBUTE_EXPLICITLY_EMPTY;
 }
 
-std::string BrowserAccessibility::ComputeAccessibleNameFromDescendants() {
+std::string BrowserAccessibility::ComputeAccessibleNameFromDescendants() const {
   std::string name;
   for (size_t i = 0; i < InternalChildCount(); ++i) {
     BrowserAccessibility* child = InternalGetChild(i);
diff --git a/content/browser/accessibility/browser_accessibility.h b/content/browser/accessibility/browser_accessibility.h
index 44ec13cd..a76e56a6 100644
--- a/content/browser/accessibility/browser_accessibility.h
+++ b/content/browser/accessibility/browser_accessibility.h
@@ -383,7 +383,7 @@
 
   // If an object is focusable but has no accessible name, use this
   // to compute a name from its descendants.
-  std::string ComputeAccessibleNameFromDescendants();
+  std::string ComputeAccessibleNameFromDescendants() const;
 
   // Creates a text position rooted at this object.
   AXPlatformPosition::AXPositionInstance CreatePositionAt(
diff --git a/content/browser/accessibility/browser_accessibility_android.cc b/content/browser/accessibility/browser_accessibility_android.cc
index 8028085..ac6841e 100644
--- a/content/browser/accessibility/browser_accessibility_android.cc
+++ b/content/browser/accessibility/browser_accessibility_android.cc
@@ -77,9 +77,16 @@
   // Optionally replace entered password text with bullet characters
   // based on a user preference.
   if (IsPassword()) {
-    bool should_expose = static_cast<BrowserAccessibilityManagerAndroid*>(
-        manager())->ShouldExposePasswordText();
-    if (!should_expose) {
+    auto* manager =
+        static_cast<BrowserAccessibilityManagerAndroid*>(this->manager());
+    if (manager->ShouldRespectDisplayedPasswordText()) {
+      // In the Chrome accessibility tree, the value of a password node is
+      // unobscured. However, if ShouldRespectDisplayedPasswordText() returns
+      // true we should try to expose whatever's actually visually displayed,
+      // whether that's the actual password or dots or whatever. To do this
+      // we rely on the password field's shadow dom.
+      value = base::UTF8ToUTF16(ComputeAccessibleNameFromDescendants());
+    } else if (!manager->ShouldExposePasswordText()) {
       value = base::string16(value.size(), ui::kSecurePasswordBullet);
     }
   }
diff --git a/content/browser/accessibility/browser_accessibility_manager_android.cc b/content/browser/accessibility/browser_accessibility_manager_android.cc
index b279640..2fe938ba 100644
--- a/content/browser/accessibility/browser_accessibility_manager_android.cc
+++ b/content/browser/accessibility/browser_accessibility_manager_android.cc
@@ -211,6 +211,16 @@
                .obj());
 }
 
+bool BrowserAccessibilityManagerAndroid::ShouldRespectDisplayedPasswordText() {
+  JNIEnv* env = AttachCurrentThread();
+  ScopedJavaLocalRef<jobject> obj = GetJavaRefFromRootManager();
+  if (obj.is_null())
+    return false;
+
+  return Java_BrowserAccessibilityManager_shouldRespectDisplayedPasswordText(
+      env, obj);
+}
+
 bool BrowserAccessibilityManagerAndroid::ShouldExposePasswordText() {
   JNIEnv* env = AttachCurrentThread();
   ScopedJavaLocalRef<jobject> obj = GetJavaRefFromRootManager();
@@ -253,7 +263,20 @@
   // Sometimes we get events on nodes in our internal accessibility tree
   // that aren't exposed on Android. Update |node| to point to the highest
   // ancestor that's a leaf node.
+  BrowserAccessibility* original_node = node;
   node = node->GetClosestPlatformObject();
+  BrowserAccessibilityAndroid* android_node =
+      static_cast<BrowserAccessibilityAndroid*>(node);
+
+  // If the closest platform object is a password field, the event we're
+  // getting is doing something in the shadow dom, for example replacing a
+  // character with a dot after a short pause. On Android we don't want to
+  // fire an event for those changes, but we do want to make sure our internal
+  // state is correct, so we call OnDataChanged() and then return.
+  if (android_node->IsPassword() && original_node != node) {
+    android_node->OnDataChanged();
+    return;
+  }
 
   // Always send AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED to notify
   // the Android system that the accessibility hierarchy rooted at this
@@ -267,8 +290,6 @@
     return;
   }
 
-  BrowserAccessibilityAndroid* android_node =
-      static_cast<BrowserAccessibilityAndroid*>(node);
   switch (event_type) {
     case ui::AX_EVENT_LOAD_COMPLETE:
       Java_BrowserAccessibilityManager_handlePageLoaded(
@@ -499,7 +520,9 @@
 
   Java_BrowserAccessibilityManager_setAccessibilityNodeInfoKitKatAttributes(
       env, obj, info, is_root, node->IsEditableText(),
-      base::android::ConvertUTF16ToJavaString(env, node->GetRoleDescription()));
+      base::android::ConvertUTF16ToJavaString(env, node->GetRoleDescription()),
+      node->GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START),
+      node->GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END));
 
   Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLollipopAttributes(
       env, obj, info,
diff --git a/content/browser/accessibility/browser_accessibility_manager_android.h b/content/browser/accessibility/browser_accessibility_manager_android.h
index 5d15faf2..a9b56c1 100644
--- a/content/browser/accessibility/browser_accessibility_manager_android.h
+++ b/content/browser/accessibility/browser_accessibility_manager_android.h
@@ -74,6 +74,7 @@
   }
   bool prune_tree_for_screen_reader() { return prune_tree_for_screen_reader_; }
 
+  bool ShouldRespectDisplayedPasswordText();
   bool ShouldExposePasswordText();
 
   // BrowserAccessibilityManager overrides.
diff --git a/content/browser/browsing_data/browsing_data_remover_impl_unittest.cc b/content/browser/browsing_data/browsing_data_remover_impl_unittest.cc
index 762f38f8..d147fef 100644
--- a/content/browser/browsing_data/browsing_data_remover_impl_unittest.cc
+++ b/content/browser/browsing_data/browsing_data_remover_impl_unittest.cc
@@ -118,8 +118,8 @@
 
 net::CanonicalCookie CreateCookieWithHost(const GURL& source) {
   std::unique_ptr<net::CanonicalCookie> cookie(net::CanonicalCookie::Create(
-      source, "A", "1", std::string(), "/", base::Time::Now(),
-      base::Time::Now(), false, false, net::CookieSameSite::DEFAULT_MODE,
+      "A", "1", source.host(), "/", base::Time::Now(), base::Time::Now(),
+      base::Time(), false, false, net::CookieSameSite::DEFAULT_MODE,
       net::COOKIE_PRIORITY_MEDIUM));
   EXPECT_TRUE(cookie);
   return *cookie;
diff --git a/content/browser/net/quota_policy_cookie_store_unittest.cc b/content/browser/net/quota_policy_cookie_store_unittest.cc
index 02c2f9a..eab1eec 100644
--- a/content/browser/net/quota_policy_cookie_store_unittest.cc
+++ b/content/browser/net/quota_policy_cookie_store_unittest.cc
@@ -91,15 +91,15 @@
   }
 
   // Adds a persistent cookie to store_.
-  void AddCookie(const GURL& url,
-                 const std::string& name,
+  void AddCookie(const std::string& name,
                  const std::string& value,
                  const std::string& domain,
                  const std::string& path,
                  const base::Time& creation) {
     store_->AddCookie(*net::CanonicalCookie::Create(
-        url, name, value, domain, path, creation, creation, false, false,
-        net::CookieSameSite::DEFAULT_MODE, net::COOKIE_PRIORITY_DEFAULT));
+        name, value, domain, path, creation, creation, base::Time(), false,
+        false, net::CookieSameSite::DEFAULT_MODE,
+        net::COOKIE_PRIORITY_DEFAULT));
   }
 
   void DestroyStore() {
@@ -136,9 +136,9 @@
   ASSERT_EQ(0U, cookies.size());
 
   base::Time t = base::Time::Now();
-  AddCookie(GURL("http://foo.com"), "A", "B", std::string(), "/", t);
+  AddCookie("A", "B", "foo.com", "/", t);
   t += base::TimeDelta::FromInternalValue(10);
-  AddCookie(GURL("http://persistent.com"), "A", "B", std::string(), "/", t);
+  AddCookie("A", "B", "persistent.com", "/", t);
 
   // Replace the store, which forces the current store to flush data to
   // disk. Then, after reloading the store, confirm that the data was flushed by
@@ -180,11 +180,11 @@
   ASSERT_EQ(0U, cookies.size());
 
   base::Time t = base::Time::Now();
-  AddCookie(GURL("http://foo.com"), "A", "B", std::string(), "/", t);
+  AddCookie("A", "B", "foo.com", "/", t);
   t += base::TimeDelta::FromInternalValue(10);
-  AddCookie(GURL("http://persistent.com"), "A", "B", std::string(), "/", t);
+  AddCookie("A", "B", "persistent.com", "/", t);
   t += base::TimeDelta::FromInternalValue(10);
-  AddCookie(GURL("http://nonpersistent.com"), "A", "B", std::string(), "/", t);
+  AddCookie("A", "B", "nonpersistent.com", "/", t);
 
   // Replace the store, which forces the current store to flush data to
   // disk. Then, after reloading the store, confirm that the data was flushed by
@@ -203,8 +203,7 @@
   EXPECT_EQ(3U, cookies.size());
 
   t += base::TimeDelta::FromInternalValue(10);
-  AddCookie(GURL("http://nonpersistent.com"), "A", "B", std::string(),
-            "/second", t);
+  AddCookie("A", "B", "nonpersistent.com", "/second", t);
 
   // Now close the store, and "nonpersistent.com" should be deleted according to
   // policy.
@@ -225,7 +224,7 @@
   ASSERT_EQ(0U, cookies.size());
 
   base::Time t = base::Time::Now();
-  AddCookie(GURL("http://foo.com"), "A", "B", std::string(), "/", t);
+  AddCookie("A", "B", "foo.com", "/", t);
 
   // Recreate |store_| with a storage policy that makes "nonpersistent.com"
   // session only, but then instruct the store to forcibly keep all cookies.
@@ -241,9 +240,9 @@
   EXPECT_EQ(1U, cookies.size());
 
   t += base::TimeDelta::FromInternalValue(10);
-  AddCookie(GURL("http://persistent.com"), "A", "B", std::string(), "/", t);
+  AddCookie("A", "B", "persistent.com", "/", t);
   t += base::TimeDelta::FromInternalValue(10);
-  AddCookie(GURL("http://nonpersistent.com"), "A", "B", std::string(), "/", t);
+  AddCookie("A", "B", "nonpersistent.com", "/", t);
 
   // Now close the store, but the "nonpersistent.com" cookie should not be
   // deleted.
@@ -270,7 +269,7 @@
   ASSERT_EQ(0U, cookies.size());
 
   base::Time t = base::Time::Now();
-  AddCookie(GURL("http://nonpersistent.com"), "A", "B", std::string(), "/", t);
+  AddCookie("A", "B", "nonpersistent.com", "/", t);
 
   // Replace the store, which forces the current store to flush data to
   // disk. Then, after reloading the store, confirm that the data was flushed by
diff --git a/content/common/BUILD.gn b/content/common/BUILD.gn
index af19509..963405f 100644
--- a/content/common/BUILD.gn
+++ b/content/common/BUILD.gn
@@ -644,9 +644,7 @@
     "//url/mojo:url_mojom_origin",
   ]
 
-  overridden_deps = [ "//ipc:mojom" ]
-  component_deps = [ "//ipc" ]
-
+  component_output_prefix = "content_common_mojo_bindings"
   export_class_attribute = "CONTENT_EXPORT"
   export_define = "CONTENT_IMPLEMENTATION=1"
   export_header = "content/common/content_export.h"
diff --git a/content/public/android/java/src/org/chromium/content/browser/accessibility/BrowserAccessibilityManager.java b/content/public/android/java/src/org/chromium/content/browser/accessibility/BrowserAccessibilityManager.java
index e2a8622..7383829 100644
--- a/content/public/android/java/src/org/chromium/content/browser/accessibility/BrowserAccessibilityManager.java
+++ b/content/public/android/java/src/org/chromium/content/browser/accessibility/BrowserAccessibilityManager.java
@@ -5,6 +5,7 @@
 package org.chromium.content.browser.accessibility;
 
 import android.annotation.SuppressLint;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.Build;
@@ -21,6 +22,7 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeProvider;
 
+import org.chromium.base.BuildInfo;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.content.browser.ContentViewCore;
@@ -1030,7 +1032,8 @@
 
     @CalledByNative
     protected void setAccessibilityNodeInfoKitKatAttributes(AccessibilityNodeInfo node,
-            boolean isRoot, boolean isEditableText, String roleDescription) {
+            boolean isRoot, boolean isEditableText, String roleDescription, int selectionStartIndex,
+            int selectionEndIndex) {
         // Requires KitKat or higher.
     }
 
@@ -1176,11 +1179,35 @@
         bundle.putFloat("AccessibilityNodeInfo.RangeInfo.current", current);
     }
 
+    /**
+     * On Android O and higher, we should respect whatever is displayed
+     * in a password box and report that via accessibility APIs, whether
+     * that's the unobscured password, or all dots.
+     *
+     * Previous to O, shouldExposePasswordText() returns a system setting
+     * that determines whether we should return the unobscured password or all
+     * dots, independent of what was displayed visually.
+     */
+    @CalledByNative
+    boolean shouldRespectDisplayedPasswordText() {
+        return BuildInfo.isAtLeastO();
+    }
+
+    /**
+     * Only relevant prior to Android O, see shouldRespectDisplayedPasswordText.
+     */
     @CalledByNative
     boolean shouldExposePasswordText() {
+        ContentResolver contentResolver = mContentViewCore.getContext().getContentResolver();
+
+        if (BuildInfo.isAtLeastO()) {
+            return (Settings.System.getInt(contentResolver, Settings.System.TEXT_SHOW_PASSWORD, 1)
+                    == 1);
+        }
+
         return (Settings.Secure.getInt(
-                        mContentViewCore.getContext().getContentResolver(),
-                        Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) == 1);
+                        contentResolver, Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0)
+                == 1);
     }
 
     private native void nativeOnAutofillPopupDisplayed(
diff --git a/content/public/android/java/src/org/chromium/content/browser/accessibility/KitKatBrowserAccessibilityManager.java b/content/public/android/java/src/org/chromium/content/browser/accessibility/KitKatBrowserAccessibilityManager.java
index 0fc2bbbb..58ea5d8 100644
--- a/content/public/android/java/src/org/chromium/content/browser/accessibility/KitKatBrowserAccessibilityManager.java
+++ b/content/public/android/java/src/org/chromium/content/browser/accessibility/KitKatBrowserAccessibilityManager.java
@@ -29,7 +29,8 @@
 
     @Override
     protected void setAccessibilityNodeInfoKitKatAttributes(AccessibilityNodeInfo node,
-            boolean isRoot, boolean isEditableText, String roleDescription) {
+            boolean isRoot, boolean isEditableText, String roleDescription, int selectionStartIndex,
+            int selectionEndIndex) {
         Bundle bundle = node.getExtras();
         bundle.putCharSequence("AccessibilityNodeInfo.roleDescription", roleDescription);
         if (isRoot) {
@@ -38,6 +39,7 @@
         }
         if (isEditableText) {
             node.setEditable(true);
+            node.setTextSelection(selectionStartIndex, selectionEndIndex);
         }
     }
 }
diff --git a/content/renderer/gpu/gpu_benchmarking_extension.cc b/content/renderer/gpu/gpu_benchmarking_extension.cc
index d5836a8..0088be9 100644
--- a/content/renderer/gpu/gpu_benchmarking_extension.cc
+++ b/content/renderer/gpu/gpu_benchmarking_extension.cc
@@ -64,8 +64,6 @@
 #if defined(OS_WIN) && !defined(NDEBUG)
 #include <XpsObjectModel.h>
 #include "base/win/scoped_comptr.h"
-#include "skia/ext/skia_encode_image.h"
-#include "ui/gfx/codec/skia_image_encoder_adapter.h"
 #endif
 
 using blink::WebCanvas;
@@ -525,9 +523,6 @@
 // `--enable-gpu-benchmarking` for this to work.
 #if defined(OS_WIN) && !defined(NDEBUG)
 static sk_sp<SkDocument> MakeXPSDocument(SkWStream* s) {
-  // Hand Skia an image encoder, needed for XPS backend.
-  skia::SetImageEncoder(&gfx::EncodeSkiaImage);
-
   // I am not sure why this hasn't been initialized yet.
   (void)CoInitializeEx(nullptr,
                        COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
diff --git a/device/generic_sensor/public/interfaces/BUILD.gn b/device/generic_sensor/public/interfaces/BUILD.gn
index 4d9f3e03..24aabe84 100644
--- a/device/generic_sensor/public/interfaces/BUILD.gn
+++ b/device/generic_sensor/public/interfaces/BUILD.gn
@@ -7,6 +7,7 @@
 mojom("interfaces") {
   visibility = [ "//device/generic_sensor/public/cpp" ]
   visibility_blink = [ "//third_party/WebKit/Source/*" ]
+  component_output_prefix = "generic_sensor_public_interfaces"
   export_class_attribute = "DEVICE_GENERIC_SENSOR_PUBLIC_EXPORT"
   export_define = "DEVICE_GENERIC_SENSOR_PUBLIC_IMPLEMENTATION=1"
   export_header =
diff --git a/device/vr/BUILD.gn b/device/vr/BUILD.gn
index 10b1226..62a3d7d 100644
--- a/device/vr/BUILD.gn
+++ b/device/vr/BUILD.gn
@@ -116,7 +116,10 @@
   }
 }
 
-mojom("mojo_bindings") {
+mojom_component("mojo_bindings") {
+  output_prefix = "device_vr_mojo_bindings"
+  macro_prefix = "DEVICE_VR_MOJO_BINDINGS"
+
   sources = [
     "vr_service.mojom",
   ]
@@ -126,10 +129,6 @@
     "//mojo/common:common_custom_types",
   ]
 
-  export_class_attribute = "DEVICE_VR_EXPORT"
-  export_define = "DEVICE_VR_IMPLEMENTATION=1"
-  export_header = "device/vr/vr_export.h"
-
   # TODO(crbug.com/714018): Convert the implementation to use OnceCallback.
   use_once_callback = false
 }
diff --git a/extensions/features/features.gni b/extensions/features/features.gni
index e469c79..6836e4a 100644
--- a/extensions/features/features.gni
+++ b/extensions/features/features.gni
@@ -5,7 +5,7 @@
 import("//build/config/features.gni")
 
 declare_args() {
-  enable_extensions = !is_android && !is_ios
+  enable_extensions = !is_android && !is_ios && !is_fuchsia
 
   # Enables Wi-Fi Display functionality
   # WARNING: This enables MPEG Transport Stream (MPEG-TS) encoding!
diff --git a/gpu/ipc/service/gpu_channel.cc b/gpu/ipc/service/gpu_channel.cc
index 37781d4..f9a65fcb 100644
--- a/gpu/ipc/service/gpu_channel.cc
+++ b/gpu/ipc/service/gpu_channel.cc
@@ -118,11 +118,11 @@
   io_thread_checker_.DetachFromThread();
 }
 
-GpuChannelMessageQueue::~GpuChannelMessageQueue() {
-  DCHECK(channel_messages_.empty());
-}
+GpuChannelMessageQueue::~GpuChannelMessageQueue() = default;
 
 void GpuChannelMessageQueue::Destroy() {
+  // There's no need to reply to sync messages here because the channel is being
+  // destroyed and the client Sends will fail.
   sync_point_order_data_->Destroy();
 
   if (preempting_flag_)
diff --git a/headless/public/util/generic_url_request_job_test.cc b/headless/public/util/generic_url_request_job_test.cc
index ec7f614e..3fe0e5569 100644
--- a/headless/public/util/generic_url_request_job_test.cc
+++ b/headless/public/util/generic_url_request_job_test.cc
@@ -370,56 +370,56 @@
 
   // Basic matching cookie.
   cookies->push_back(*net::CanonicalCookie::Create(
-      GURL("https://example.com"), "basic_cookie", "1", "example.com", "/",
-      base::Time(), base::Time(),
+      "basic_cookie", "1", ".example.com", "/", base::Time(), base::Time(),
+      base::Time(),
       /* secure */ false,
       /* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
       net::COOKIE_PRIORITY_DEFAULT));
 
   // Matching secure cookie.
   cookies->push_back(*net::CanonicalCookie::Create(
-      GURL("https://example.com"), "secure_cookie", "2", "example.com", "/",
-      base::Time(), base::Time(),
+      "secure_cookie", "2", ".example.com", "/", base::Time(), base::Time(),
+      base::Time(),
       /* secure */ true,
       /* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
       net::COOKIE_PRIORITY_DEFAULT));
 
   // Matching http-only cookie.
   cookies->push_back(*net::CanonicalCookie::Create(
-      GURL("https://example.com"), "http_only_cookie", "3", "example.com", "/",
-      base::Time(), base::Time(),
+      "http_only_cookie", "3", ".example.com", "/", base::Time(), base::Time(),
+      base::Time(),
       /* secure */ false,
       /* http_only */ true, net::CookieSameSite::NO_RESTRICTION,
       net::COOKIE_PRIORITY_DEFAULT));
 
   // Matching cookie with path.
   cookies->push_back(*net::CanonicalCookie::Create(
-      GURL("https://example.com"), "cookie_with_path", "4", "example.com",
-      "/widgets", base::Time(), base::Time(),
+      "cookie_with_path", "4", ".example.com", "/widgets", base::Time(),
+      base::Time(), base::Time(),
       /* secure */ false,
       /* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
       net::COOKIE_PRIORITY_DEFAULT));
 
   // Matching cookie with subdomain.
   cookies->push_back(*net::CanonicalCookie::Create(
-      GURL("https://cdn.example.com"), "bad_subdomain_cookie", "5",
-      "cdn.example.com", "/", base::Time(), base::Time(),
+      "bad_subdomain_cookie", "5", ".cdn.example.com", "/", base::Time(),
+      base::Time(), base::Time(),
       /* secure */ false,
       /* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
       net::COOKIE_PRIORITY_DEFAULT));
 
   // Non-matching cookie (different site).
   cookies->push_back(*net::CanonicalCookie::Create(
-      GURL("https://zombo.com"), "bad_site_cookie", "6", "zombo.com", "/",
-      base::Time(), base::Time(),
+      "bad_site_cookie", "6", ".zombo.com", "/", base::Time(), base::Time(),
+      base::Time(),
       /* secure */ false,
       /* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
       net::COOKIE_PRIORITY_DEFAULT));
 
   // Non-matching cookie (different path).
   cookies->push_back(*net::CanonicalCookie::Create(
-      GURL("https://example.com"), "bad_path_cookie", "7", "example.com",
-      "/gadgets", base::Time(), base::Time(),
+      "bad_path_cookie", "7", ".example.com", "/gadgets", base::Time(),
+      base::Time(), base::Time(),
       /* secure */ false,
       /* http_only */ false, net::CookieSameSite::NO_RESTRICTION,
       net::COOKIE_PRIORITY_DEFAULT));
diff --git a/ios/chrome/app/application_delegate/app_state.mm b/ios/chrome/app/application_delegate/app_state.mm
index 2062c8f..2f55716 100644
--- a/ios/chrome/app/application_delegate/app_state.mm
+++ b/ios/chrome/app/application_delegate/app_state.mm
@@ -340,12 +340,11 @@
                           startupInformation:_startupInformation
                       browserViewInformation:[_browserLauncher
                                                  browserViewInformation]];
-  } else if (_shouldOpenNTPTabOnActive) {
-    if (![tabSwitcher openNewTabFromTabSwitcher]) {
-      [[[_browserLauncher browserViewInformation] currentBVC] newTab:nil];
-    }
-    _shouldOpenNTPTabOnActive = NO;
+  } else if (_shouldOpenNTPTabOnActive &&
+             ![tabSwitcher openNewTabFromTabSwitcher]) {
+    [[[_browserLauncher browserViewInformation] currentBVC] newTab:nil];
   }
+  _shouldOpenNTPTabOnActive = NO;
 
   [MetricsMediator logStartupDuration:_startupInformation];
 }
diff --git a/ios/chrome/browser/ntp_tiles/ios_most_visited_sites_factory.cc b/ios/chrome/browser/ntp_tiles/ios_most_visited_sites_factory.cc
index a3a4b48f..22b8b1b4 100644
--- a/ios/chrome/browser/ntp_tiles/ios_most_visited_sites_factory.cc
+++ b/ios/chrome/browser/ntp_tiles/ios_most_visited_sites_factory.cc
@@ -14,6 +14,7 @@
 #include "components/ntp_tiles/most_visited_sites.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #include "ios/chrome/browser/favicon/favicon_service_factory.h"
+#include "ios/chrome/browser/favicon/ios_chrome_large_icon_service_factory.h"
 #include "ios/chrome/browser/history/top_sites_factory.h"
 #include "ios/chrome/browser/ntp_tiles/ios_popular_sites_factory.h"
 #include "ios/chrome/browser/suggestions/suggestions_service_factory.h"
@@ -35,6 +36,7 @@
       base::MakeUnique<ntp_tiles::IconCacherImpl>(
           ios::FaviconServiceFactory::GetForBrowserState(
               browser_state, ServiceAccessType::IMPLICIT_ACCESS),
+          IOSChromeLargeIconServiceFactory::GetForBrowserState(browser_state),
           base::MakeUnique<image_fetcher::ImageFetcherImpl>(
               image_fetcher::CreateIOSImageDecoder(task_runner),
               browser_state->GetRequestContext())),
diff --git a/ios/net/cookies/cookie_cache_unittest.cc b/ios/net/cookies/cookie_cache_unittest.cc
index a3438ae44..f875004 100644
--- a/ios/net/cookies/cookie_cache_unittest.cc
+++ b/ios/net/cookies/cookie_cache_unittest.cc
@@ -17,10 +17,10 @@
 CanonicalCookie MakeCookie(const GURL& url,
                            const std::string& name,
                            const std::string& value) {
-  return *CanonicalCookie::Create(url, name, value, std::string(), url.path(),
-                                  base::Time(), base::Time(), false, false,
-                                  net::CookieSameSite::DEFAULT_MODE,
-                                  net::COOKIE_PRIORITY_DEFAULT);
+  return *CanonicalCookie::Create(
+      name, value, url.host(), url.path(), base::Time(), base::Time(),
+      base::Time(), false, false, net::CookieSameSite::DEFAULT_MODE,
+      net::COOKIE_PRIORITY_DEFAULT);
 }
 
 }  // namespace
diff --git a/ios/net/cookies/cookie_store_ios.mm b/ios/net/cookies/cookie_store_ios.mm
index 8e9e9ea..1856af7 100644
--- a/ios/net/cookies/cookie_store_ios.mm
+++ b/ios/net/cookies/cookie_store_ios.mm
@@ -374,12 +374,43 @@
   if (creation_time.is_null())
     creation_time = base::Time::Now();
 
+  // Validate consistency of passed arguments.
+  if (ParsedCookie::ParseTokenString(name) != name ||
+      ParsedCookie::ParseValueString(value) != value ||
+      ParsedCookie::ParseValueString(domain) != domain ||
+      ParsedCookie::ParseValueString(path) != path) {
+    if (!callback.is_null())
+      callback.Run(false);
+    return;
+  }
+
+  // Validate passed arguments against URL.
+  std::string cookie_domain;
+  std::string cookie_path = CanonicalCookie::CanonPathWithString(url, path);
+  if ((secure && !url.SchemeIsCryptographic()) ||
+      !cookie_util::GetCookieDomainWithString(url, domain, &cookie_domain) ||
+      (!path.empty() && cookie_path != path)) {
+    if (!callback.is_null())
+      callback.Run(false);
+    return;
+  }
+
+  // Canonicalize path again to make sure it escapes characters as needed.
+  url::Component path_component(0, cookie_path.length());
+  url::RawCanonOutputT<char> canon_path;
+  url::Component canon_path_component;
+  url::CanonicalizePath(cookie_path.data(), path_component, &canon_path,
+                        &canon_path_component);
+  cookie_path = std::string(canon_path.data() + canon_path_component.begin,
+                            canon_path_component.len);
+
   // First create a CanonicalCookie, to normalize the arguments,
   // particularly domain and path, and perform validation.
   std::unique_ptr<net::CanonicalCookie> canonical_cookie =
-      net::CanonicalCookie::Create(
-          url, name, value, domain, path, creation_time, expiration_time,
-          secure, http_only, same_site, priority);
+      net::CanonicalCookie::Create(name, value, cookie_domain, cookie_path,
+                                   creation_time, expiration_time,
+                                   creation_time, secure, http_only, same_site,
+                                   priority);
 
   if (canonical_cookie) {
     NSHTTPCookie* cookie = SystemCookieFromCanonicalCookie(*canonical_cookie);
diff --git a/ios/net/cookies/cookie_store_ios_test_util.mm b/ios/net/cookies/cookie_store_ios_test_util.mm
index f3771b6..a660968f 100644
--- a/ios/net/cookies/cookie_store_ios_test_util.mm
+++ b/ios/net/cookies/cookie_store_ios_test_util.mm
@@ -42,10 +42,10 @@
 
   std::unique_ptr<net::CanonicalCookie> bad_canonical_cookie(
       net::CanonicalCookie::Create(
-          GURL("http://domain/"), "name", "\x81r\xe4\xbd\xa0\xe5\xa5\xbd",
-          std::string(), "/path/",
+          "name", "\x81r\xe4\xbd\xa0\xe5\xa5\xbd", "domain", "/path/",
           base::Time(),  // creation
           base::Time(),  // expires
+          base::Time(),  // last accessed
           false,         // secure
           false,         // httponly
           net::CookieSameSite::DEFAULT_MODE, net::COOKIE_PRIORITY_DEFAULT));
@@ -138,4 +138,4 @@
   EXPECT_EQ(0u, [[store cookies] count]);
 }
 
-}  // namespace net
\ No newline at end of file
+}  // namespace net
diff --git a/ipc/BUILD.gn b/ipc/BUILD.gn
index 95f35f3..a3aaaa3 100644
--- a/ipc/BUILD.gn
+++ b/ipc/BUILD.gn
@@ -129,13 +129,12 @@
   }
 }
 
-mojom("mojom") {
+mojom_component("mojom") {
+  output_prefix = "ipc_mojom"
+  macro_prefix = "IPC_MOJOM"
   sources = [
     "ipc.mojom",
   ]
-  export_class_attribute = "IPC_EXPORT"
-  export_define = "IPC_IMPLEMENTATION"
-  export_header = "ipc/ipc_export.h"
 }
 
 mojom("test_interfaces") {
diff --git a/mojo/public/interfaces/bindings/BUILD.gn b/mojo/public/interfaces/bindings/BUILD.gn
index c2cadcd7..650e8bf7e 100644
--- a/mojo/public/interfaces/bindings/BUILD.gn
+++ b/mojo/public/interfaces/bindings/BUILD.gn
@@ -11,9 +11,14 @@
     "pipe_control_messages.mojom",
   ]
 
+  component_output_prefix = "mojo_public_interfaces_bindings"
   export_class_attribute = "MOJO_CPP_BINDINGS_EXPORT"
   export_define = "MOJO_CPP_BINDINGS_IMPLEMENTATION"
   export_header = "mojo/public/cpp/bindings/bindings_export.h"
+
+  export_class_attribute_shared = "MOJO_CPP_BINDINGS_EXPORT"
+  export_define_shared = "MOJO_CPP_BINDINGS_IMPLEMENTATION"
+  export_header_shared = "mojo/public/cpp/bindings/bindings_export.h"
 }
 
 # TODO(yzshen): Remove this target and use the one above once
diff --git a/mojo/public/tools/bindings/blink_bindings_configuration.gni b/mojo/public/tools/bindings/blink_bindings_configuration.gni
index bb0fc43..36e9fda 100644
--- a/mojo/public/tools/bindings/blink_bindings_configuration.gni
+++ b/mojo/public/tools/bindings/blink_bindings_configuration.gni
@@ -31,3 +31,4 @@
 }
 
 blacklist = []
+component_macro_suffix = "_BLINK"
diff --git a/mojo/public/tools/bindings/chromium_bindings_configuration.gni b/mojo/public/tools/bindings/chromium_bindings_configuration.gni
index 0d020488..6fe0750 100644
--- a/mojo/public/tools/bindings/chromium_bindings_configuration.gni
+++ b/mojo/public/tools/bindings/chromium_bindings_configuration.gni
@@ -80,3 +80,5 @@
         config = read_file(typemap, "scope")
       } ]
 }
+
+component_macro_suffix = ""
diff --git a/mojo/public/tools/bindings/generate_export_header.py b/mojo/public/tools/bindings/generate_export_header.py
new file mode 100755
index 0000000..a1639c6
--- /dev/null
+++ b/mojo/public/tools/bindings/generate_export_header.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Generates a C++ header to define a component export macro."""
+
+import argparse
+import os
+import sys
+
+
+_TEMPLATE = """// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef {{guard}}
+#define {{guard}}
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined({{impl_macro}})
+#define {{export_macro}} __declspec(dllexport)
+#else
+#define {{export_macro}} __declspec(dllimport)
+#endif  // defined({{impl_macro}})
+
+#else  // defined(WIN32)
+
+#if defined({{impl_macro}})
+#define {{export_macro}} __attribute__((visibility("default")))
+#else
+#define {{export_macro}}
+#endif
+
+#endif
+
+#else  // defined(COMPONENT_BUILD)
+#define {{export_macro}}
+#endif
+
+#endif  // {{guard}}
+"""
+
+
+def WriteHeader(output_file, relative_path, impl_macro, export_macro):
+  # Allow inputs for impl_macro to be of the form FOO_IMPL=1. Drop the "=1".
+  impl_macro = impl_macro.split("=")[0]
+
+  substitutions = {
+    "guard": relative_path.replace("/", "_").replace(".", "_").upper() + "_",
+    "impl_macro": impl_macro,
+    "export_macro": export_macro,
+  }
+  contents = _TEMPLATE
+  for k, v in substitutions.iteritems():
+    contents = contents.replace("{{%s}}" % k, v)
+  with open(output_file, "w") as f:
+    f.write(contents)
+
+
+def main():
+  parser = argparse.ArgumentParser(
+      description=__doc__,
+      formatter_class=argparse.RawDescriptionHelpFormatter)
+  parser.add_argument(
+      "--impl_macro", type=str, required=True,
+      help=("The name of the macro to emit for component IMPL blocks."))
+  parser.add_argument(
+      "--export_macro", type=str, required=True,
+      help=("The name of the macro to emit for component EXPORT attributes."))
+  parser.add_argument(
+      "--output_file", type=str, required=True,
+      help=("The file path to which the generated header should be written."))
+  parser.add_argument(
+      "--relative_path", type=str, required=True,
+      help=("The generated header's path relative to the generated output"
+            " root. Used to generate header guards."))
+
+  params, _ = parser.parse_known_args()
+  WriteHeader(params.output_file, params.relative_path, params.impl_macro,
+              params.export_macro)
+
+
+if __name__ == "__main__":
+  sys.exit(main())
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module-shared-internal.h.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module-shared-internal.h.tmpl
index c400868..3033a99 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/module-shared-internal.h.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/module-shared-internal.h.tmpl
@@ -22,6 +22,10 @@
 #include "{{import.path}}-shared-internal.h"
 {%- endfor %}
 
+{%- if export_header %}
+#include "{{export_header}}"
+{%- endif %}
+
 namespace mojo {
 namespace internal {
 class ValidationContext;
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl
index b7c3070..148dc7ae 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl
@@ -57,6 +57,10 @@
 #include "{{import.path}}-shared.h"
 {%- endfor %}
 
+{%- if export_header %}
+#include "{{export_header}}"
+{%- endif %}
+
 {{namespace_begin()}}
 
 {#--- Struct Forward Declarations -#}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl
index 156f774..cc2dcb2 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl
@@ -1,10 +1,8 @@
 {%- set class_name = struct.name ~ "_Data" -%}
 
-class {{class_name}} {
+class {{export_attribute}} {{class_name}} {
  public:
-  static {{class_name}}* New(mojo::internal::Buffer* buf) {
-    return new (buf->Allocate(sizeof({{class_name}}))) {{class_name}}();
-  }
+  static {{class_name}}* New(mojo::internal::Buffer* buf);
 
   static bool Validate(const void* data,
                        mojo::internal::ValidationContext* validation_context);
@@ -38,8 +36,7 @@
 {%- endif %}
 
  private:
-  {{class_name}}() : header_({sizeof(*this), {{struct.versions[-1].version}}}) {
-  }
+  {{class_name}}();
   ~{{class_name}}() = delete;
 };
 static_assert(sizeof({{class_name}}) == {{struct.versions[-1].num_bytes}},
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl
index 60dca401..ad816ab 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl
@@ -2,6 +2,11 @@
 {%- set class_name = struct.name ~ "_Data" %}
 
 // static
+{{class_name}}* {{class_name}}::New(mojo::internal::Buffer* buf) {
+  return new (buf->Allocate(sizeof({{class_name}}))) {{class_name}}();
+}
+
+// static
 bool {{class_name}}::Validate(
     const void* data,
     mojo::internal::ValidationContext* validation_context) {
@@ -68,3 +73,5 @@
   return true;
 }
 
+{{class_name}}::{{class_name}}()
+    : header_({sizeof(*this), {{struct.versions[-1].version}}}) {}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_declaration.tmpl
index 005ba76b..a249eb6 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/union_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/union_declaration.tmpl
@@ -2,7 +2,7 @@
 {%- set enum_name = union.name ~ "_Tag" -%}
 {%- import "struct_macros.tmpl" as struct_macros %}
 
-class {{class_name}} {
+class {{export_attribute}} {{class_name}} {
  public:
   // Used to identify Mojom Union Data Classes.
   typedef void MojomUnionDataType;
diff --git a/mojo/public/tools/bindings/mojom.gni b/mojo/public/tools/bindings/mojom.gni
index 51497f1..8abf2042 100644
--- a/mojo/public/tools/bindings/mojom.gni
+++ b/mojo/public/tools/bindings/mojom.gni
@@ -36,6 +36,9 @@
   "$mojom_generator_script",
 ]
 
+generate_export_header_script =
+    "$mojom_generator_root/generate_export_header.py"
+
 if (enable_mojom_typemapping) {
   if (!is_ios) {
     _bindings_configuration_files = [
@@ -72,9 +75,11 @@
   _bindings_configurations = [
     {
       typemaps = []
+      component_macro_suffix = ""
     },
     {
       variant = "blink"
+      component_macro_suffix = "_BLINK"
       for_blink = true
       typemaps = []
     },
@@ -141,10 +146,30 @@
 #       TODO(yzshen): Switch all existing users to use_new_js_bindings=true and
 #       remove the old mode.
 #
+#   component_output_prefix (optional)
+#       The prefix to use for the output_name of any component library emitted
+#       for generated C++ bindings. If this is omitted, C++ bindings targets are
+#       emitted as source_sets instead. Because this controls the name of the
+#       output shared library binary in the root output directory, it must be
+#       unique across the entire build configuration.
+#
+#       This is required if |component_macro_prefix| is specified.
+#
+#   component_macro_prefix (optional)
+#       This specifies a macro prefix to use for component export macros and
+#       should therefore be globally unique in the project. For example if this
+#       is "FOO_BAR", then the generated C++ sources will be built with
+#       FOO_BAR_IMPL defined, and the generated public headers will affix
+#       FOO_BAR_EXPORT to all public symbol definitions; the meaning of the
+#       EXPORT macro depends on whether the corresponding IMPL macro is defined,
+#       per standard practice with Chromium component exports.
+#
 # The following parameters are used to support the component build. They are
 # needed so that bindings which are linked with a component can use the same
 # export settings for classes. The first three are for the chromium variant, and
-# the last three are for the blink variant.
+# the last three are for the blink variant. These parameters are mutually
+# exclusive to |component_macro_prefix|, but |component_output_prefix| may still
+# be used to uniqueify the generated invariant (i.e. shared) output component.
 #   export_class_attribute (optional)
 #       The attribute to add to the class declaration. e.g. "CONTENT_EXPORT"
 #   export_define (optional)
@@ -180,6 +205,7 @@
     assert(defined(invoker.export_class_attribute))
     assert(defined(invoker.export_define))
     assert(defined(invoker.export_header))
+    assert(!defined(invoker.component_macro_prefix))
   }
   if (defined(invoker.export_class_attribute_blink) ||
       defined(invoker.export_define_blink) ||
@@ -187,6 +213,7 @@
     assert(defined(invoker.export_class_attribute_blink))
     assert(defined(invoker.export_define_blink))
     assert(defined(invoker.export_header_blink))
+    assert(!defined(invoker.component_macro_prefix))
   }
   if (defined(invoker.overridden_deps) || defined(invoker.component_deps)) {
     assert(defined(invoker.overridden_deps))
@@ -207,6 +234,10 @@
     all_deps += invoker.public_deps
   }
 
+  if (defined(invoker.component_macro_prefix)) {
+    assert(defined(invoker.component_output_prefix))
+  }
+
   group("${target_name}__is_mojom") {
   }
 
@@ -246,6 +277,47 @@
       }
     }
 
+    if (defined(invoker.component_macro_prefix)) {
+      shared_component_export_macro =
+          "${invoker.component_macro_prefix}_SHARED_EXPORT"
+      shared_component_impl_macro =
+          "${invoker.component_macro_prefix}_SHARED_IMPL"
+      shared_component_output_name = "${invoker.component_output_prefix}_shared"
+    } else if (defined(invoker.export_class_attribute_shared) ||
+               defined(invoker.export_class_attribute)) {
+      if (defined(invoker.export_class_attribute_shared)) {
+        assert(defined(invoker.export_header_shared))
+        shared_component_export_macro = invoker.export_class_attribute_shared
+        shared_component_impl_macro = invoker.export_define_shared
+      } else {
+        assert(!defined(invoker.export_header_shared))
+
+        # If no explicit shared attribute/define was provided by the invoker,
+        # we derive some reasonable settings frorm the default variant. A new
+        # export header will be generated for these and used by shared code.
+        shared_component_export_macro =
+            "MOJOM_SHARED_" + invoker.export_class_attribute
+        shared_component_impl_macro = "MOJOM_SHARED_" + invoker.export_define
+      }
+
+      if (defined(invoker.component_output_prefix)) {
+        shared_component_output_name =
+            "${invoker.component_output_prefix}_shared"
+      } else {
+        shared_component_output_name = "${target_name}_shared"
+      }
+    }
+
+    if (defined(shared_component_export_macro) &&
+        !defined(invoker.export_header_shared)) {
+      generated_shared_export_header =
+          rebase_path("${target_name}_shared_export.h", "", target_gen_dir)
+      shared_export_header =
+          rebase_path(generated_shared_export_header, root_gen_dir)
+    } else if (defined(invoker.export_header_shared)) {
+      shared_export_header = invoker.export_header_shared
+    }
+
     generator_shared_cpp_outputs = [
       "{{source_gen_dir}}/{{source_name_part}}.mojom-shared-internal.h",
       "{{source_gen_dir}}/{{source_name_part}}.mojom-shared.cc",
@@ -273,11 +345,40 @@
         "--depfile_target",
         "{{source_gen_dir}}/{{source_name_part}}.mojom-shared-internal.h",
       ]
+
+      if (defined(shared_component_export_macro)) {
+        args += [
+          "--export_attribute",
+          shared_component_export_macro,
+          "--export_header",
+          shared_export_header,
+        ]
+      }
     }
   }
 
-  shared_cpp_sources_suffix = "shared_cpp_sources"
-  shared_cpp_sources_target_name = "${target_name}_${shared_cpp_sources_suffix}"
+  if (defined(generated_shared_export_header)) {
+    shared_export_generator_target_name =
+        "${target_name}__generate_shared_export_header"
+    action(shared_export_generator_target_name) {
+      script = generate_export_header_script
+      outputs = [
+        get_path_info(generated_shared_export_header, "abspath"),
+      ]
+      args = [
+        "--export_macro",
+        shared_component_export_macro,
+        "--impl_macro",
+        shared_component_impl_macro,
+        "--output_file",
+        generated_shared_export_header,
+        "--relative_path",
+        rebase_path(generated_shared_export_header, root_gen_dir),
+      ]
+    }
+  }
+
+  shared_cpp_sources_target_name = "${target_name}_shared_cpp_sources"
   source_set(shared_cpp_sources_target_name) {
     if (defined(invoker.testonly)) {
       testonly = invoker.testonly
@@ -288,13 +389,44 @@
           process_file_template(invoker.sources, generator_shared_cpp_outputs)
       deps += [ ":$generator_shared_target_name" ]
     }
-    public_deps = []
+    public_deps = [
+      "//mojo/public/cpp/bindings",
+    ]
     foreach(d, all_deps) {
       # Resolve the name, so that a target //mojo/something becomes
       # //mojo/something:something and we can append shared_cpp_sources_suffix
       # to get the cpp dependency name.
       full_name = get_label_info("$d", "label_no_toolchain")
-      public_deps += [ "${full_name}_${shared_cpp_sources_suffix}" ]
+      public_deps += [ "${full_name}_shared" ]
+    }
+    if (defined(shared_component_impl_macro)) {
+      defines = [ shared_component_impl_macro ]
+    }
+    if (defined(generated_shared_export_header)) {
+      sources += [ get_path_info(generated_shared_export_header, "abspath") ]
+      public_deps += [ ":$shared_export_generator_target_name" ]
+    }
+  }
+
+  shared_cpp_library_target_name = "${target_name}_shared"
+  if (defined(shared_component_output_name)) {
+    component(shared_cpp_library_target_name) {
+      if (defined(invoker.testonly)) {
+        testonly = invoker.testonly
+      }
+      output_name = "$shared_component_output_name"
+      public_deps = [
+        ":$shared_cpp_sources_target_name",
+      ]
+    }
+  } else {
+    group(shared_cpp_library_target_name) {
+      if (defined(invoker.testonly)) {
+        testonly = invoker.testonly
+      }
+      public_deps = [
+        ":$shared_cpp_sources_target_name",
+      ]
     }
   }
 
@@ -365,6 +497,31 @@
         }
       }
 
+      if (defined(invoker.component_macro_prefix)) {
+        export_header_generator_target_name =
+            "${target_name}${variant_suffix}__generate_export_header"
+        generated_export_header =
+            rebase_path("${target_name}${variant_suffix}_export.h",
+                        "",
+                        target_gen_dir)
+        action(export_header_generator_target_name) {
+          script = generate_export_header_script
+          outputs = [
+            get_path_info(generated_export_header, "abspath"),
+          ]
+          args = [
+            "--export_macro",
+            "${invoker.component_macro_prefix}${bindings_configuration.component_macro_suffix}_EXPORT",
+            "--impl_macro",
+            "${invoker.component_macro_prefix}${bindings_configuration.component_macro_suffix}_IMPL",
+            "--output_file",
+            generated_export_header,
+            "--relative_path",
+            rebase_path(generated_export_header, root_gen_dir),
+          ]
+        }
+      }
+
       if (!cpp_only) {
         generator_js_outputs =
             [ "{{source_gen_dir}}/{{source_name_part}}.mojom.js" ]
@@ -416,6 +573,15 @@
           rebase_path(type_mappings_path, root_build_dir),
         ]
 
+        if (defined(invoker.component_macro_prefix)) {
+          args += [
+            "--export_attribute",
+            "${invoker.component_macro_prefix}${bindings_configuration.component_macro_suffix}_EXPORT",
+            "--export_header",
+            rebase_path(generated_export_header, root_gen_dir),
+          ]
+        }
+
         if (defined(bindings_configuration.for_blink) &&
             bindings_configuration.for_blink) {
           args += [ "--for_blink" ]
@@ -511,7 +677,13 @@
       }
     }
 
-    source_set("${target_name}${variant_suffix}") {
+    if (defined(invoker.component_macro_prefix)) {
+      output_target_type = "component"
+    } else {
+      output_target_type = "source_set"
+    }
+
+    target(output_target_type, "${target_name}${variant_suffix}") {
       if (defined(bindings_configuration.for_blink) &&
           bindings_configuration.for_blink &&
           defined(invoker.visibility_blink)) {
@@ -544,13 +716,19 @@
         "//mojo/public/interfaces/bindings:bindings_shared__generator",
       ]
       public_deps = [
-        ":$shared_cpp_sources_target_name",
+        ":$shared_cpp_library_target_name",
         "//base",
         "//mojo/public/cpp/bindings",
       ]
       if (enabled_sources != []) {
         public_deps += [ ":$generator_target_name" ]
       }
+      if (defined(invoker.component_macro_prefix)) {
+        output_name = "${invoker.component_output_prefix}${variant_suffix}"
+        defines += [ "${invoker.component_macro_prefix}${bindings_configuration.component_macro_suffix}_IMPL" ]
+        sources += [ get_path_info(generated_export_header, "abspath") ]
+        deps += [ ":$export_header_generator_target_name" ]
+      }
       foreach(d, all_deps) {
         # Resolve the name, so that a target //mojo/something becomes
         # //mojo/something:something and we can append variant_suffix to
@@ -662,3 +840,23 @@
     }
   }
 }
+
+# A helper for the mojom() template above when component libraries are desired
+# for generated C++ bindings units. Supports all the same arguments as mojom()
+# except for the optional |component_output_prefix| and |component_macro_prefix|
+# arguments. These are instead shortened to |output_prefix| and |macro_prefix|
+# and are *required*.
+template("mojom_component") {
+  assert(defined(invoker.output_prefix) && defined(invoker.macro_prefix))
+
+  mojom(target_name) {
+    forward_variables_from(invoker,
+                           "*",
+                           [
+                             "output_prefix",
+                             "macro_prefix",
+                           ])
+    component_output_prefix = invoker.output_prefix
+    component_macro_prefix = invoker.macro_prefix
+  }
+}
diff --git a/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java b/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java
index 0148717..4209092 100644
--- a/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java
+++ b/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java
@@ -208,7 +208,9 @@
         @TargetApi(Build.VERSION_CODES.LOLLIPOP)
         @VisibleForTesting
         protected Network[] getAllNetworksUnfiltered() {
-            return mConnectivityManager.getAllNetworks();
+            Network[] networks = mConnectivityManager.getAllNetworks();
+            // Very rarely this API inexplicably returns {@code null}, crbug.com/721116.
+            return networks == null ? new Network[0] : networks;
         }
 
         /**
diff --git a/net/android/javatests/src/org/chromium/net/NetworkChangeNotifierTest.java b/net/android/javatests/src/org/chromium/net/NetworkChangeNotifierTest.java
index caa9ec8..6c1b7a8 100644
--- a/net/android/javatests/src/org/chromium/net/NetworkChangeNotifierTest.java
+++ b/net/android/javatests/src/org/chromium/net/NetworkChangeNotifierTest.java
@@ -733,6 +733,7 @@
                     ConnectionType.CONNECTION_NONE, delegate.getConnectionType(invalidNetwork));
 
             Network[] networks = delegate.getAllNetworksUnfiltered();
+            Assert.assertNotNull(networks);
             if (networks.length >= 1) {
                 delegate.getConnectionType(networks[0]);
             }
diff --git a/net/cookies/canonical_cookie.cc b/net/cookies/canonical_cookie.cc
index 2b53e4ee..2eb732b 100644
--- a/net/cookies/canonical_cookie.cc
+++ b/net/cookies/canonical_cookie.cc
@@ -74,36 +74,6 @@
   return cookie_util::GetCookieDomainWithString(url, domain_string, result);
 }
 
-std::string CanonPathWithString(const GURL& url,
-                                const std::string& path_string) {
-  // The RFC says the path should be a prefix of the current URL path.
-  // However, Mozilla allows you to set any path for compatibility with
-  // broken websites.  We unfortunately will mimic this behavior.  We try
-  // to be generous and accept cookies with an invalid path attribute, and
-  // default the path to something reasonable.
-
-  // The path was supplied in the cookie, we'll take it.
-  if (!path_string.empty() && path_string[0] == '/')
-    return path_string;
-
-  // The path was not supplied in the cookie or invalid, we will default
-  // to the current URL path.
-  // """Defaults to the path of the request URL that generated the
-  //    Set-Cookie response, up to, but not including, the
-  //    right-most /."""
-  // How would this work for a cookie on /?  We will include it then.
-  const std::string& url_path = url.path();
-
-  size_t idx = url_path.find_last_of('/');
-
-  // The cookie path was invalid or a single '/'.
-  if (idx == 0 || idx == std::string::npos)
-    return std::string("/");
-
-  // Return up to the rightmost '/'.
-  return url_path.substr(0, idx);
-}
-
 // Compares cookies using name, domain and path, so that "equivalent" cookies
 // (per RFC 2965) are equal to each other.
 int PartialCookieOrdering(const CanonicalCookie& a, const CanonicalCookie& b) {
@@ -130,12 +100,35 @@
 CanonicalCookie::~CanonicalCookie() {}
 
 // static
-std::string CanonicalCookie::CanonPath(const GURL& url,
-                                       const ParsedCookie& pc) {
-  std::string path_string;
-  if (pc.HasPath())
-    path_string = pc.Path();
-  return CanonPathWithString(url, path_string);
+std::string CanonicalCookie::CanonPathWithString(
+    const GURL& url,
+    const std::string& path_string) {
+  // The RFC says the path should be a prefix of the current URL path.
+  // However, Mozilla allows you to set any path for compatibility with
+  // broken websites.  We unfortunately will mimic this behavior.  We try
+  // to be generous and accept cookies with an invalid path attribute, and
+  // default the path to something reasonable.
+
+  // The path was supplied in the cookie, we'll take it.
+  if (!path_string.empty() && path_string[0] == '/')
+    return path_string;
+
+  // The path was not supplied in the cookie or invalid, we will default
+  // to the current URL path.
+  // """Defaults to the path of the request URL that generated the
+  //    Set-Cookie response, up to, but not including, the
+  //    right-most /."""
+  // How would this work for a cookie on /?  We will include it then.
+  const std::string& url_path = url.path();
+
+  size_t idx = url_path.find_last_of('/');
+
+  // The cookie path was invalid or a single '/'.
+  if (idx == 0 || idx == std::string::npos)
+    return std::string("/");
+
+  // Return up to the rightmost '/'.
+  return url_path.substr(0, idx);
 }
 
 // static
@@ -201,7 +194,9 @@
     return nullptr;
   }
 
-  std::string cookie_path = CanonicalCookie::CanonPath(url, parsed_cookie);
+  std::string cookie_path = CanonPathWithString(
+      url, parsed_cookie.HasPath() ? parsed_cookie.Path() : std::string());
+
   Time server_time(creation_time);
   if (options.has_server_time())
     server_time = options.server_time();
@@ -229,62 +224,6 @@
 
 // static
 std::unique_ptr<CanonicalCookie> CanonicalCookie::Create(
-    const GURL& url,
-    const std::string& name,
-    const std::string& value,
-    const std::string& domain,
-    const std::string& path,
-    const base::Time& creation,
-    const base::Time& expiration,
-    bool secure,
-    bool http_only,
-    CookieSameSite same_site,
-    CookiePriority priority) {
-  // Expect valid attribute tokens and values, as defined by the ParsedCookie
-  // logic, otherwise don't create the cookie.
-  std::string parsed_name = ParsedCookie::ParseTokenString(name);
-  if (parsed_name != name)
-    return nullptr;
-  std::string parsed_value = ParsedCookie::ParseValueString(value);
-  if (parsed_value != value)
-    return nullptr;
-
-  std::string parsed_domain = ParsedCookie::ParseValueString(domain);
-  if (parsed_domain != domain)
-    return nullptr;
-  std::string cookie_domain;
-  if (!cookie_util::GetCookieDomainWithString(url, parsed_domain,
-                                               &cookie_domain)) {
-    return nullptr;
-  }
-
-  if (secure && !url.SchemeIsCryptographic())
-    return nullptr;
-
-  std::string parsed_path = ParsedCookie::ParseValueString(path);
-  if (parsed_path != path)
-    return nullptr;
-
-  std::string cookie_path = CanonPathWithString(url, parsed_path);
-  // Expect that the path was either not specified (empty), or is valid.
-  if (!parsed_path.empty() && cookie_path != parsed_path)
-    return nullptr;
-  // Canonicalize path again to make sure it escapes characters as needed.
-  url::Component path_component(0, cookie_path.length());
-  url::RawCanonOutputT<char> canon_path;
-  url::Component canon_path_component;
-  url::CanonicalizePath(cookie_path.data(), path_component, &canon_path,
-                        &canon_path_component);
-  cookie_path = std::string(canon_path.data() + canon_path_component.begin,
-                            canon_path_component.len);
-
-  return base::WrapUnique(new CanonicalCookie(
-      parsed_name, parsed_value, cookie_domain, cookie_path, creation,
-      expiration, creation, secure, http_only, same_site, priority));
-}
-
-// static
-std::unique_ptr<CanonicalCookie> CanonicalCookie::Create(
     const std::string& name,
     const std::string& value,
     const std::string& domain,
@@ -296,6 +235,9 @@
     bool http_only,
     CookieSameSite same_site,
     CookiePriority priority) {
+  DCHECK(!name.empty());
+  DCHECK(!path.empty());
+
   return base::WrapUnique(
       new CanonicalCookie(name, value, domain, path, creation, expiration,
                           last_access, secure, http_only, same_site, priority));
diff --git a/net/cookies/canonical_cookie.h b/net/cookies/canonical_cookie.h
index 18d9302e..1dc66dc 100644
--- a/net/cookies/canonical_cookie.h
+++ b/net/cookies/canonical_cookie.h
@@ -40,22 +40,8 @@
       const CookieOptions& options);
 
   // Creates a canonical cookie from unparsed attribute values.
-  // Canonicalizes and validates inputs.  May return NULL if an attribute
-  // value is invalid.
-  static std::unique_ptr<CanonicalCookie> Create(const GURL& url,
-                                                 const std::string& name,
-                                                 const std::string& value,
-                                                 const std::string& domain,
-                                                 const std::string& path,
-                                                 const base::Time& creation,
-                                                 const base::Time& expiration,
-                                                 bool secure,
-                                                 bool http_only,
-                                                 CookieSameSite same_site,
-                                                 CookiePriority priority);
-
-  // Creates a canonical cookie from unparsed attribute values.
-  // It does not do any validation.
+  // It does not do any canonicalization.
+  // |name| and |path| must not be empty.
   static std::unique_ptr<CanonicalCookie> Create(const std::string& name,
                                                  const std::string& value,
                                                  const std::string& domain,
@@ -137,7 +123,8 @@
 
   std::string DebugString() const;
 
-  static std::string CanonPath(const GURL& url, const ParsedCookie& pc);
+  static std::string CanonPathWithString(const GURL& url,
+                                         const std::string& path_string);
 
   // Returns a "null" time if expiration was unspecified or invalid.
   static base::Time CanonExpiration(const ParsedCookie& pc,
diff --git a/net/cookies/canonical_cookie_unittest.cc b/net/cookies/canonical_cookie_unittest.cc
index 2386e85..f3b6eff 100644
--- a/net/cookies/canonical_cookie_unittest.cc
+++ b/net/cookies/canonical_cookie_unittest.cc
@@ -19,8 +19,9 @@
   base::Time current_time = base::Time::Now();
 
   std::unique_ptr<CanonicalCookie> cookie(CanonicalCookie::Create(
-      url, "A", "2", std::string(), "/test", current_time, base::Time(), false,
-      false, CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_DEFAULT));
+      "A", "2", "www.example.com", "/test", current_time, base::Time(),
+      base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
+      COOKIE_PRIORITY_DEFAULT));
   EXPECT_EQ("A", cookie->Name());
   EXPECT_EQ("2", cookie->Value());
   EXPECT_EQ("www.example.com", cookie->Domain());
@@ -30,7 +31,7 @@
   EXPECT_EQ(CookieSameSite::NO_RESTRICTION, cookie->SameSite());
 
   std::unique_ptr<CanonicalCookie> cookie2(CanonicalCookie::Create(
-      url, "A", "2", ".www.example.com", std::string(), current_time,
+      "A", "2", ".www.example.com", "/", current_time, base::Time(),
       base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
       COOKIE_PRIORITY_DEFAULT));
   EXPECT_EQ("A", cookie2->Name());
@@ -93,9 +94,10 @@
 
   // Test the creating cookies using specific parameter instead of a cookie
   // string.
-  cookie = CanonicalCookie::Create(
-      url, "A", "2", "www.example.com", "/test", creation_time, base::Time(),
-      false, false, CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_DEFAULT);
+  cookie = CanonicalCookie::Create("A", "2", ".www.example.com", "/test",
+                                   creation_time, base::Time(), base::Time(),
+                                   false, false, CookieSameSite::DEFAULT_MODE,
+                                   COOKIE_PRIORITY_DEFAULT);
   EXPECT_EQ("A", cookie->Name());
   EXPECT_EQ("2", cookie->Value());
   EXPECT_EQ(".www.example.com", cookie->Domain());
@@ -104,9 +106,10 @@
   EXPECT_FALSE(cookie->IsHttpOnly());
   EXPECT_EQ(CookieSameSite::NO_RESTRICTION, cookie->SameSite());
 
-  cookie = CanonicalCookie::Create(
-      url, "A", "2", ".www.example.com", "/test", creation_time, base::Time(),
-      false, false, CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_DEFAULT);
+  cookie = CanonicalCookie::Create("A", "2", ".www.example.com", "/test",
+                                   creation_time, base::Time(), base::Time(),
+                                   false, false, CookieSameSite::DEFAULT_MODE,
+                                   COOKIE_PRIORITY_DEFAULT);
   EXPECT_EQ("A", cookie->Name());
   EXPECT_EQ("2", cookie->Value());
   EXPECT_EQ(".www.example.com", cookie->Domain());
@@ -178,23 +181,26 @@
 
   // Test that a cookie is equivalent to itself.
   std::unique_ptr<CanonicalCookie> cookie(CanonicalCookie::Create(
-      url, cookie_name, cookie_value, cookie_domain, cookie_path, creation_time,
-      expiration_time, secure, httponly, same_site, COOKIE_PRIORITY_MEDIUM));
+      cookie_name, cookie_value, cookie_domain, cookie_path, creation_time,
+      expiration_time, base::Time(), secure, httponly, same_site,
+      COOKIE_PRIORITY_MEDIUM));
   EXPECT_TRUE(cookie->IsEquivalent(*cookie));
   EXPECT_TRUE(cookie->IsEquivalentForSecureCookieMatching(*cookie));
 
   // Test that two identical cookies are equivalent.
   std::unique_ptr<CanonicalCookie> other_cookie(CanonicalCookie::Create(
-      url, cookie_name, cookie_value, cookie_domain, cookie_path, creation_time,
-      expiration_time, secure, httponly, same_site, COOKIE_PRIORITY_MEDIUM));
+      cookie_name, cookie_value, cookie_domain, cookie_path, creation_time,
+      expiration_time, base::Time(), secure, httponly, same_site,
+      COOKIE_PRIORITY_MEDIUM));
   EXPECT_TRUE(cookie->IsEquivalent(*other_cookie));
   EXPECT_TRUE(other_cookie->IsEquivalentForSecureCookieMatching(*cookie));
 
   // Tests that use different variations of attribute values that
   // DON'T affect cookie equivalence.
   other_cookie = CanonicalCookie::Create(
-      url, cookie_name, "2", cookie_domain, cookie_path, creation_time,
-      expiration_time, secure, httponly, same_site, COOKIE_PRIORITY_HIGH);
+      cookie_name, "2", cookie_domain, cookie_path, creation_time,
+      expiration_time, base::Time(), secure, httponly, same_site,
+      COOKIE_PRIORITY_HIGH);
   EXPECT_TRUE(cookie->IsEquivalent(*other_cookie));
   EXPECT_TRUE(cookie->IsEquivalentForSecureCookieMatching(*other_cookie));
   EXPECT_TRUE(other_cookie->IsEquivalentForSecureCookieMatching(*cookie));
@@ -202,38 +208,42 @@
   base::Time other_creation_time =
       creation_time + base::TimeDelta::FromMinutes(2);
   other_cookie = CanonicalCookie::Create(
-      url, cookie_name, "2", cookie_domain, cookie_path, other_creation_time,
-      expiration_time, secure, httponly, same_site, COOKIE_PRIORITY_MEDIUM);
+      cookie_name, "2", cookie_domain, cookie_path, other_creation_time,
+      expiration_time, base::Time(), secure, httponly, same_site,
+      COOKIE_PRIORITY_MEDIUM);
   EXPECT_TRUE(cookie->IsEquivalent(*other_cookie));
   EXPECT_TRUE(cookie->IsEquivalentForSecureCookieMatching(*other_cookie));
   EXPECT_TRUE(other_cookie->IsEquivalentForSecureCookieMatching(*cookie));
 
   other_cookie = CanonicalCookie::Create(
-      url, cookie_name, cookie_name, cookie_domain, cookie_path, creation_time,
-      expiration_time, true, httponly, same_site, COOKIE_PRIORITY_LOW);
-  EXPECT_TRUE(cookie->IsEquivalent(*other_cookie));
-  EXPECT_TRUE(cookie->IsEquivalentForSecureCookieMatching(*other_cookie));
-  EXPECT_TRUE(other_cookie->IsEquivalentForSecureCookieMatching(*cookie));
-
-  other_cookie = CanonicalCookie::Create(
-      url, cookie_name, cookie_name, cookie_domain, cookie_path, creation_time,
-      expiration_time, secure, true, same_site, COOKIE_PRIORITY_LOW);
-  EXPECT_TRUE(cookie->IsEquivalent(*other_cookie));
-  EXPECT_TRUE(cookie->IsEquivalentForSecureCookieMatching(*other_cookie));
-  EXPECT_TRUE(other_cookie->IsEquivalentForSecureCookieMatching(*cookie));
-
-  other_cookie = CanonicalCookie::Create(
-      url, cookie_name, cookie_name, cookie_domain, cookie_path, creation_time,
-      expiration_time, secure, httponly, CookieSameSite::STRICT_MODE,
+      cookie_name, cookie_name, cookie_domain, cookie_path, creation_time,
+      expiration_time, base::Time(), true, httponly, same_site,
       COOKIE_PRIORITY_LOW);
   EXPECT_TRUE(cookie->IsEquivalent(*other_cookie));
   EXPECT_TRUE(cookie->IsEquivalentForSecureCookieMatching(*other_cookie));
   EXPECT_TRUE(other_cookie->IsEquivalentForSecureCookieMatching(*cookie));
 
+  other_cookie = CanonicalCookie::Create(
+      cookie_name, cookie_name, cookie_domain, cookie_path, creation_time,
+      expiration_time, base::Time(), secure, true, same_site,
+      COOKIE_PRIORITY_LOW);
+  EXPECT_TRUE(cookie->IsEquivalent(*other_cookie));
+  EXPECT_TRUE(cookie->IsEquivalentForSecureCookieMatching(*other_cookie));
+  EXPECT_TRUE(other_cookie->IsEquivalentForSecureCookieMatching(*cookie));
+
+  other_cookie = CanonicalCookie::Create(
+      cookie_name, cookie_name, cookie_domain, cookie_path, creation_time,
+      expiration_time, base::Time(), secure, httponly,
+      CookieSameSite::STRICT_MODE, COOKIE_PRIORITY_LOW);
+  EXPECT_TRUE(cookie->IsEquivalent(*other_cookie));
+  EXPECT_TRUE(cookie->IsEquivalentForSecureCookieMatching(*other_cookie));
+  EXPECT_TRUE(other_cookie->IsEquivalentForSecureCookieMatching(*cookie));
+
   // Cookies whose names mismatch are not equivalent.
   other_cookie = CanonicalCookie::Create(
-      url, "B", cookie_value, cookie_domain, cookie_path, creation_time,
-      expiration_time, secure, httponly, same_site, COOKIE_PRIORITY_MEDIUM);
+      "B", cookie_value, cookie_domain, cookie_path, creation_time,
+      expiration_time, base::Time(), secure, httponly, same_site,
+      COOKIE_PRIORITY_MEDIUM);
   EXPECT_FALSE(cookie->IsEquivalent(*other_cookie));
   EXPECT_FALSE(cookie->IsEquivalentForSecureCookieMatching(*other_cookie));
   EXPECT_FALSE(other_cookie->IsEquivalentForSecureCookieMatching(*cookie));
@@ -242,8 +252,9 @@
   // at the same domain. These are, however, equivalent according to the laxer
   // rules of 'IsEquivalentForSecureCookieMatching'.
   other_cookie = CanonicalCookie::Create(
-      url, cookie_name, cookie_value, std::string(), cookie_path, creation_time,
-      expiration_time, secure, httponly, same_site, COOKIE_PRIORITY_MEDIUM);
+      cookie_name, cookie_value, "www.example.com", cookie_path, creation_time,
+      expiration_time, base::Time(), secure, httponly, same_site,
+      COOKIE_PRIORITY_MEDIUM);
   EXPECT_TRUE(cookie->IsDomainCookie());
   EXPECT_FALSE(other_cookie->IsDomainCookie());
   EXPECT_FALSE(cookie->IsEquivalent(*other_cookie));
@@ -253,8 +264,8 @@
   // Likewise, a cookie on 'example.com' is not equivalent to a cookie on
   // 'www.example.com', but they are equivalent for secure cookie matching.
   other_cookie = CanonicalCookie::Create(
-      url, cookie_name, cookie_value, ".example.com", cookie_path,
-      creation_time, expiration_time, secure, httponly, same_site,
+      cookie_name, cookie_value, ".example.com", cookie_path, creation_time,
+      expiration_time, base::Time(), secure, httponly, same_site,
       COOKIE_PRIORITY_MEDIUM);
   EXPECT_FALSE(cookie->IsEquivalent(*other_cookie));
   EXPECT_TRUE(cookie->IsEquivalentForSecureCookieMatching(*other_cookie));
@@ -266,23 +277,25 @@
   // |other_cookie| set on '/test' or '/path/subpath'. It is, however,
   // equivalent for secure cookie matching to |other_cookie| set on '/'.
   other_cookie = CanonicalCookie::Create(
-      url, cookie_name, cookie_value, cookie_domain, "/test", creation_time,
-      expiration_time, secure, httponly, same_site, COOKIE_PRIORITY_MEDIUM);
+      cookie_name, cookie_value, cookie_domain, "/test", creation_time,
+      expiration_time, base::Time(), secure, httponly, same_site,
+      COOKIE_PRIORITY_MEDIUM);
   EXPECT_FALSE(cookie->IsEquivalent(*other_cookie));
   EXPECT_FALSE(cookie->IsEquivalentForSecureCookieMatching(*other_cookie));
   EXPECT_FALSE(other_cookie->IsEquivalentForSecureCookieMatching(*cookie));
 
   other_cookie = CanonicalCookie::Create(
-      url, cookie_name, cookie_value, cookie_domain, cookie_path + "/subpath",
-      creation_time, expiration_time, secure, httponly, same_site,
+      cookie_name, cookie_value, cookie_domain, cookie_path + "/subpath",
+      creation_time, expiration_time, base::Time(), secure, httponly, same_site,
       COOKIE_PRIORITY_MEDIUM);
   EXPECT_FALSE(cookie->IsEquivalent(*other_cookie));
   EXPECT_FALSE(cookie->IsEquivalentForSecureCookieMatching(*other_cookie));
   EXPECT_TRUE(other_cookie->IsEquivalentForSecureCookieMatching(*cookie));
 
   other_cookie = CanonicalCookie::Create(
-      url, cookie_name, cookie_value, cookie_domain, "/", creation_time,
-      expiration_time, secure, httponly, same_site, COOKIE_PRIORITY_MEDIUM);
+      cookie_name, cookie_value, cookie_domain, "/", creation_time,
+      expiration_time, base::Time(), secure, httponly, same_site,
+      COOKIE_PRIORITY_MEDIUM);
   EXPECT_FALSE(cookie->IsEquivalent(*other_cookie));
   EXPECT_TRUE(cookie->IsEquivalentForSecureCookieMatching(*other_cookie));
   EXPECT_FALSE(other_cookie->IsEquivalentForSecureCookieMatching(*cookie));
@@ -579,28 +592,6 @@
   EXPECT_FALSE(http_cookie_secure.get());
   EXPECT_TRUE(https_cookie_no_secure.get());
   EXPECT_TRUE(https_cookie_secure.get());
-
-  std::unique_ptr<CanonicalCookie> http_cookie_no_secure_extended(
-      CanonicalCookie::Create(
-          http_url, "a", "b", "", "", creation_time, creation_time, false,
-          false, CookieSameSite::STRICT_MODE, COOKIE_PRIORITY_DEFAULT));
-  std::unique_ptr<CanonicalCookie> http_cookie_secure_extended(
-      CanonicalCookie::Create(
-          http_url, "a", "b", "", "", creation_time, creation_time, true, false,
-          CookieSameSite::STRICT_MODE, COOKIE_PRIORITY_DEFAULT));
-  std::unique_ptr<CanonicalCookie> https_cookie_no_secure_extended(
-      CanonicalCookie::Create(
-          https_url, "a", "b", "", "", creation_time, creation_time, false,
-          false, CookieSameSite::STRICT_MODE, COOKIE_PRIORITY_DEFAULT));
-  std::unique_ptr<CanonicalCookie> https_cookie_secure_extended(
-      CanonicalCookie::Create(
-          https_url, "a", "b", "", "", creation_time, creation_time, true,
-          false, CookieSameSite::STRICT_MODE, COOKIE_PRIORITY_DEFAULT));
-
-  EXPECT_TRUE(http_cookie_no_secure_extended.get());
-  EXPECT_FALSE(http_cookie_secure_extended.get());
-  EXPECT_TRUE(https_cookie_no_secure_extended.get());
-  EXPECT_TRUE(https_cookie_secure_extended.get());
 }
 
 TEST(CanonicalCookieTest, TestPrefixHistograms) {
diff --git a/net/cookies/cookie_monster.cc b/net/cookies/cookie_monster.cc
index 2272d7b0..146e8acd 100644
--- a/net/cookies/cookie_monster.cc
+++ b/net/cookies/cookie_monster.cc
@@ -1058,16 +1058,43 @@
     last_time_seen_ = actual_creation_time;
   }
 
+  // Validate consistency of passed arguments.
+  if (ParsedCookie::ParseTokenString(name) != name ||
+      ParsedCookie::ParseValueString(value) != value ||
+      ParsedCookie::ParseValueString(domain) != domain ||
+      ParsedCookie::ParseValueString(path) != path) {
+    return false;
+  }
+
+  // Validate passed arguments against URL.
+  if (secure && !url.SchemeIsCryptographic())
+    return false;
+
+  std::string cookie_domain;
+  if (!cookie_util::GetCookieDomainWithString(url, domain, &cookie_domain))
+    return false;
+
+  std::string cookie_path = CanonicalCookie::CanonPathWithString(url, path);
+  if (!path.empty() && cookie_path != path)
+    return false;
+
+  // Canonicalize path again to make sure it escapes characters as needed.
+  url::Component path_component(0, cookie_path.length());
+  url::RawCanonOutputT<char> canon_path;
+  url::Component canon_path_component;
+  url::CanonicalizePath(cookie_path.data(), path_component, &canon_path,
+                        &canon_path_component);
+  cookie_path = std::string(canon_path.data() + canon_path_component.begin,
+                            canon_path_component.len);
+
   std::unique_ptr<CanonicalCookie> cc(CanonicalCookie::Create(
-      url, name, value, domain, path, actual_creation_time, expiration_time,
-      secure, http_only, same_site, priority));
+      name, value, cookie_domain, cookie_path, actual_creation_time,
+      expiration_time, last_access_time, secure, http_only, same_site,
+      priority));
 
   if (!cc.get())
     return false;
 
-  if (!last_access_time.is_null())
-    cc->SetLastAccessDate(last_access_time);
-
   CookieOptions options;
   options.set_include_httponly();
   options.set_same_site_cookie_mode(
diff --git a/net/cookies/cookie_monster_store_test.cc b/net/cookies/cookie_monster_store_test.cc
index 558d4bc..34fb81db 100644
--- a/net/cookies/cookie_monster_store_test.cc
+++ b/net/cookies/cookie_monster_store_test.cc
@@ -130,10 +130,10 @@
                       : base::Time();
   std::string cookie_path = pc.Path();
 
-  return CanonicalCookie::Create(url, pc.Name(), pc.Value(), url.host(),
+  return CanonicalCookie::Create(pc.Name(), pc.Value(), "." + url.host(),
                                  cookie_path, creation_time, cookie_expires,
-                                 pc.IsSecure(), pc.IsHttpOnly(), pc.SameSite(),
-                                 pc.Priority());
+                                 base::Time(), pc.IsSecure(), pc.IsHttpOnly(),
+                                 pc.SameSite(), pc.Priority());
 }
 
 void AddCookieToList(const GURL& url,
@@ -239,8 +239,8 @@
     // strict secure cookies are enforced, the cookie will fail to be created if
     // |secure| is true but the URL is an insecure scheme.
     std::unique_ptr<CanonicalCookie> cc(CanonicalCookie::Create(
-        GURL(base::StringPrintf("https://h%05d.izzle/", i)), "a", "1",
-        std::string(), "/path", creation_time, expiration_time, secure, false,
+        "a", "1", base::StringPrintf("h%05d.izzle", i), "/path", creation_time,
+        expiration_time, base::Time(), secure, false,
         CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_DEFAULT));
     cc->SetLastAccessDate(last_access_time);
     store->AddCookie(*cc);
diff --git a/net/cookies/cookie_monster_unittest.cc b/net/cookies/cookie_monster_unittest.cc
index 706c75f..b172d6b 100644
--- a/net/cookies/cookie_monster_unittest.cc
+++ b/net/cookies/cookie_monster_unittest.cc
@@ -1040,13 +1040,13 @@
   MockSetCookiesCallback set_cookies_callback;
   CookieList list;
   list.push_back(*CanonicalCookie::Create(
-      http_www_google_.url(), "A", "B", http_www_google_.domain(), "/",
-      base::Time::Now(), base::Time(), false, true,
-      CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_DEFAULT));
+      "A", "B", "." + http_www_google_.domain(), "/", base::Time::Now(),
+      base::Time(), base::Time(), false, true, CookieSameSite::DEFAULT_MODE,
+      COOKIE_PRIORITY_DEFAULT));
   list.push_back(*CanonicalCookie::Create(
-      http_www_google_.url(), "C", "D", http_www_google_.domain(), "/",
-      base::Time::Now(), base::Time(), false, true,
-      CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_DEFAULT));
+      "C", "D", "." + http_www_google_.domain(), "/", base::Time::Now(),
+      base::Time(), base::Time(), false, true, CookieSameSite::DEFAULT_MODE,
+      COOKIE_PRIORITY_DEFAULT));
 
   BeginWith(
       SetAllCookiesAction(&cookie_monster(), list, &set_cookies_callback));
@@ -2534,17 +2534,17 @@
 
   CookieList list;
   list.push_back(*CanonicalCookie::Create(
-      http_www_google_.url(), "A", "B", http_www_google_.url().host(), "/",
-      base::Time::Now(), base::Time(), false, false,
-      CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_DEFAULT));
+      "A", "B", "." + http_www_google_.url().host(), "/", base::Time::Now(),
+      base::Time(), base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
+      COOKIE_PRIORITY_DEFAULT));
   list.push_back(*CanonicalCookie::Create(
-      http_www_google_.url(), "W", "X", http_www_google_.url().host(), "/bar",
-      base::Time::Now(), base::Time(), false, false,
-      CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_DEFAULT));
+      "W", "X", "." + http_www_google_.url().host(), "/bar", base::Time::Now(),
+      base::Time(), base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
+      COOKIE_PRIORITY_DEFAULT));
   list.push_back(*CanonicalCookie::Create(
-      http_www_google_.url(), "Y", "Z", http_www_google_.url().host(), "/",
-      base::Time::Now(), base::Time(), false, false,
-      CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_DEFAULT));
+      "Y", "Z", "." + http_www_google_.url().host(), "/", base::Time::Now(),
+      base::Time(), base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
+      COOKIE_PRIORITY_DEFAULT));
 
   // SetAllCookies must not flush.
   ASSERT_EQ(0, store->flush_count());
@@ -2577,53 +2577,53 @@
   base::Time creation_time = now - base::TimeDelta::FromSeconds(1);
 
   std::unique_ptr<CanonicalCookie> cookie1(CanonicalCookie::Create(
-      http_www_google_.url(), "A", "B", http_www_google_.url().host(), "/",
-      creation_time, base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
+      "A", "B", "." + http_www_google_.url().host(), "/", creation_time,
+      base::Time(), base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
       COOKIE_PRIORITY_DEFAULT));
   std::unique_ptr<CanonicalCookie> cookie2(CanonicalCookie::Create(
-      http_www_google_.url(), "C", "D", http_www_google_.url().host(), "/",
-      creation_time, base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
+      "C", "D", "." + http_www_google_.url().host(), "/", creation_time,
+      base::Time(), base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
       COOKIE_PRIORITY_DEFAULT));
   std::unique_ptr<CanonicalCookie> cookie3(CanonicalCookie::Create(
-      http_www_google_.url(), "E", "F", http_www_google_.url().host(), "/",
-      creation_time, base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
+      "E", "F", "." + http_www_google_.url().host(), "/", creation_time,
+      base::Time(), base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
       COOKIE_PRIORITY_DEFAULT));
   std::unique_ptr<CanonicalCookie> cookie4(CanonicalCookie::Create(
-      http_www_google_.url(), "G", "H", http_www_google_.url().host(), "/",
-      creation_time, base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
+      "G", "H", "." + http_www_google_.url().host(), "/", creation_time,
+      base::Time(), base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
       COOKIE_PRIORITY_DEFAULT));
   std::unique_ptr<CanonicalCookie> cookie4_with_new_value(
       CanonicalCookie::Create(
-          http_www_google_.url(), "G", "iamnew", http_www_google_.url().host(),
-          "/", creation_time, base::Time(), false, false,
+          "G", "iamnew", "." + http_www_google_.url().host(), "/",
+          creation_time, base::Time(), base::Time(), false, false,
           CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_DEFAULT));
   std::unique_ptr<CanonicalCookie> cookie5(CanonicalCookie::Create(
-      http_www_google_.url(), "I", "J", http_www_google_.url().host(), "/",
-      creation_time, base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
+      "I", "J", "." + http_www_google_.url().host(), "/", creation_time,
+      base::Time(), base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
       COOKIE_PRIORITY_DEFAULT));
   std::unique_ptr<CanonicalCookie> cookie5_with_new_creation_time(
-      CanonicalCookie::Create(
-          http_www_google_.url(), "I", "J", http_www_google_.url().host(), "/",
-          now, base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
-          COOKIE_PRIORITY_DEFAULT));
+      CanonicalCookie::Create("I", "J", "." + http_www_google_.url().host(),
+                              "/", now, base::Time(), base::Time(), false,
+                              false, CookieSameSite::DEFAULT_MODE,
+                              COOKIE_PRIORITY_DEFAULT));
   std::unique_ptr<CanonicalCookie> cookie6(CanonicalCookie::Create(
-      http_www_google_.url(), "K", "L", http_www_google_.url().host(), "/foo",
-      creation_time, base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
+      "K", "L", "." + http_www_google_.url().host(), "/foo", creation_time,
+      base::Time(), base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
       COOKIE_PRIORITY_DEFAULT));
   std::unique_ptr<CanonicalCookie> cookie6_with_new_path(
-      CanonicalCookie::Create(
-          http_www_google_.url(), "K", "L", http_www_google_.url().host(),
-          "/bar", creation_time, base::Time(), false, false,
-          CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_DEFAULT));
+      CanonicalCookie::Create("K", "L", "." + http_www_google_.url().host(),
+                              "/bar", creation_time, base::Time(), base::Time(),
+                              false, false, CookieSameSite::DEFAULT_MODE,
+                              COOKIE_PRIORITY_DEFAULT));
   std::unique_ptr<CanonicalCookie> cookie7(CanonicalCookie::Create(
-      http_www_google_.url(), "M", "N", http_www_google_.url().host(), "/foo",
-      creation_time, base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
+      "M", "N", "." + http_www_google_.url().host(), "/foo", creation_time,
+      base::Time(), base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
       COOKIE_PRIORITY_DEFAULT));
   std::unique_ptr<CanonicalCookie> cookie7_with_new_path(
-      CanonicalCookie::Create(
-          http_www_google_.url(), "M", "N", http_www_google_.url().host(),
-          "/bar", creation_time, base::Time(), false, false,
-          CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_DEFAULT));
+      CanonicalCookie::Create("M", "N", "." + http_www_google_.url().host(),
+                              "/bar", creation_time, base::Time(), base::Time(),
+                              false, false, CookieSameSite::DEFAULT_MODE,
+                              COOKIE_PRIORITY_DEFAULT));
 
   CookieList old_cookies;
   old_cookies.push_back(*cookie1);
@@ -2836,11 +2836,11 @@
   // We have to manually build this cookie because it contains a control
   // character, and our cookie line parser rejects control characters.
   std::unique_ptr<CanonicalCookie> cc = CanonicalCookie::Create(
-      url, "baz",
+      "baz",
       "\x05"
       "boo",
-      domain, path, now2, later, false, false, CookieSameSite::DEFAULT_MODE,
-      COOKIE_PRIORITY_DEFAULT);
+      "." + domain, path, now2, later, base::Time(), false, false,
+      CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_DEFAULT);
   initial_cookies.push_back(std::move(cc));
 
   AddCookieToList(url, "hello=world; path=" + path, now3, &initial_cookies);
diff --git a/net/cookies/cookie_store_unittest.h b/net/cookies/cookie_store_unittest.h
index 48d9d8c5..e8ac9882 100644
--- a/net/cookies/cookie_store_unittest.h
+++ b/net/cookies/cookie_store_unittest.h
@@ -465,6 +465,34 @@
   EXPECT_TRUE(++it == cookies.end());
 }
 
+// Test enforcement around setting secure cookies.
+TYPED_TEST_P(CookieStoreTest, SetCookieWithDetailsSecureEnforcement) {
+  CookieStore* cs = this->GetCookieStore();
+  GURL http_url(this->http_www_google_.url());
+  std::string http_domain(http_url.host());
+  GURL https_url(this->https_www_google_.url());
+  std::string https_domain(https_url.host());
+
+  // Confirm that setting the secure attribute on an HTTP URL fails, but
+  // the other combinations work.
+  EXPECT_TRUE(this->SetCookieWithDetails(
+      cs, http_url, "A", "B", http_domain, "/", base::Time::Now(), base::Time(),
+      base::Time(), false, false, CookieSameSite::NO_RESTRICTION,
+      COOKIE_PRIORITY_DEFAULT));
+  EXPECT_FALSE(this->SetCookieWithDetails(
+      cs, http_url, "A", "B", http_domain, "/", base::Time::Now(), base::Time(),
+      base::Time(), true, false, CookieSameSite::NO_RESTRICTION,
+      COOKIE_PRIORITY_DEFAULT));
+  EXPECT_TRUE(this->SetCookieWithDetails(
+      cs, https_url, "A", "B", https_domain, "/", base::Time::Now(),
+      base::Time(), base::Time(), false, false, CookieSameSite::NO_RESTRICTION,
+      COOKIE_PRIORITY_DEFAULT));
+  EXPECT_TRUE(this->SetCookieWithDetails(
+      cs, https_url, "A", "B", https_domain, "/", base::Time::Now(),
+      base::Time(), base::Time(), true, false, CookieSameSite::NO_RESTRICTION,
+      COOKIE_PRIORITY_DEFAULT));
+}
+
 // The iOS networking stack uses the iOS cookie parser, which we do not
 // control. While it is spec-compliant, that does not match the practical
 // behavior of most UAs in some cases, which we try to replicate. See
@@ -1423,6 +1451,7 @@
 
 REGISTER_TYPED_TEST_CASE_P(CookieStoreTest,
                            SetCookieWithDetailsAsync,
+                           SetCookieWithDetailsSecureEnforcement,
                            EmptyKeyTest,
                            DomainTest,
                            DomainWithTrailingDotTest,
diff --git a/net/disk_cache/disk_cache_perftest.cc b/net/disk_cache/disk_cache_perftest.cc
index e16cccb..5151d34 100644
--- a/net/disk_cache/disk_cache_perftest.cc
+++ b/net/disk_cache/disk_cache_perftest.cc
@@ -15,6 +15,7 @@
 #include "base/run_loop.h"
 #include "base/strings/string_util.h"
 #include "base/test/perf_time_logger.h"
+#include "base/test/scoped_task_environment.h"
 #include "base/test/test_file_util.h"
 #include "base/threading/thread.h"
 #include "net/base/cache_type.h"
@@ -91,6 +92,7 @@
 
  private:
   const size_t saved_fd_limit_;
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
 };
 
 // Creates num_entries on the cache, and writes kHeaderSize bytes of metadata
diff --git a/net/extras/sqlite/sqlite_persistent_cookie_store_perftest.cc b/net/extras/sqlite/sqlite_persistent_cookie_store_perftest.cc
index f73ba2f8..e9b0ef2 100644
--- a/net/extras/sqlite/sqlite_persistent_cookie_store_perftest.cc
+++ b/net/extras/sqlite/sqlite_persistent_cookie_store_perftest.cc
@@ -77,12 +77,11 @@
     base::Time t = base::Time::Now();
     for (int domain_num = 0; domain_num < 300; domain_num++) {
       std::string domain_name(base::StringPrintf(".domain_%d.com", domain_num));
-      GURL gurl("http://www" + domain_name);
       for (int cookie_num = 0; cookie_num < 50; ++cookie_num) {
         t += base::TimeDelta::FromInternalValue(10);
         store_->AddCookie(*CanonicalCookie::Create(
-            gurl, base::StringPrintf("Cookie_%d", cookie_num), "1", domain_name,
-            "/", t, t, false, false, CookieSameSite::DEFAULT_MODE,
+            base::StringPrintf("Cookie_%d", cookie_num), "1", domain_name, "/",
+            t, t, t, false, false, CookieSameSite::DEFAULT_MODE,
             COOKIE_PRIORITY_DEFAULT));
       }
     }
diff --git a/net/extras/sqlite/sqlite_persistent_cookie_store_unittest.cc b/net/extras/sqlite/sqlite_persistent_cookie_store_unittest.cc
index 2c9b7a1..b70877d 100644
--- a/net/extras/sqlite/sqlite_persistent_cookie_store_unittest.cc
+++ b/net/extras/sqlite/sqlite_persistent_cookie_store_unittest.cc
@@ -167,27 +167,25 @@
   void WaitOnDBEvent() { db_thread_event_.Wait(); }
 
   // Adds a persistent cookie to store_.
-  void AddCookie(const GURL& url,
-                 const std::string& name,
+  void AddCookie(const std::string& name,
                  const std::string& value,
                  const std::string& domain,
                  const std::string& path,
                  const base::Time& creation) {
     store_->AddCookie(*CanonicalCookie::Create(
-        url, name, value, domain, path, creation, creation, false, false,
-        CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_DEFAULT));
+        name, value, domain, path, creation, creation, base::Time(), false,
+        false, CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_DEFAULT));
   }
 
-  void AddCookieWithExpiration(const GURL& url,
-                               const std::string& name,
+  void AddCookieWithExpiration(const std::string& name,
                                const std::string& value,
                                const std::string& domain,
                                const std::string& path,
                                const base::Time& creation,
                                const base::Time& expiration) {
     store_->AddCookie(*CanonicalCookie::Create(
-        url, name, value, domain, path, creation, expiration, false, false,
-        CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_DEFAULT));
+        name, value, domain, path, creation, expiration, base::Time(), false,
+        false, CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_DEFAULT));
   }
 
   std::string ReadRawDBContents() {
@@ -217,8 +215,7 @@
 
 TEST_F(SQLitePersistentCookieStoreTest, TestInvalidMetaTableRecovery) {
   InitializeStore(false, false);
-  AddCookie(GURL("http://foo.bar"), "A", "B", std::string(), "/",
-            base::Time::Now());
+  AddCookie("A", "B", "foo.bar", "/", base::Time::Now());
   DestroyStore();
 
   // Load up the store and verify that it has good data in it.
@@ -246,8 +243,7 @@
   ASSERT_EQ(0U, cookies.size());
 
   // Verify that, after, recovery, the database persists properly.
-  AddCookie(GURL("http://foo.bar"), "X", "Y", std::string(), "/",
-            base::Time::Now());
+  AddCookie("X", "Y", "foo.bar", "/", base::Time::Now());
   DestroyStore();
   CreateAndLoad(false, false, &cookies);
   ASSERT_EQ(1U, cookies.size());
@@ -260,8 +256,7 @@
 // Test if data is stored as expected in the SQLite database.
 TEST_F(SQLitePersistentCookieStoreTest, TestPersistance) {
   InitializeStore(false, false);
-  AddCookie(GURL("http://foo.bar"), "A", "B", std::string(), "/",
-            base::Time::Now());
+  AddCookie("A", "B", "foo.bar", "/", base::Time::Now());
   // Replace the store effectively destroying the current one and forcing it
   // to write its data to disk. Then we can see if after loading it again it
   // is still there.
@@ -291,28 +286,23 @@
 
   // Add persistent cookies.
   base::Time t = base::Time::Now();
-  AddCookie(GURL("http://a1.com"), "A", "B", std::string(), "/", t);
+  AddCookie("A", "B", "a1.com", "/", t);
   t += base::TimeDelta::FromInternalValue(10);
-  AddCookie(GURL("http://a2.com"), "A", "B", std::string(), "/", t);
+  AddCookie("A", "B", "a2.com", "/", t);
   t += base::TimeDelta::FromInternalValue(10);
-  AddCookie(GURL("http://a3.com"), "A", "B", std::string(), "/", t);
+  AddCookie("A", "B", "a3.com", "/", t);
 
   // Add transient cookies.
   t += base::TimeDelta::FromInternalValue(10);
-  AddCookieWithExpiration(GURL("http://b1.com"), "A", "B", std::string(), "/",
-                          t, base::Time());
+  AddCookieWithExpiration("A", "B", "b1.com", "/", t, base::Time());
   t += base::TimeDelta::FromInternalValue(10);
-  AddCookieWithExpiration(GURL("http://b2.com"), "A", "B", std::string(), "/",
-                          t, base::Time());
+  AddCookieWithExpiration("A", "B", "b2.com", "/", t, base::Time());
   t += base::TimeDelta::FromInternalValue(10);
-  AddCookieWithExpiration(GURL("http://b3.com"), "A", "B", std::string(), "/",
-                          t, base::Time());
+  AddCookieWithExpiration("A", "B", "b3.com", "/", t, base::Time());
   t += base::TimeDelta::FromInternalValue(10);
-  AddCookieWithExpiration(GURL("http://b4.com"), "A", "B", std::string(), "/",
-                          t, base::Time());
+  AddCookieWithExpiration("A", "B", "b4.com", "/", t, base::Time());
   t += base::TimeDelta::FromInternalValue(10);
-  AddCookieWithExpiration(GURL("http://b5.com"), "A", "B", std::string(), "/",
-                          t, base::Time());
+  AddCookieWithExpiration("A", "B", "b5.com", "/", t, base::Time());
   DestroyStore();
 
   // Load the store a second time. Before the store finishes loading, add a
@@ -329,8 +319,7 @@
   store_->Load(base::Bind(&SQLitePersistentCookieStoreTest::OnLoaded,
                           base::Unretained(this)));
   t += base::TimeDelta::FromInternalValue(10);
-  AddCookieWithExpiration(GURL("http://c.com"), "A", "B", std::string(), "/", t,
-                          base::Time());
+  AddCookieWithExpiration("A", "B", "c.com", "/", t, base::Time());
   base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
                             base::WaitableEvent::InitialState::NOT_SIGNALED);
   store_->Flush(
@@ -367,13 +356,13 @@
 TEST_F(SQLitePersistentCookieStoreTest, TestLoadCookiesForKey) {
   InitializeStore(false, false);
   base::Time t = base::Time::Now();
-  AddCookie(GURL("http://foo.bar"), "A", "B", std::string(), "/", t);
+  AddCookie("A", "B", "foo.bar", "/", t);
   t += base::TimeDelta::FromInternalValue(10);
-  AddCookie(GURL("http://www.aaa.com"), "A", "B", std::string(), "/", t);
+  AddCookie("A", "B", "www.aaa.com", "/", t);
   t += base::TimeDelta::FromInternalValue(10);
-  AddCookie(GURL("http://travel.aaa.com"), "A", "B", std::string(), "/", t);
+  AddCookie("A", "B", "travel.aaa.com", "/", t);
   t += base::TimeDelta::FromInternalValue(10);
-  AddCookie(GURL("http://www.bbb.com"), "A", "B", std::string(), "/", t);
+  AddCookie("A", "B", "www.bbb.com", "/", t);
   DestroyStore();
 
   store_ = new SQLitePersistentCookieStore(
@@ -443,7 +432,7 @@
     base::Time t = base::Time::Now() + base::TimeDelta::FromMicroseconds(c);
     std::string name(1, c);
     std::string value(1000, c);
-    AddCookie(GURL("http://foo.bar"), name, value, std::string(), "/", t);
+    AddCookie(name, value, "foo.bar", "/", t);
   }
 
   Flush();
@@ -459,9 +448,9 @@
 
   // Add a session cookie.
   store_->AddCookie(*CanonicalCookie::Create(
-      GURL("http://sessioncookie.com"), "C", "D", std::string(), "/",
-      base::Time::Now(), base::Time(), false, false,
-      CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_DEFAULT));
+      "C", "D", "sessioncookie.com", "/", base::Time::Now(), base::Time(),
+      base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
+      COOKIE_PRIORITY_DEFAULT));
 
   // Force the store to write its data to the disk.
   DestroyStore();
@@ -486,9 +475,9 @@
 
   // Add a session cookie.
   store_->AddCookie(*CanonicalCookie::Create(
-      GURL("http://sessioncookie.com"), "C", "D", std::string(), "/",
-      base::Time::Now(), base::Time(), false, false,
-      CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_DEFAULT));
+      "C", "D", "sessioncookie.com", "/", base::Time::Now(), base::Time(),
+      base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
+      COOKIE_PRIORITY_DEFAULT));
 
   // Force the store to write its data to the disk.
   DestroyStore();
@@ -516,15 +505,15 @@
 
   // Add a session cookie.
   store_->AddCookie(*CanonicalCookie::Create(
-      GURL("http://sessioncookie.com"), kSessionName, "val", std::string(), "/",
-      base::Time::Now(), base::Time(), false, false,
-      CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_DEFAULT));
+      kSessionName, "val", "sessioncookie.com", "/", base::Time::Now(),
+      base::Time(), base::Time(), false, false, CookieSameSite::DEFAULT_MODE,
+      COOKIE_PRIORITY_DEFAULT));
   // Add a persistent cookie.
   store_->AddCookie(*CanonicalCookie::Create(
-      GURL("http://sessioncookie.com"), kPersistentName, "val", std::string(),
-      "/", base::Time::Now() - base::TimeDelta::FromDays(1),
-      base::Time::Now() + base::TimeDelta::FromDays(1), false, false,
-      CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_DEFAULT));
+      kPersistentName, "val", "sessioncookie.com", "/",
+      base::Time::Now() - base::TimeDelta::FromDays(1),
+      base::Time::Now() + base::TimeDelta::FromDays(1), base::Time(), false,
+      false, CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_DEFAULT));
 
   // Force the store to write its data to the disk.
   DestroyStore();
@@ -551,7 +540,7 @@
 }
 
 TEST_F(SQLitePersistentCookieStoreTest, PriorityIsPersistent) {
-  static const char kURL[] = "http://sessioncookie.com";
+  static const char kDomain[] = "sessioncookie.com";
   static const char kLowName[] = "low";
   static const char kMediumName[] = "medium";
   static const char kHighName[] = "high";
@@ -562,24 +551,24 @@
 
   // Add a low-priority persistent cookie.
   store_->AddCookie(*CanonicalCookie::Create(
-      GURL(kURL), kLowName, kCookieValue, std::string(), kCookiePath,
+      kLowName, kCookieValue, kDomain, kCookiePath,
       base::Time::Now() - base::TimeDelta::FromMinutes(1),
-      base::Time::Now() + base::TimeDelta::FromDays(1), false, false,
-      CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_LOW));
+      base::Time::Now() + base::TimeDelta::FromDays(1), base::Time(), false,
+      false, CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_LOW));
 
   // Add a medium-priority persistent cookie.
   store_->AddCookie(*CanonicalCookie::Create(
-      GURL(kURL), kMediumName, kCookieValue, std::string(), kCookiePath,
+      kMediumName, kCookieValue, kDomain, kCookiePath,
       base::Time::Now() - base::TimeDelta::FromMinutes(2),
-      base::Time::Now() + base::TimeDelta::FromDays(1), false, false,
-      CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_MEDIUM));
+      base::Time::Now() + base::TimeDelta::FromDays(1), base::Time(), false,
+      false, CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_MEDIUM));
 
   // Add a high-priority peristent cookie.
   store_->AddCookie(*CanonicalCookie::Create(
-      GURL(kURL), kHighName, kCookieValue, std::string(), kCookiePath,
+      kHighName, kCookieValue, kDomain, kCookiePath,
       base::Time::Now() - base::TimeDelta::FromMinutes(3),
-      base::Time::Now() + base::TimeDelta::FromDays(1), false, false,
-      CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_HIGH));
+      base::Time::Now() + base::TimeDelta::FromDays(1), base::Time(), false,
+      false, CookieSameSite::DEFAULT_MODE, COOKIE_PRIORITY_HIGH));
 
   // Force the store to write its data to the disk.
   DestroyStore();
@@ -612,7 +601,7 @@
 }
 
 TEST_F(SQLitePersistentCookieStoreTest, SameSiteIsPersistent) {
-  const char kURL[] = "http://sessioncookie.com";
+  const char kDomain[] = "sessioncookie.com";
   const char kNoneName[] = "none";
   const char kLaxName[] = "lax";
   const char kStrictName[] = "strict";
@@ -623,24 +612,24 @@
 
   // Add a non-samesite cookie.
   store_->AddCookie(*CanonicalCookie::Create(
-      GURL(kURL), kNoneName, kCookieValue, std::string(), kCookiePath,
+      kNoneName, kCookieValue, kDomain, kCookiePath,
       base::Time::Now() - base::TimeDelta::FromMinutes(1),
-      base::Time::Now() + base::TimeDelta::FromDays(1), false, false,
-      CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT));
+      base::Time::Now() + base::TimeDelta::FromDays(1), base::Time(), false,
+      false, CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT));
 
   // Add a lax-samesite persistent cookie.
   store_->AddCookie(*CanonicalCookie::Create(
-      GURL(kURL), kLaxName, kCookieValue, std::string(), kCookiePath,
+      kLaxName, kCookieValue, kDomain, kCookiePath,
       base::Time::Now() - base::TimeDelta::FromMinutes(2),
-      base::Time::Now() + base::TimeDelta::FromDays(1), false, false,
-      CookieSameSite::LAX_MODE, COOKIE_PRIORITY_DEFAULT));
+      base::Time::Now() + base::TimeDelta::FromDays(1), base::Time(), false,
+      false, CookieSameSite::LAX_MODE, COOKIE_PRIORITY_DEFAULT));
 
   // Add a strict-samesite persistent cookie.
   store_->AddCookie(*CanonicalCookie::Create(
-      GURL(kURL), kStrictName, kCookieValue, std::string(), kCookiePath,
+      kStrictName, kCookieValue, kDomain, kCookiePath,
       base::Time::Now() - base::TimeDelta::FromMinutes(3),
-      base::Time::Now() + base::TimeDelta::FromDays(1), false, false,
-      CookieSameSite::STRICT_MODE, COOKIE_PRIORITY_DEFAULT));
+      base::Time::Now() + base::TimeDelta::FromDays(1), base::Time(), false,
+      false, CookieSameSite::STRICT_MODE, COOKIE_PRIORITY_DEFAULT));
 
   // Force the store to write its data to the disk.
   DestroyStore();
@@ -674,8 +663,7 @@
 
   // Create unencrypted cookie store and write something to it.
   InitializeStore(false, false);
-  AddCookie(GURL("http://foo.bar"), "name", "value123XYZ", std::string(), "/",
-            base::Time::Now());
+  AddCookie("name", "value123XYZ", "foo.bar", "/", base::Time::Now());
   DestroyStore();
 
   // Verify that "value" is visible in the file.  This is necessary in order to
@@ -694,10 +682,9 @@
 
   // Make sure we can update existing cookie and add new cookie as encrypted.
   store_->DeleteCookie(*(cookies[0]));
-  AddCookie(GURL("http://foo.bar"), "name", "encrypted_value123XYZ",
-            std::string(), "/", base::Time::Now());
-  AddCookie(GURL("http://foo.bar"), "other", "something456ABC", std::string(),
-            "/", base::Time::Now() + base::TimeDelta::FromInternalValue(10));
+  AddCookie("name", "encrypted_value123XYZ", "foo.bar", "/", base::Time::Now());
+  AddCookie("other", "something456ABC", "foo.bar", "/",
+            base::Time::Now() + base::TimeDelta::FromInternalValue(10));
   DestroyStore();
   cookies.clear();
   CreateAndLoad(true, false, &cookies);
@@ -746,8 +733,7 @@
 
   // Create unencrypted cookie store and write something to it.
   InitializeStore(true, false);
-  AddCookie(GURL("http://foo.bar"), "name", "value123XYZ", std::string(), "/",
-            base::Time::Now());
+  AddCookie("name", "value123XYZ", "foo.bar", "/", base::Time::Now());
   DestroyStore();
 
   // Verify that "value" is not visible in the file.
@@ -766,10 +752,9 @@
   // Make sure we can update existing cookie and it writes unencrypted.
   cookie_crypto_delegate_->should_encrypt_ = false;
   store_->DeleteCookie(*(cookies[0]));
-  AddCookie(GURL("http://foo.bar"), "name", "plaintext_value123XYZ",
-            std::string(), "/", base::Time::Now());
-  AddCookie(GURL("http://foo.bar"), "other", "something456ABC", std::string(),
-            "/", base::Time::Now() + base::TimeDelta::FromInternalValue(10));
+  AddCookie("name", "plaintext_value123XYZ", "foo.bar", "/", base::Time::Now());
+  AddCookie("other", "something456ABC", "foo.bar", "/",
+            base::Time::Now() + base::TimeDelta::FromInternalValue(10));
   DestroyStore();
   cookies.clear();
   CreateAndLoad(true, false, &cookies);
@@ -805,8 +790,7 @@
 TEST_F(SQLitePersistentCookieStoreTest, EmptyLoadAfterClose) {
   // Create unencrypted cookie store and write something to it.
   InitializeStore(false, false);
-  AddCookie(GURL("http://foo.bar"), "name", "value123XYZ", std::string(), "/",
-            base::Time::Now());
+  AddCookie("name", "value123XYZ", "foo.bar", "/", base::Time::Now());
   DestroyStore();
 
   // Create the cookie store, but immediately close it.
diff --git a/net/spdy/core/spdy_framer.cc b/net/spdy/core/spdy_framer.cc
index 51689299..d19bc04 100644
--- a/net/spdy/core/spdy_framer.cc
+++ b/net/spdy/core/spdy_framer.cc
@@ -1591,66 +1591,6 @@
   return original_len - len;
 }
 
-bool SpdyFramer::ParseHeaderBlockInBuffer(const char* header_data,
-                                          size_t header_length,
-                                          SpdyHeaderBlock* block) const {
-  SpdyFrameReader reader(header_data, header_length);
-
-  // Read number of headers.
-  uint32_t num_headers;
-  if (!reader.ReadUInt32(&num_headers)) {
-    DVLOG(1) << "Unable to read number of headers.";
-    return false;
-  }
-
-  // Read each header.
-  for (uint32_t index = 0; index < num_headers; ++index) {
-    SpdyStringPiece temp;
-
-    // Read header name.
-    if (!reader.ReadStringPiece32(&temp)) {
-      DVLOG(1) << "Unable to read header name (" << index + 1 << " of "
-               << num_headers << ").";
-      return false;
-    }
-    const char* begin = temp.data();
-    const char* end = begin;
-    std::advance(end, temp.size());
-    if (std::any_of(begin, end, isupper)) {
-      DVLOG(1) << "Malformed header: Header name " << temp
-               << " contains upper-case characters.";
-      return false;
-    }
-    SpdyString name(temp);
-
-    // Read header value.
-    if (!reader.ReadStringPiece32(&temp)) {
-      DVLOG(1) << "Unable to read header value (" << index + 1 << " of "
-               << num_headers << ").";
-      return false;
-    }
-    SpdyString value(temp);
-
-    // Ensure no duplicates.
-    if (block->find(name) != block->end()) {
-      DVLOG(1) << "Duplicate header '" << name << "' (" << index + 1 << " of "
-               << num_headers << ").";
-      return false;
-    }
-
-    // Store header.
-    (*block)[name] = value;
-  }
-  if (reader.GetBytesConsumed() != header_length) {
-    SPDY_BUG << "Buffer expected to consist entirely of headers, but only "
-             << reader.GetBytesConsumed() << " bytes consumed, from "
-             << header_length;
-    return false;
-  }
-
-  return true;
-}
-
 SpdyFramer::SpdyFrameIterator::SpdyFrameIterator(SpdyFramer* framer)
     : framer_(framer),
       is_first_frame_(true),
@@ -1660,7 +1600,7 @@
 SpdyFramer::SpdyFrameIterator::~SpdyFrameIterator() {}
 
 size_t SpdyFramer::SpdyFrameIterator::NextFrame(ZeroCopyOutputBuffer* output) {
-  const SpdyFrameWithHeaderBlockIR* frame_ir = GetIR();
+  const SpdyFrameIR* frame_ir = GetIR();
   if (frame_ir == nullptr) {
     LOG(WARNING) << "frame_ir doesn't exist.";
     return false;
@@ -1682,12 +1622,14 @@
     debug_total_size_ += size_without_block;
     debug_total_size_ += encoding->size();
     if (!has_next_frame_) {
+      auto* header_block_frame_ir =
+          static_cast<const SpdyFrameWithHeaderBlockIR*>(frame_ir);
       // TODO(birenroy) are these (here and below) still necessary?
       // HTTP2 uses HPACK for header compression. However, continue to
       // use GetSerializedLength() for an apples-to-apples comparision of
       // compression performance between HPACK and SPDY w/ deflate.
       size_t debug_payload_len =
-          framer_->GetSerializedLength(&frame_ir->header_block());
+          framer_->GetSerializedLength(&header_block_frame_ir->header_block());
       framer_->debug_visitor_->OnSendCompressedFrame(
           frame_ir->stream_id(), frame_ir->frame_type(), debug_payload_len,
           debug_total_size_);
@@ -1721,8 +1663,7 @@
 
 SpdyFramer::SpdyHeaderFrameIterator::~SpdyHeaderFrameIterator() {}
 
-const SpdyFrameWithHeaderBlockIR* SpdyFramer::SpdyHeaderFrameIterator::GetIR()
-    const {
+const SpdyFrameIR* SpdyFramer::SpdyHeaderFrameIterator::GetIR() const {
   return headers_ir_.get();
 }
 
@@ -1747,8 +1688,7 @@
 
 SpdyFramer::SpdyPushPromiseFrameIterator::~SpdyPushPromiseFrameIterator() {}
 
-const SpdyFrameWithHeaderBlockIR*
-SpdyFramer::SpdyPushPromiseFrameIterator::GetIR() const {
+const SpdyFrameIR* SpdyFramer::SpdyPushPromiseFrameIterator::GetIR() const {
   return push_promise_ir_.get();
 }
 
@@ -1781,6 +1721,10 @@
   return frame_ir_ != nullptr;
 }
 
+const SpdyFrameIR* SpdyFramer::SpdyControlFrameIterator::GetIR() const {
+  return frame_ir_.get();
+}
+
 // TODO(yasong): remove all the static_casts.
 std::unique_ptr<SpdyFrameSequence> SpdyFramer::CreateIterator(
     SpdyFramer* framer,
diff --git a/net/spdy/core/spdy_framer.h b/net/spdy/core/spdy_framer.h
index 02ee43f..1972393 100644
--- a/net/spdy/core/spdy_framer.h
+++ b/net/spdy/core/spdy_framer.h
@@ -222,6 +222,10 @@
 
   // Returns true iff there is at least one more frame in the sequence.
   virtual bool HasNextFrame() const = 0;
+
+  // Get SpdyFrameIR of the frame to be serialized.
+  // TODO(yasong): return const SpdyFrameIR& instead.
+  virtual const SpdyFrameIR* GetIR() const = 0;
 };
 
 class ExtensionVisitorInterface {
@@ -386,13 +390,6 @@
   SpdyState state() const;
   bool HasError() const { return state() == SPDY_ERROR; }
 
-  // Given a buffer containing a serialized header block parse out a
-  // SpdyHeaderBlock, putting the results in the given header block.
-  // Returns true if successfully parsed, false otherwise.
-  bool ParseHeaderBlockInBuffer(const char* header_data,
-                                size_t header_length,
-                                SpdyHeaderBlock* block) const;
-
   // Create a SpdyFrameSequence to serialize |frame_ir|.
   static std::unique_ptr<SpdyFrameSequence> CreateIterator(
       SpdyFramer* framer,
@@ -650,7 +647,6 @@
     SpdyFrameIterator& operator=(const SpdyFrameIterator&) = delete;
 
    protected:
-    virtual const SpdyFrameWithHeaderBlockIR* GetIR() const = 0;
     virtual size_t GetFrameSizeSansBlock() const = 0;
     virtual bool SerializeGivenEncoding(const SpdyString& encoding,
                                         ZeroCopyOutputBuffer* output) const = 0;
@@ -683,7 +679,7 @@
     ~SpdyHeaderFrameIterator() override;
 
    private:
-    const SpdyFrameWithHeaderBlockIR* GetIR() const override;
+    const SpdyFrameIR* GetIR() const override;
     size_t GetFrameSizeSansBlock() const override;
     bool SerializeGivenEncoding(const SpdyString& encoding,
                                 ZeroCopyOutputBuffer* output) const override;
@@ -705,7 +701,7 @@
     ~SpdyPushPromiseFrameIterator() override;
 
    private:
-    const SpdyFrameWithHeaderBlockIR* GetIR() const override;
+    const SpdyFrameIR* GetIR() const override;
     size_t GetFrameSizeSansBlock() const override;
     bool SerializeGivenEncoding(const SpdyString& encoding,
                                 ZeroCopyOutputBuffer* output) const override;
@@ -725,6 +721,8 @@
 
     bool HasNextFrame() const override;
 
+    const SpdyFrameIR* GetIR() const override;
+
    private:
     SpdyFramer* const framer_;
     std::unique_ptr<const SpdyFrameIR> frame_ir_;
diff --git a/net/spdy/core/spdy_framer_test.cc b/net/spdy/core/spdy_framer_test.cc
index 0f3deff..92125ec 100644
--- a/net/spdy/core/spdy_framer_test.cc
+++ b/net/spdy/core/spdy_framer_test.cc
@@ -859,39 +859,6 @@
   EXPECT_EQ(0u, visitor.headers_.size());
 }
 
-// Test that we treat incoming upper-case or mixed-case header values as
-// malformed.
-TEST_P(SpdyFramerTest, RejectUpperCaseHeaderBlockValue) {
-  SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
-
-  SpdyFrameBuilder frame(1024);
-  frame.BeginNewFrame(framer, SpdyFrameType::HEADERS, 0, 1);
-  frame.WriteUInt32(1);
-  frame.WriteStringPiece32("Name1");
-  frame.WriteStringPiece32("value1");
-
-  SpdyFrameBuilder frame2(1024);
-  frame2.BeginNewFrame(framer, SpdyFrameType::HEADERS, 0, 1);
-  frame2.WriteUInt32(2);
-  frame2.WriteStringPiece32("name1");
-  frame2.WriteStringPiece32("value1");
-  frame2.WriteStringPiece32("nAmE2");
-  frame2.WriteStringPiece32("value2");
-
-  SpdySerializedFrame control_frame(frame.take());
-  SpdyStringPiece serialized_headers =
-      GetSerializedHeaders(control_frame, framer);
-  SpdySerializedFrame control_frame2(frame2.take());
-  SpdyStringPiece serialized_headers2 =
-      GetSerializedHeaders(control_frame2, framer);
-
-  SpdyHeaderBlock new_headers;
-  EXPECT_FALSE(framer.ParseHeaderBlockInBuffer(
-      serialized_headers.data(), serialized_headers.size(), &new_headers));
-  EXPECT_FALSE(framer.ParseHeaderBlockInBuffer(
-      serialized_headers2.data(), serialized_headers2.size(), &new_headers));
-}
-
 // Test that we can encode and decode stream dependency values in a header
 // frame.
 TEST_P(SpdyFramerTest, HeaderStreamDependencyValues) {
@@ -1302,27 +1269,6 @@
       << SpdyFramer::SpdyFramerErrorToString(framer.spdy_framer_error());
 }
 
-TEST_P(SpdyFramerTest, DuplicateHeader) {
-  SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
-  // Frame builder with plentiful buffer size.
-  SpdyFrameBuilder frame(1024);
-  frame.BeginNewFrame(framer, SpdyFrameType::HEADERS, 0, 3);
-
-  frame.WriteUInt32(2);  // Number of headers.
-  frame.WriteStringPiece32("name");
-  frame.WriteStringPiece32("value1");
-  frame.WriteStringPiece32("name");
-  frame.WriteStringPiece32("value2");
-
-  SpdyHeaderBlock new_headers;
-  SpdySerializedFrame control_frame(frame.take());
-  SpdyStringPiece serialized_headers =
-      GetSerializedHeaders(control_frame, framer);
-  // This should fail because duplicate headers are verboten by the spec.
-  EXPECT_FALSE(framer.ParseHeaderBlockInBuffer(
-      serialized_headers.data(), serialized_headers.size(), &new_headers));
-}
-
 TEST_P(SpdyFramerTest, MultiValueHeader) {
   SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
   SpdyString value("value1\0value2", 13);
diff --git a/remoting/remoting_enable.gni b/remoting/remoting_enable.gni
index a031976..ceb53310 100644
--- a/remoting/remoting_enable.gni
+++ b/remoting/remoting_enable.gni
@@ -6,5 +6,5 @@
 import("//media/media_options.gni")
 
 declare_args() {
-  enable_remoting = !is_chromecast && enable_webrtc
+  enable_remoting = !is_chromecast && !is_fuchsia && enable_webrtc
 }
diff --git a/services/resource_coordinator/public/interfaces/BUILD.gn b/services/resource_coordinator/public/interfaces/BUILD.gn
index dd081b6..a860ec3 100644
--- a/services/resource_coordinator/public/interfaces/BUILD.gn
+++ b/services/resource_coordinator/public/interfaces/BUILD.gn
@@ -21,6 +21,7 @@
     "//mojo/common:common_custom_types",
   ]
 
+  component_output_prefix = "resource_coordinator_public_interfaces_internal"
   export_class_attribute = "SERVICES_RESOURCE_COORDINATOR_PUBLIC_CPP_EXPORT"
   export_define = "SERVICES_RESOURCE_COORDINATOR_PUBLIC_CPP_IMPLEMENTATION=1"
   export_header =
diff --git a/skia/BUILD.gn b/skia/BUILD.gn
index 97b0a710..9d39d53 100644
--- a/skia/BUILD.gn
+++ b/skia/BUILD.gn
@@ -32,6 +32,7 @@
     "//third_party/skia/include/config",
     "//third_party/skia/include/core",
     "//third_party/skia/include/effects",
+    "//third_party/skia/include/encode",
     "//third_party/skia/include/images",
     "//third_party/skia/include/lazy",
     "//third_party/skia/include/pathops",
@@ -43,6 +44,14 @@
   ]
 
   defines = skia_for_chromium_defines
+  defines += [
+    "SK_HAS_PNG_LIBRARY",
+    "SK_HAS_WEBP_LIBRARY",
+  ]
+
+  if (!is_ios) {
+    defines += [ "SK_HAS_JPEG_LIBRARY" ]
+  }
 
   if (is_win || is_mac) {
     defines += [ "SK_FREETYPE_MINIMUM_RUNTIME_VERSION=(((FREETYPE_MAJOR) * 0x01000000) | ((FREETYPE_MINOR) * 0x00010000) | ((FREETYPE_PATCH) * 0x00000100))" ]
@@ -89,14 +98,15 @@
 # Internal-facing config for Skia library code.
 config("skia_library_config") {
   # These include directories are only included for Skia code and are not
-  # exported to dependents. It's not clear if this is on purpose, but this
-  # matches the GYP build.
+  # exported to dependents.
   include_dirs = [
     "//third_party/skia/include/private",
     "//third_party/skia/include/client/android",
+    "//third_party/skia/src/codec",
     "//third_party/skia/src/core",
     "//third_party/skia/src/effects/gradients",
     "//third_party/skia/src/image",
+    "//third_party/skia/src/images",
     "//third_party/skia/src/opts",
     "//third_party/skia/src/pdf",
     "//third_party/skia/src/ports",
@@ -218,8 +228,6 @@
     "ext/opacity_filter_canvas.h",
     "ext/recursive_gaussian_convolution.cc",
     "ext/recursive_gaussian_convolution.h",
-    "ext/skia_encode_image.cc",
-    "ext/skia_encode_image.h",
     "ext/skia_histogram.cc",
     "ext/skia_histogram.h",
     "ext/skia_memory_dump_provider.cc",
@@ -262,6 +270,11 @@
   sources += [
     "//third_party/skia/src/fonts/SkFontMgr_indirect.cpp",
     "//third_party/skia/src/fonts/SkRemotableFontMgr.cpp",
+    "//third_party/skia/src/images/SkImageEncoder.cpp",
+    "//third_party/skia/src/images/SkJPEGWriteUtility.cpp",
+    "//third_party/skia/src/images/SkJpegEncoder.cpp",
+    "//third_party/skia/src/images/SkPngEncoder.cpp",
+    "//third_party/skia/src/images/SkWebpEncoder.cpp",
     "//third_party/skia/src/ports/SkGlobalInitialization_default.cpp",
     "//third_party/skia/src/ports/SkImageGenerator_none.cpp",
     "//third_party/skia/src/ports/SkOSFile_stdio.cpp",
@@ -410,11 +423,28 @@
     "//base",
     "//base/third_party/dynamic_annotations",
     "//build/config/freetype",
+    "//third_party:jpeg",
+    "//third_party/libpng",
+    "//third_party/libwebp",
   ]
 
+  if (is_ios) {
+    sources -= [
+      "//third_party/skia/src/images/SkJPEGWriteUtility.cpp",
+      "//third_party/skia/src/images/SkJpegEncoder.cpp",
+    ]
+    deps -= [ "//third_party:jpeg" ]
+  }
+
   if (is_linux) {
     if (use_pango) {
-      configs += [ "//build/config/linux/pangocairo" ]
+      configs += [
+        # libpng_config will be included automatically from deps.  We do this
+        # to ensure that it is included before the pangocairo path (which also
+        # has a png.h).
+        "//third_party/libpng:libpng_config",
+        "//build/config/linux/pangocairo",
+      ]
     }
     deps += [
       "//build/linux:fontconfig",
diff --git a/skia/ext/skia_encode_image.cc b/skia/ext/skia_encode_image.cc
deleted file mode 100644
index bb6820f..0000000
--- a/skia/ext/skia_encode_image.cc
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 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 "skia/ext/skia_encode_image.h"
-
-#include "third_party/skia/include/core/SkImageEncoder.h"
-
-static skia::ImageEncoderFunction g_image_encoder = nullptr;
-
-void skia::SetImageEncoder(skia::ImageEncoderFunction image_encoder) {
-  g_image_encoder = image_encoder;
-}
-
-bool SkEncodeImage(SkWStream* stream,
-                   const SkPixmap& pixmap,
-                   SkEncodedImageFormat format,
-                   int quality) {
-  skia::ImageEncoderFunction encoder = g_image_encoder;
-  return encoder && encoder(stream, pixmap, format, quality);
-}
diff --git a/skia/ext/skia_encode_image.h b/skia/ext/skia_encode_image.h
deleted file mode 100644
index 30e209fa..0000000
--- a/skia/ext/skia_encode_image.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 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 SKIA_EXT_SKIA_ENCODE_IMAGE_H
-#define SKIA_EXT_SKIA_ENCODE_IMAGE_H
-
-#include "third_party/skia/include/core/SkEncodedImageFormat.h"
-#include "third_party/skia/include/core/SkTypes.h"
-
-class SkPixmap;
-class SkWStream;
-
-namespace skia {
-
-using ImageEncoderFunction = bool (*)(SkWStream*,
-                                      const SkPixmap&,
-                                      SkEncodedImageFormat,
-                                      int quality);
-
-SK_API void SetImageEncoder(ImageEncoderFunction);
-
-}  // namespace skia
-
-#endif  // SKIA_EXT_SKIA_ENCODE_IMAGE_H
diff --git a/testing/buildbot/chromium.perf.json b/testing/buildbot/chromium.perf.json
index abd41d8..5e68bfa 100644
--- a/testing/buildbot/chromium.perf.json
+++ b/testing/buildbot/chromium.perf.json
@@ -916,26 +916,6 @@
         }
       },
       {
-        "args": [],
-        "isolate_name": "cc_perftests",
-        "name": "cc_perftests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build73-b1--device2",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
         "args": [
           "dromaeo.domcoreattr",
           "-v",
diff --git a/third_party/.gitignore b/third_party/.gitignore
index 47c52825..95f49c1 100644
--- a/third_party/.gitignore
+++ b/third_party/.gitignore
@@ -58,6 +58,7 @@
 /flatbuffers/src
 /fontconfig/src
 /freetype/src
+/fuchsia-sdk
 /gestures/gestures
 /gles2_conform
 /glslang/src
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/iso-latin1-header.html b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/iso-latin1-header.https.html
similarity index 75%
rename from third_party/WebKit/LayoutTests/http/tests/serviceworker/iso-latin1-header.html
rename to third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/iso-latin1-header.https.html
index 938fbd3..ad0ab59 100644
--- a/third_party/WebKit/LayoutTests/http/tests/serviceworker/iso-latin1-header.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/iso-latin1-header.https.html
@@ -1,9 +1,9 @@
 <!DOCTYPE html>
 <title>Service Worker: respondWith with header value containing an ISO Latin 1 (ISO-8859-1 Character Set) string</title>
-<script src="../resources/testharness.js"></script>
-<script src="../resources/testharnessreport.js"></script>
-<script src="../resources/get-host-info.js?pipe=sub"></script>
-<script src="resources/test-helpers.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
 <script>
 async_test(function(t) {
     var SCOPE = 'resources/iso-latin1-header-iframe.html';
@@ -16,16 +16,17 @@
       .then(function() { return with_iframe(SCOPE); })
       .then(function(frame) {
           var channel = new MessageChannel();
+          t.add_cleanup(function() {
+              frame.remove();
+            });
           channel.port1.onmessage = t.step_func(function(e) {
               assert_equals(e.data.results, 'finish');
-              frame.remove();
               service_worker_unregister_and_done(t, SCOPE);
             });
           frame.contentWindow.postMessage({},
-                                          host_info['HTTP_ORIGIN'],
+                                          host_info['HTTPS_ORIGIN'],
                                           [channel.port2]);
         })
       .catch(unreached_rejection(t));
   }, 'Verify the response of FetchEvent using XMLHttpRequest');
-
 </script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/opaque-response-preloaded.https.html b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/opaque-response-preloaded.https.html
new file mode 100644
index 0000000..ffe9344
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/opaque-response-preloaded.https.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Opaque responses should not be reused for XHRs</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+const WORKER =
+  'resources/opaque-response-preloaded-worker.js';
+const SCOPE =
+  'resources/opaque-response-preloaded-iframe.html';
+var resolve_done;
+var done_was_called = new Promise(resolve => resolve_done = resolve);
+// Called by the iframe when done.
+function done(result) { resolve_done(result); }
+
+// This tests that the browser does not inappropriately use a cached opaque
+// response for a request that is not no-cors. The test opens a controlled
+// iframe that uses link rel=preload to issue a same-origin no-cors request.
+// The service worker responds to the request with an opaque response. Then the
+// iframe does an XHR (not no-cors) to that URL again. The request should fail.
+promise_test(t => {
+    return service_worker_unregister_and_register(t, WORKER, SCOPE)
+      .then(reg => {
+           add_completion_callback(() => reg.unregister());
+           return wait_for_state(t, reg.installing, 'activated');
+         })
+      .then(() => with_iframe(SCOPE))
+      .then(frame => t.add_cleanup(() => frame.remove() ))
+      .then(() => done_was_called)
+      .then(result => assert_equals(result, 'PASS'));
+  }, 'Opaque responses should not be reused for XHRs');
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/iso-latin1-header-iframe.html b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/iso-latin1-header-iframe.html
similarity index 86%
rename from third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/iso-latin1-header-iframe.html
rename to third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/iso-latin1-header-iframe.html
index 8da2582..90e3f69 100644
--- a/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/iso-latin1-header-iframe.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/iso-latin1-header-iframe.html
@@ -1,7 +1,4 @@
-<script src="../../resources/testharness.js"></script>
-<script src="test-helpers.js"></script>
 <script>
-
 function xhr_send(method, data) {
   return new Promise(function(resolve, reject) {
       var xhr = new XMLHttpRequest();
@@ -23,5 +20,4 @@
       .then(function() { port.postMessage({results: 'finish'}); })
       .catch(function(e) { port.postMessage({results: 'failure:' + e}); });
   });
-
 </script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/iso-latin1-header-worker.js b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/iso-latin1-header-worker.js
similarity index 100%
rename from third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/iso-latin1-header-worker.js
rename to third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/iso-latin1-header-worker.js
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/opaque-response-in-memorycache-iframe.html b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/opaque-response-preloaded-iframe.html
similarity index 81%
rename from third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/opaque-response-in-memorycache-iframe.html
rename to third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/opaque-response-preloaded-iframe.html
index 5214b91..9c0eed1d 100644
--- a/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/opaque-response-in-memorycache-iframe.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/opaque-response-preloaded-iframe.html
@@ -4,6 +4,8 @@
 <script>
 function runTest() {
   var l = document.createElement('link');
+  // Use link rel=preload to try to get the browser to cache the opaque
+  // response.
   l.setAttribute('rel', 'preload');
   l.setAttribute('href', 'opaque-response');
   l.onload = function() {
@@ -12,8 +14,8 @@
     xhr.open('GET', 'opaque-response');
     // opaque-response returns an opaque response from serviceworker and thus
     // the XHR must fail because it is not no-cors request.
-    // Particularly, the XHR must not reuse preloaded the opaque response on
-    // MemoryCache.
+    // Particularly, the XHR must not reuse the opaque response from the
+    // preload request.
     xhr.onerror = function() {
       parent.done('PASS');
     };
diff --git a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/opaque-response-preloaded-worker.js b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/opaque-response-preloaded-worker.js
new file mode 100644
index 0000000..1615be2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/opaque-response-preloaded-worker.js
@@ -0,0 +1,12 @@
+importScripts('/common/get-host-info.sub.js');
+
+var remoteUrl = get_host_info()['HTTPS_REMOTE_ORIGIN'] +
+  '/service-workers/service-worker/resources/simple.txt'
+
+self.addEventListener('fetch', event => {
+    if (!event.request.url.match(/opaque-response$/)) {
+      return;
+    }
+
+    event.respondWith(fetch(remoteUrl, {mode: 'no-cors'}));
+  });
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/chromium.performance-timeline.html b/third_party/WebKit/LayoutTests/http/tests/serviceworker/chromium.performance-timeline.html
new file mode 100644
index 0000000..ac7cb25
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/serviceworker/chromium.performance-timeline.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<!-- This test is prefixed with `chromium.` because it is maintained only to
+  preserve test coverage until such time as the more rigorous version available
+  in the Web Platform Tests project can be made to pass. See
+  https://crbug.com/507169 -->
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.js"></script>
+<script>
+
+service_worker_test(
+    '../workers/resources/performance-timeline-worker.js',
+    'Test Performance Timeline API in Service Worker');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/opaque-response-in-memorycache.html b/third_party/WebKit/LayoutTests/http/tests/serviceworker/opaque-response-in-memorycache.html
deleted file mode 100644
index a723e24..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/serviceworker/opaque-response-in-memorycache.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Opaque responses on MemoryCache should not be reused for XHRs</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/test-helpers.js"></script>
-<script>
-const WORKER =
-  'resources/opaque-response-in-memorycache-worker.js';
-const SCOPE =
-  'resources/opaque-response-in-memorycache-iframe.html';
-var resolve_done;
-var done_was_called = new Promise(resolve => resolve_done = resolve);
-// Called by the iframe when done.
-function done(result) { resolve_done(result); }
-
-// This test creates an controlled iframe that makes a fetch request. The
-// service worker returns a response with a body stream containing an invalid
-// chunk.
-promise_test(t => {
-    return service_worker_unregister_and_register(t, WORKER, SCOPE)
-      .then(reg => {
-           add_completion_callback(() => reg.unregister());
-           return wait_for_state(t, reg.installing, 'activated');
-         })
-      .then(() => with_iframe(SCOPE))
-      .then(() => done_was_called)
-      .then(result => assert_equals(result, 'PASS'));
-  }, 'Opaque responses on MemoryCache should not be reused for XHRs');
-</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/performance-timeline.html b/third_party/WebKit/LayoutTests/http/tests/serviceworker/performance-timeline.html
deleted file mode 100644
index b5706666..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/serviceworker/performance-timeline.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<!DOCTYPE html>
-<script src="../resources/testharness.js"></script>
-<script src="../resources/testharnessreport.js"></script>
-<script src="resources/test-helpers.js"></script>
-<script>
-
-service_worker_test(
-    '../workers/resources/performance-timeline-worker.js',
-    'Test Performance Timeline API in Service Worker');
-
-</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/opaque-response-in-memorycache-worker.js b/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/opaque-response-in-memorycache-worker.js
deleted file mode 100644
index 2a58d8c..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/opaque-response-in-memorycache-worker.js
+++ /dev/null
@@ -1,5 +0,0 @@
-self.addEventListener('fetch', event => {
-    if (!event.request.url.match(/opaque-response$/))
-      return;
-    event.respondWith(fetch("http://localhost:8000/serviceworker/resources/simple.txt", {mode: 'no-cors'}));
-  });
diff --git a/third_party/WebKit/LayoutTests/platform/linux/animations/skew-notsequential-compositor-expected.png b/third_party/WebKit/LayoutTests/platform/linux/animations/skew-notsequential-compositor-expected.png
index f62d684b..f1fb3b5 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/animations/skew-notsequential-compositor-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/animations/skew-notsequential-compositor-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/http/tests/inspector/network/waterfall-images-expected.png b/third_party/WebKit/LayoutTests/platform/linux/http/tests/inspector/network/waterfall-images-expected.png
new file mode 100644
index 0000000..972f21531
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/linux/http/tests/inspector/network/waterfall-images-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/animations/skew-notsequential-compositor-expected.png b/third_party/WebKit/LayoutTests/platform/mac/animations/skew-notsequential-compositor-expected.png
index f62d684b..f1fb3b5 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/animations/skew-notsequential-compositor-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/animations/skew-notsequential-compositor-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png b/third_party/WebKit/LayoutTests/platform/mac/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png
index 8b8cf3d..20c0624 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/animations/skew-notsequential-compositor-expected.png b/third_party/WebKit/LayoutTests/platform/win/animations/skew-notsequential-compositor-expected.png
index e7bda3a..f9c7afc 100644
--- a/third_party/WebKit/LayoutTests/platform/win/animations/skew-notsequential-compositor-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/animations/skew-notsequential-compositor-expected.png
Binary files differ
diff --git a/third_party/WebKit/Source/core/dom/SelectorQuery.cpp b/third_party/WebKit/Source/core/dom/SelectorQuery.cpp
index 7189c5e..dcf0a16 100644
--- a/third_party/WebKit/Source/core/dom/SelectorQuery.cpp
+++ b/third_party/WebKit/Source/core/dom/SelectorQuery.cpp
@@ -41,11 +41,14 @@
 #include "core/dom/shadow/ShadowRoot.h"
 #include "platform/wtf/PtrUtil.h"
 
+// Uncomment to run the SelectorQueryTests for stats in a release build.
+// #define RELEASE_QUERY_STATS
+
 namespace blink {
 
 using namespace HTMLNames;
 
-#if DCHECK_IS_ON()
+#if DCHECK_IS_ON() || defined(RELEASE_QUERY_STATS)
 static SelectorQuery::QueryStats& CurrentQueryStats() {
   DEFINE_STATIC_LOCAL(SelectorQuery::QueryStats, stats, ());
   return stats;
@@ -197,19 +200,6 @@
   }
 }
 
-inline bool SelectorQuery::CanUseFastQuery(
-    const ContainerNode& root_node) const {
-  if (uses_deep_combinator_or_shadow_pseudo_)
-    return false;
-  if (needs_updated_distribution_)
-    return false;
-  if (root_node.GetDocument().InQuirksMode())
-    return false;
-  if (!root_node.isConnected())
-    return false;
-  return selectors_.size() == 1;
-}
-
 inline bool AncestorHasClassName(ContainerNode& root_node,
                                  const AtomicString& class_name) {
   if (!root_node.IsElementNode())
@@ -387,6 +377,9 @@
 void SelectorQuery::ExecuteWithId(
     ContainerNode& root_node,
     typename SelectorQueryTrait::OutputType& output) const {
+  DCHECK_EQ(selectors_.size(), 1u);
+  DCHECK(!root_node.GetDocument().InQuirksMode());
+
   const CSSSelector& first_selector = *selectors_[0];
   const TreeScope& scope = root_node.ContainingTreeScope();
 
@@ -440,7 +433,7 @@
   if (selectors_.IsEmpty())
     return;
 
-  if (!CanUseFastQuery(root_node)) {
+  if (use_slow_scan_) {
     if (needs_updated_distribution_)
       root_node.UpdateDistribution();
     if (uses_deep_combinator_or_shadow_pseudo_) {
@@ -452,9 +445,15 @@
   }
 
   DCHECK_EQ(selectors_.size(), 1u);
-  DCHECK(!root_node.GetDocument().InQuirksMode());
+  DCHECK(!needs_updated_distribution_);
+  DCHECK(!uses_deep_combinator_or_shadow_pseudo_);
 
-  if (selector_id_) {
+  // In quirks mode getElementById("a") is case sensitive and should only
+  // match elements with lowercase id "a", but querySelector is case-insensitive
+  // so querySelector("#a") == querySelector("#A"), which means we can only use
+  // the id fast path when we're in a standards mode document.
+  if (selector_id_ && root_node.IsInTreeScope() &&
+      !root_node.GetDocument().InQuirksMode()) {
     ExecuteWithId<SelectorQueryTrait>(root_node, output);
     return;
   }
@@ -496,7 +495,8 @@
       selector_id_is_rightmost_(true),
       selector_id_affected_by_sibling_combinator_(false),
       uses_deep_combinator_or_shadow_pseudo_(false),
-      needs_updated_distribution_(false) {
+      needs_updated_distribution_(false),
+      use_slow_scan_(true) {
   selectors_.ReserveInitialCapacity(selector_list_.ComputeLength());
   for (const CSSSelector* selector = selector_list_.First(); selector;
        selector = CSSSelectorList::Next(*selector)) {
@@ -510,6 +510,7 @@
 
   if (selectors_.size() == 1 && !uses_deep_combinator_or_shadow_pseudo_ &&
       !needs_updated_distribution_) {
+    use_slow_scan_ = false;
     for (const CSSSelector* current = selectors_[0]; current;
          current = current->TagHistory()) {
       if (current->Match() == CSSSelector::kId) {
diff --git a/third_party/WebKit/Source/core/dom/SelectorQuery.h b/third_party/WebKit/Source/core/dom/SelectorQuery.h
index 660c681b6..4ad6f1f 100644
--- a/third_party/WebKit/Source/core/dom/SelectorQuery.h
+++ b/third_party/WebKit/Source/core/dom/SelectorQuery.h
@@ -81,8 +81,6 @@
  private:
   explicit SelectorQuery(CSSSelectorList);
 
-  bool CanUseFastQuery(const ContainerNode& root_node) const;
-
   template <typename SelectorQueryTrait>
   void ExecuteWithId(ContainerNode& root_node,
                      typename SelectorQueryTrait::OutputType&) const;
@@ -118,6 +116,7 @@
   bool selector_id_affected_by_sibling_combinator_ : 1;
   bool uses_deep_combinator_or_shadow_pseudo_ : 1;
   bool needs_updated_distribution_ : 1;
+  bool use_slow_scan_ : 1;
 };
 
 class SelectorQueryCache {
diff --git a/third_party/WebKit/Source/core/dom/SelectorQueryTest.cpp b/third_party/WebKit/Source/core/dom/SelectorQueryTest.cpp
index 1828396e..91389fe4 100644
--- a/third_party/WebKit/Source/core/dom/SelectorQueryTest.cpp
+++ b/third_party/WebKit/Source/core/dom/SelectorQueryTest.cpp
@@ -15,6 +15,9 @@
 #include "core/html/HTMLHtmlElement.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+// Uncomment to run the SelectorQueryTests for stats in a release build.
+// #define RELEASE_QUERY_STATS
+
 namespace blink {
 
 namespace {
@@ -42,7 +45,7 @@
       Element* match = scope.QuerySelector(selector);
       EXPECT_EQ(test_case.matches, match ? 1u : 0u);
     }
-#if DCHECK_IS_ON()
+#if DCHECK_IS_ON() || defined(RELEASE_QUERY_STATS)
     SelectorQuery::QueryStats stats = SelectorQuery::LastQueryStats();
     EXPECT_EQ(test_case.stats.total_count, stats.total_count);
     EXPECT_EQ(test_case.stats.fast_id, stats.fast_id);
@@ -298,24 +301,29 @@
       "  </body>"
       "</html>");
   static const struct QueryTest kTestCases[] = {
-      // Quirks mode always uses the slow path.
-      {"#one", false, 1, {5, 0, 0, 0, 0, 5, 0}},
-      {"#One", false, 1, {5, 0, 0, 0, 0, 5, 0}},
-      {"#ONE", false, 1, {5, 0, 0, 0, 0, 5, 0}},
-      {"#ONE", true, 2, {6, 0, 0, 0, 0, 6, 0}},
-      {"[id=One]", false, 1, {5, 0, 0, 0, 0, 5, 0}},
-      {"[id=One]", true, 1, {6, 0, 0, 0, 0, 6, 0}},
-      {"span", false, 1, {4, 0, 0, 0, 0, 4, 0}},
-      {"span", true, 3, {6, 0, 0, 0, 0, 6, 0}},
-      {".two", false, 1, {5, 0, 0, 0, 0, 5, 0}},
-      {".two", true, 2, {6, 0, 0, 0, 0, 6, 0}},
-      {"body #first", false, 1, {4, 0, 0, 0, 0, 4, 0}},
-      {"body #one", true, 2, {6, 0, 0, 0, 0, 6, 0}},
+      // Quirks mode can't use the id fast path due to being case-insensitive.
+      {"#one", false, 1, {5, 0, 0, 0, 5, 0, 0}},
+      {"#One", false, 1, {5, 0, 0, 0, 5, 0, 0}},
+      {"#ONE", false, 1, {5, 0, 0, 0, 5, 0, 0}},
+      {"#ONE", true, 2, {6, 0, 0, 0, 6, 0, 0}},
+      {"[id=One]", false, 1, {5, 0, 0, 0, 5, 0, 0}},
+      {"[id=One]", true, 1, {6, 0, 0, 0, 6, 0, 0}},
+      {"body #first", false, 1, {4, 0, 0, 0, 4, 0, 0}},
+      {"body #one", true, 2, {6, 0, 0, 0, 6, 0, 0}},
+      // Quirks can use the class and tag name fast paths though.
+      {"span", false, 1, {4, 0, 0, 4, 0, 0, 0}},
+      {"span", true, 3, {6, 0, 0, 6, 0, 0, 0}},
+      {".two", false, 1, {5, 0, 5, 0, 0, 0, 0}},
+      {".two", true, 2, {6, 0, 6, 0, 0, 0, 0}},
+      {"body span", false, 1, {4, 0, 0, 0, 4, 0, 0}},
+      {"body span", true, 3, {6, 0, 0, 0, 6, 0, 0}},
+      {"body .two", false, 1, {5, 0, 5, 0, 0, 0, 0}},
+      {"body .two", true, 2, {6, 0, 6, 0, 0, 0, 0}},
   };
   RunTests(*document, kTestCases);
 }
 
-TEST(SelectorQueryTest, DisconnectedSubtreeSlowPath) {
+TEST(SelectorQueryTest, DisconnectedSubtree) {
   Document* document = HTMLDocument::Create();
   Element* scope = document->createElement("div");
   scope->setInnerHTML(
@@ -327,38 +335,49 @@
       "    <span id=multiple class=B></span>"
       "  </span>"
       "</section>");
-  ShadowRoot& shadowRoot =
-      scope->EnsureShadow().AddShadowRoot(*scope, ShadowRootType::kOpen);
-  // Make the inside the ShadowRoot look identical to the outer document.
-  shadowRoot.appendChild(
-      ElementTraversal::FirstChild(*scope)->CloneElementWithChildren());
   static const struct QueryTest kTestCases[] = {
-      // TODO(esprehn): Disconnected subtrees always uses the slow path, but
-      // we can actually use it in a number of cases, for example using the id
-      // map for things inside a tree scope, or using the fast class scanning
-      // always.
-      {"#A", false, 1, {3, 0, 0, 0, 0, 3, 0}},
-      {"#B", false, 1, {4, 0, 0, 0, 0, 4, 0}},
-      {"#B", true, 1, {6, 0, 0, 0, 0, 6, 0}},
-      {"#multiple", true, 2, {6, 0, 0, 0, 0, 6, 0}},
-      {".child", false, 1, {4, 0, 0, 0, 0, 4, 0}},
-      {".child", true, 2, {6, 0, 0, 0, 0, 6, 0}},
-      {"#first span", false, 1, {3, 0, 0, 0, 0, 3, 0}},
-      {"#first span", true, 4, {6, 0, 0, 0, 0, 6, 0}},
+      {"#A", false, 1, {3, 0, 0, 0, 3, 0, 0}},
+      {"#B", false, 1, {4, 0, 0, 0, 4, 0, 0}},
+      {"#B", true, 1, {6, 0, 0, 0, 6, 0, 0}},
+      {"#multiple", true, 2, {6, 0, 0, 0, 6, 0, 0}},
+      {".child", false, 1, {4, 0, 4, 0, 0, 0, 0}},
+      {".child", true, 2, {6, 0, 6, 0, 0, 0, 0}},
+      {"#first span", false, 1, {3, 0, 0, 0, 3, 0, 0}},
+      {"#first span", true, 4, {6, 0, 0, 0, 6, 0, 0}},
   };
 
-  {
-    SCOPED_TRACE("Inside disconnected subtree");
-    RunTests(*scope, kTestCases);
-  }
+  RunTests(*scope, kTestCases);
+}
 
-  {
-    // Run all the tests a second time but with a scope inside a shadow root,
-    // this tests for cases where we could have used the id map the ShadowRoot
-    // is keeping track of.
-    SCOPED_TRACE("Inside disconnected shadow root subtree");
-    RunTests(shadowRoot, kTestCases);
-  }
+TEST(SelectorQueryTest, DisconnectedTreeScope) {
+  Document* document = HTMLDocument::Create();
+  Element* host = document->createElement("div");
+  // TODO(esprehn): Element::attachShadow() should not require a ScriptState,
+  // it should handle the use counting in the bindings layer instead of in the
+  // C++.
+  ShadowRoot& shadowRoot =
+      host->EnsureShadow().AddShadowRoot(*host, ShadowRootType::kOpen);
+  shadowRoot.setInnerHTML(
+      "<section>"
+      "  <span id=first>"
+      "    <span id=A class=A></span>"
+      "    <span id=B class=child></span>"
+      "    <span id=multiple class=child></span>"
+      "    <span id=multiple class=B></span>"
+      "  </span>"
+      "</section>");
+  static const struct QueryTest kTestCases[] = {
+      {"#A", false, 1, {1, 1, 0, 0, 0, 0, 0}},
+      {"#B", false, 1, {1, 1, 0, 0, 0, 0, 0}},
+      {"#B", true, 1, {1, 1, 0, 0, 0, 0, 0}},
+      {"#multiple", true, 2, {2, 2, 0, 0, 0, 0, 0}},
+      {".child", false, 1, {4, 0, 4, 0, 0, 0, 0}},
+      {".child", true, 2, {6, 0, 6, 0, 0, 0, 0}},
+      {"#first span", false, 1, {2, 1, 0, 0, 1, 0, 0}},
+      {"#first span", true, 4, {5, 1, 0, 0, 4, 0, 0}},
+  };
+
+  RunTests(shadowRoot, kTestCases);
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/layout/LayoutBlockFlow.cpp b/third_party/WebKit/Source/core/layout/LayoutBlockFlow.cpp
index 17b6bb2..7dc3af6 100644
--- a/third_party/WebKit/Source/core/layout/LayoutBlockFlow.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutBlockFlow.cpp
@@ -4149,21 +4149,23 @@
       break;
 
     FloatingObject& floating_object = **it;
-    if (!float_box_is_self_painting_layer) {
-      // This repeats the logic in addOverhangingFloats() about shouldPaint
-      // flag:
-      // - The nearest enclosing block in which the float doesn't overhang
-      //   paints the float;
-      // - Or even if the float overhangs, if the ancestor block has
-      //   self-painting layer, it paints the float.
-      if (ancestor_block->HasSelfPaintingLayer() ||
-          !ancestor_block->IsOverhangingFloat(floating_object)) {
-        floating_object.SetShouldPaint(true);
-        return;
-      }
-    } else {
-      floating_object.SetShouldPaint(false);
-    }
+    // This repeats the logic in addOverhangingFloats() about shouldPaint
+    // flag:
+    // - The nearest enclosing block in which the float doesn't overhang
+    //   paints the float;
+    // - Or even if the float overhangs, if the ancestor block has
+    //   self-painting layer, it paints the float.
+    LayoutBlockFlow* parent_block =
+        ToLayoutBlockFlow(floating_object.GetLayoutObject()->Parent());
+    bool is_overhanging_float =
+        parent_block && parent_block->IsOverhangingFloat(floating_object);
+    bool should_paint =
+        !float_box_is_self_painting_layer &&
+        (ancestor_block->HasSelfPaintingLayer() || !is_overhanging_float);
+    floating_object.SetShouldPaint(should_paint);
+
+    if (floating_object.ShouldPaint())
+      return;
   }
 }
 
diff --git a/third_party/WebKit/Source/core/layout/LayoutText.cpp b/third_party/WebKit/Source/core/layout/LayoutText.cpp
index 174b61cf..761125e 100644
--- a/third_party/WebKit/Source/core/layout/LayoutText.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutText.cpp
@@ -1719,8 +1719,7 @@
 void LayoutText::SetText(PassRefPtr<StringImpl> text, bool force) {
   DCHECK(text);
 
-  bool equal = Equal(text_.Impl(), text.Get());
-  if (equal && !force)
+  if (!force && Equal(text_.Impl(), text.Get()))
     return;
 
   SetTextInternal(std::move(text));
@@ -1733,14 +1732,8 @@
         LayoutInvalidationReason::kTextChanged);
   known_to_have_no_overflow_and_no_fallback_fonts_ = false;
 
-  // Don't bother updating the AX tree if there's no change. Otherwise, when
-  // typing in password fields, we would announce each "dot" twice: once when a
-  // character is typed, and second when that character is hidden.
-  if (!equal) {
-    AXObjectCache* cache = GetDocument().ExistingAXObjectCache();
-    if (cache)
-      cache->TextChanged(this);
-  }
+  if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache())
+    cache->TextChanged(this);
 
   TextAutosizer* text_autosizer = GetDocument().GetTextAutosizer();
   if (text_autosizer)
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/SettingsUI.js b/third_party/WebKit/Source/devtools/front_end/ui/SettingsUI.js
index abb0b9c1..7cc50ad 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/SettingsUI.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui/SettingsUI.js
@@ -86,21 +86,6 @@
 };
 
 /**
- * @param {!Common.Setting} setting
- * @return {!Element}
- */
-UI.SettingsUI.createSettingFieldset = function(setting) {
-  var fieldset = createElement('fieldset');
-  fieldset.disabled = !setting.get();
-  setting.addChangeListener(settingChanged);
-  return fieldset;
-
-  function settingChanged() {
-    fieldset.disabled = !setting.get();
-  }
-};
-
-/**
  * @interface
  */
 UI.SettingUI = function() {};
diff --git a/third_party/WebKit/Source/modules/imagecapture/README.md b/third_party/WebKit/Source/modules/imagecapture/README.md
index 0166c49..b0b52e2 100644
--- a/third_party/WebKit/Source/modules/imagecapture/README.md
+++ b/third_party/WebKit/Source/modules/imagecapture/README.md
@@ -42,14 +42,18 @@
 example, changing the zoom level is instantly reflected on the `theTrack`,
 while connecting the Red Eye Reduction, if available, is not.
 
- Object | type | retrieved by... |
- :--| :-- | --: |
-[`PhotoCapabilities`](https://w3c.github.io/mediacapture-image/##photocapabilities-section) | non-live capabilities | `theImageCapturer.getPhotoCapabilities()` |
-[`MediaTrackCapabilities`](https://w3c.github.io/mediacapture-image/#mediatrackcapabilities-section) | live capabilities | `theTrack.getCapabilities()` |
-|  |  |
-[`PhotoSettings`](https://w3c.github.io/mediacapture-image/##photocapabilities-section) | non-live settings |  |
-[`MediaTrackSettings`](https://w3c.github.io/mediacapture-image/#mediatracksettings-section) | live settings | `theTrack.getSettings()` |
+| Object                   |type                 | retrieved by...                         |
+| :--                      |:--                  | --:                                     |
+|[`PhotoCapabilities`]     |non-live capabilities|`theImageCapturer.getPhotoCapabilities()`|
+|[`MediaTrackCapabilities`]|live capabilities    |`theTrack.getCapabilities()`             |
+|                          |                     |                                         |
+|[`PhotoSettings`]         |non-live settings    |                                         |
+|[`MediaTrackSettings`]    |live settings        |`theTrack.getSettings()`                 |
 
+[`PhotoCapabilities`]: https://w3c.github.io/mediacapture-image/##photocapabilities-section
+[`MediaTrackCapabilities`]: https://w3c.github.io/mediacapture-image/#mediatrackcapabilities-section
+[`PhotoSettings`]: https://w3c.github.io/mediacapture-image/##photosettings-section
+[`MediaTrackSettings`]: https://w3c.github.io/mediacapture-image/#mediatracksettings-section
 
 ## Other topics
 
diff --git a/third_party/WebKit/Source/platform/text/BidiResolverTest.cpp b/third_party/WebKit/Source/platform/text/BidiResolverTest.cpp
index 62eea87..eedbc2fa 100644
--- a/third_party/WebKit/Source/platform/text/BidiResolverTest.cpp
+++ b/third_party/WebKit/Source/platform/text/BidiResolverTest.cpp
@@ -183,13 +183,13 @@
 
   TextRun text_run(input.data(), input.size());
   switch (paragraph_direction) {
-    case bidi_test::DirectionAutoLTR:
+    case bidi_test::kDirectionAutoLTR:
       text_run.SetDirection(DetermineParagraphDirectionality(text_run));
       break;
-    case bidi_test::DirectionLTR:
+    case bidi_test::kDirectionLTR:
       text_run.SetDirection(TextDirection::kLtr);
       break;
-    case bidi_test::DirectionRTL:
+    case bidi_test::kDirectionRTL:
       text_run.SetDirection(TextDirection::kRtl);
       break;
     default:
@@ -206,7 +206,7 @@
   std::ostringstream error_context;
   error_context << ", line " << line_number << " \"" << line << "\"";
   error_context << " context: "
-                << bidi_test::nameFromParagraphDirection(paragraph_direction);
+                << bidi_test::NameFromParagraphDirection(paragraph_direction);
 
   std::vector<int> actual_order;
   std::vector<int> actual_levels;
@@ -273,7 +273,7 @@
   std::ifstream bidi_test_file(bidi_test_path.c_str());
   EXPECT_TRUE(bidi_test_file.is_open());
   bidi_test::Harness<BidiTestRunner> harness(runner);
-  harness.parse(bidi_test_file);
+  harness.Parse(bidi_test_file);
   bidi_test_file.close();
 
   if (runner.tests_skipped_)
@@ -305,7 +305,7 @@
   std::ifstream bidi_test_file(bidi_test_path.c_str());
   EXPECT_TRUE(bidi_test_file.is_open());
   bidi_test::CharacterHarness<BidiTestRunner> harness(runner);
-  harness.parse(bidi_test_file);
+  harness.Parse(bidi_test_file);
   bidi_test_file.close();
 
   if (runner.tests_skipped_)
diff --git a/third_party/WebKit/Source/platform/text/BidiTestHarness.h b/third_party/WebKit/Source/platform/text/BidiTestHarness.h
index eabfbb3..9ddd6d4 100644
--- a/third_party/WebKit/Source/platform/text/BidiTestHarness.h
+++ b/third_party/WebKit/Source/platform/text/BidiTestHarness.h
@@ -58,22 +58,22 @@
 namespace bidi_test {
 
 enum ParagraphDirection {
-  DirectionNone = 0,
-  DirectionAutoLTR = 1,
-  DirectionLTR = 2,
-  DirectionRTL = 4,
+  kDirectionNone = 0,
+  kDirectionAutoLTR = 1,
+  kDirectionLTR = 2,
+  kDirectionRTL = 4,
 };
 const int kMaxParagraphDirection =
-    DirectionAutoLTR | DirectionLTR | DirectionRTL;
+    kDirectionAutoLTR | kDirectionLTR | kDirectionRTL;
 
 // For error printing:
-std::string nameFromParagraphDirection(ParagraphDirection paragraphDirection) {
-  switch (paragraphDirection) {
-    case bidi_test::DirectionAutoLTR:
+std::string NameFromParagraphDirection(ParagraphDirection paragraph_direction) {
+  switch (paragraph_direction) {
+    case bidi_test::kDirectionAutoLTR:
       return "Auto-LTR";
-    case bidi_test::DirectionLTR:
+    case bidi_test::kDirectionLTR:
       return "LTR";
-    case bidi_test::DirectionRTL:
+    case bidi_test::kDirectionRTL:
       return "RTL";
     default:
       // This should never be reached.
@@ -84,187 +84,190 @@
 template <class Runner>
 class Harness {
  public:
-  Harness(Runner& runner) : m_runner(runner) {}
-  void parse(std::istream& bidiTestFile);
+  Harness(Runner& runner) : runner_(runner) {}
+  void Parse(std::istream& bidi_test_file);
 
  private:
-  Runner& m_runner;
+  Runner& runner_;
 };
 
 // We could use boost::trim, but no other part of Blink uses boost yet.
-inline void ltrim(std::string& s) {
-  static const std::string separators(" \t");
-  s.erase(0, s.find_first_not_of(separators));
+inline void Ltrim(std::string& s) {
+  static const std::string kSeparators(" \t");
+  s.erase(0, s.find_first_not_of(kSeparators));
 }
 
-inline void rtrim(std::string& s) {
-  static const std::string separators(" \t");
-  size_t lastNonSpace = s.find_last_not_of(separators);
-  if (lastNonSpace == std::string::npos) {
+inline void Rtrim(std::string& s) {
+  static const std::string kSeparators(" \t");
+  size_t last_non_space = s.find_last_not_of(kSeparators);
+  if (last_non_space == std::string::npos) {
     s.erase();
     return;
   }
-  size_t firstSpaceAtEndOfString = lastNonSpace + 1;
-  if (firstSpaceAtEndOfString >= s.size())
+  size_t first_space_at_end_of_string = last_non_space + 1;
+  if (first_space_at_end_of_string >= s.size())
     return;  // lastNonSpace was the last char.
-  s.erase(firstSpaceAtEndOfString,
+  s.erase(first_space_at_end_of_string,
           std::string::npos);  // erase to the end of the string.
 }
 
-inline void trim(std::string& s) {
-  rtrim(s);
-  ltrim(s);
+inline void Trim(std::string& s) {
+  Rtrim(s);
+  Ltrim(s);
 }
 
-static std::vector<std::string> parseStringList(const std::string& str) {
+static std::vector<std::string> ParseStringList(const std::string& str) {
   std::vector<std::string> strings;
-  static const std::string separators(" \t");
-  size_t lastPos = str.find_first_not_of(separators);   // skip leading spaces
-  size_t pos = str.find_first_of(separators, lastPos);  // find next space
+  static const std::string kSeparators(" \t");
+  size_t last_pos = str.find_first_not_of(kSeparators);   // skip leading spaces
+  size_t pos = str.find_first_of(kSeparators, last_pos);  // find next space
 
-  while (std::string::npos != pos || std::string::npos != lastPos) {
-    strings.push_back(str.substr(lastPos, pos - lastPos));
-    lastPos = str.find_first_not_of(separators, pos);
-    pos = str.find_first_of(separators, lastPos);
+  while (std::string::npos != pos || std::string::npos != last_pos) {
+    strings.push_back(str.substr(last_pos, pos - last_pos));
+    last_pos = str.find_first_not_of(kSeparators, pos);
+    pos = str.find_first_of(kSeparators, last_pos);
   }
   return strings;
 }
 
-static int parseInt(const std::string& str) {
+static int ParseInt(const std::string& str) {
   return atoi(str.c_str());
 }
 
-static std::vector<int> parseIntList(const std::string& str) {
+static std::vector<int> ParseIntList(const std::string& str) {
   std::vector<int> ints;
-  std::vector<std::string> strings = parseStringList(str);
+  std::vector<std::string> strings = ParseStringList(str);
   for (size_t x = 0; x < strings.size(); x++) {
-    int i = parseInt(strings[x]);
+    int i = ParseInt(strings[x]);
     ints.push_back(i);
   }
   return ints;
 }
 
-static std::vector<int> parseLevels(const std::string& line) {
+static std::vector<int> ParseLevels(const std::string& line) {
   std::vector<int> levels;
-  std::vector<std::string> strings = parseStringList(line);
+  std::vector<std::string> strings = ParseStringList(line);
   for (size_t x = 0; x < strings.size(); x++) {
-    const std::string& levelString = strings[x];
+    const std::string& level_string = strings[x];
     int i;
-    if (levelString == "x")
+    if (level_string == "x")
       i = -1;
     else
-      i = parseInt(levelString);
+      i = ParseInt(level_string);
     levels.push_back(i);
   }
   return levels;
 }
 
 // This is not thread-safe as written.
-static std::basic_string<UChar> parseTestString(const std::string& line) {
-  std::basic_string<UChar> testString;
-  static std::map<std::string, UChar> charClassExamples;
-  if (charClassExamples.empty()) {
+static std::basic_string<UChar> ParseTestString(const std::string& line) {
+  std::basic_string<UChar> test_string;
+  static std::map<std::string, UChar> char_class_examples;
+  if (char_class_examples.empty()) {
     // FIXME: Explicit make_pair is ugly, but required for C++98 compat.
-    charClassExamples.insert(std::make_pair("L", 0x6c));     // 'l' for L
-    charClassExamples.insert(std::make_pair("R", 0x05D0));   // HEBREW ALEF
-    charClassExamples.insert(std::make_pair("EN", 0x33));    // '3' for EN
-    charClassExamples.insert(std::make_pair("ES", 0x2d));    // '-' for ES
-    charClassExamples.insert(std::make_pair("ET", 0x25));    // '%' for ET
-    charClassExamples.insert(std::make_pair("AN", 0x0660));  // arabic 0
-    charClassExamples.insert(std::make_pair("CS", 0x2c));    // ',' for CS
-    charClassExamples.insert(std::make_pair("B", 0x0A));     // <control-000A>
-    charClassExamples.insert(std::make_pair("S", 0x09));     // <control-0009>
-    charClassExamples.insert(std::make_pair("WS", 0x20));    // ' ' for WS
-    charClassExamples.insert(std::make_pair("ON", 0x3d));    // '=' for ON
-    charClassExamples.insert(
+    char_class_examples.insert(std::make_pair("L", 0x6c));     // 'l' for L
+    char_class_examples.insert(std::make_pair("R", 0x05D0));   // HEBREW ALEF
+    char_class_examples.insert(std::make_pair("EN", 0x33));    // '3' for EN
+    char_class_examples.insert(std::make_pair("ES", 0x2d));    // '-' for ES
+    char_class_examples.insert(std::make_pair("ET", 0x25));    // '%' for ET
+    char_class_examples.insert(std::make_pair("AN", 0x0660));  // arabic 0
+    char_class_examples.insert(std::make_pair("CS", 0x2c));    // ',' for CS
+    char_class_examples.insert(std::make_pair("B", 0x0A));     // <control-000A>
+    char_class_examples.insert(std::make_pair("S", 0x09));     // <control-0009>
+    char_class_examples.insert(std::make_pair("WS", 0x20));    // ' ' for WS
+    char_class_examples.insert(std::make_pair("ON", 0x3d));    // '=' for ON
+    char_class_examples.insert(
         std::make_pair("NSM", 0x05BF));  // HEBREW POINT RAFE
-    charClassExamples.insert(std::make_pair("AL", 0x0608));  // ARABIC RAY
-    charClassExamples.insert(std::make_pair("BN", 0x00AD));  // SOFT HYPHEN
-    charClassExamples.insert(std::make_pair("LRE", 0x202A));
-    charClassExamples.insert(std::make_pair("RLE", 0x202B));
-    charClassExamples.insert(std::make_pair("PDF", 0x202C));
-    charClassExamples.insert(std::make_pair("LRO", 0x202D));
-    charClassExamples.insert(std::make_pair("RLO", 0x202E));
-    charClassExamples.insert(std::make_pair("LRI", 0x2066));
-    charClassExamples.insert(std::make_pair("RLI", 0x2067));
-    charClassExamples.insert(std::make_pair("FSI", 0x2068));
-    charClassExamples.insert(std::make_pair("PDI", 0x2069));
+    char_class_examples.insert(std::make_pair("AL", 0x0608));  // ARABIC RAY
+    char_class_examples.insert(std::make_pair("BN", 0x00AD));  // SOFT HYPHEN
+    char_class_examples.insert(std::make_pair("LRE", 0x202A));
+    char_class_examples.insert(std::make_pair("RLE", 0x202B));
+    char_class_examples.insert(std::make_pair("PDF", 0x202C));
+    char_class_examples.insert(std::make_pair("LRO", 0x202D));
+    char_class_examples.insert(std::make_pair("RLO", 0x202E));
+    char_class_examples.insert(std::make_pair("LRI", 0x2066));
+    char_class_examples.insert(std::make_pair("RLI", 0x2067));
+    char_class_examples.insert(std::make_pair("FSI", 0x2068));
+    char_class_examples.insert(std::make_pair("PDI", 0x2069));
   }
 
-  std::vector<std::string> charClasses = parseStringList(line);
-  for (size_t i = 0; i < charClasses.size(); i++) {
+  std::vector<std::string> char_classes = ParseStringList(line);
+  for (size_t i = 0; i < char_classes.size(); i++) {
     // FIXME: If the lookup failed we could return false for a parse error.
-    testString.push_back(charClassExamples.find(charClasses[i])->second);
+    test_string.push_back(char_class_examples.find(char_classes[i])->second);
   }
-  return testString;
+  return test_string;
 }
 
-static bool parseParagraphDirectionMask(const std::string& line,
-                                        int& modeMask) {
-  modeMask = parseInt(line);
-  return modeMask >= 1 && modeMask <= kMaxParagraphDirection;
+static bool ParseParagraphDirectionMask(const std::string& line,
+                                        int& mode_mask) {
+  mode_mask = ParseInt(line);
+  return mode_mask >= 1 && mode_mask <= kMaxParagraphDirection;
 }
 
-static void parseError(const std::string& line, size_t lineNumber) {
+static void ParseError(const std::string& line, size_t line_number) {
   // Use printf to avoid the expense of std::cout.
-  printf("Parse error, line %zu : %s\n", lineNumber, line.c_str());
+  printf("Parse error, line %zu : %s\n", line_number, line.c_str());
 }
 
 template <class Runner>
-void Harness<Runner>::parse(std::istream& bidiTestFile) {
-  static const std::string levelsPrefix("@Levels");
-  static const std::string reorderPrefix("@Reorder");
+void Harness<Runner>::Parse(std::istream& bidi_test_file) {
+  static const std::string kLevelsPrefix("@Levels");
+  static const std::string kReorderPrefix("@Reorder");
 
   // FIXME: UChar is an ICU type and cheating a bit to use here.
   // uint16_t might be more portable.
-  std::basic_string<UChar> testString;
+  std::basic_string<UChar> test_string;
   std::vector<int> levels;
   std::vector<int> reorder;
-  int paragraphDirectionMask;
+  int paragraph_direction_mask;
 
   std::string line;
-  size_t lineNumber = 0;
-  while (std::getline(bidiTestFile, line)) {
-    lineNumber++;
-    const std::string originalLine = line;
-    size_t commentStart = line.find_first_of('#');
-    if (commentStart != std::string::npos)
-      line = line.substr(0, commentStart);
-    trim(line);
+  size_t line_number = 0;
+  while (std::getline(bidi_test_file, line)) {
+    line_number++;
+    const std::string original_line = line;
+    size_t comment_start = line.find_first_of('#');
+    if (comment_start != std::string::npos)
+      line = line.substr(0, comment_start);
+    Trim(line);
     if (line.empty())
       continue;
     if (line[0] == '@') {
-      if (!line.find(levelsPrefix)) {
-        levels = parseLevels(line.substr(levelsPrefix.length() + 1));
+      if (!line.find(kLevelsPrefix)) {
+        levels = ParseLevels(line.substr(kLevelsPrefix.length() + 1));
         continue;
       }
-      if (!line.find(reorderPrefix)) {
-        reorder = parseIntList(line.substr(reorderPrefix.length() + 1));
+      if (!line.find(kReorderPrefix)) {
+        reorder = ParseIntList(line.substr(kReorderPrefix.length() + 1));
         continue;
       }
     } else {
       // Assume it's a data line.
-      size_t seperatorIndex = line.find_first_of(';');
-      if (seperatorIndex == std::string::npos) {
-        parseError(originalLine, lineNumber);
+      size_t seperator_index = line.find_first_of(';');
+      if (seperator_index == std::string::npos) {
+        ParseError(original_line, line_number);
         continue;
       }
-      testString = parseTestString(line.substr(0, seperatorIndex));
-      if (!parseParagraphDirectionMask(line.substr(seperatorIndex + 1),
-                                       paragraphDirectionMask)) {
-        parseError(originalLine, lineNumber);
+      test_string = ParseTestString(line.substr(0, seperator_index));
+      if (!ParseParagraphDirectionMask(line.substr(seperator_index + 1),
+                                       paragraph_direction_mask)) {
+        ParseError(original_line, line_number);
         continue;
       }
 
-      if (paragraphDirectionMask & DirectionAutoLTR)
-        m_runner.RunTest(testString, reorder, levels, DirectionAutoLTR,
-                         originalLine, lineNumber);
-      if (paragraphDirectionMask & DirectionLTR)
-        m_runner.RunTest(testString, reorder, levels, DirectionLTR,
-                         originalLine, lineNumber);
-      if (paragraphDirectionMask & DirectionRTL)
-        m_runner.RunTest(testString, reorder, levels, DirectionRTL,
-                         originalLine, lineNumber);
+      if (paragraph_direction_mask & kDirectionAutoLTR) {
+        runner_.RunTest(test_string, reorder, levels, kDirectionAutoLTR,
+                         original_line, line_number);
+      }
+      if (paragraph_direction_mask & kDirectionLTR) {
+        runner_.RunTest(test_string, reorder, levels, kDirectionLTR,
+                         original_line, line_number);
+      }
+      if (paragraph_direction_mask & kDirectionRTL) {
+        runner_.RunTest(test_string, reorder, levels, kDirectionRTL,
+                         original_line, line_number);
+      }
     }
   }
 }
@@ -272,17 +275,17 @@
 template <class Runner>
 class CharacterHarness {
  public:
-  CharacterHarness(Runner& runner) : m_runner(runner) {}
-  void parse(std::istream& bidiTestFile);
+  CharacterHarness(Runner& runner) : runner_(runner) {}
+  void Parse(std::istream& bidi_test_file);
 
  private:
-  Runner& m_runner;
+  Runner& runner_;
 };
 
-static std::basic_string<UChar> parseUCharHexadecimalList(
+static std::basic_string<UChar> ParseUCharHexadecimalList(
     const std::string& str) {
   std::basic_string<UChar> string;
-  std::vector<std::string> strings = parseStringList(str);
+  std::vector<std::string> strings = ParseStringList(str);
   for (size_t x = 0; x < strings.size(); x++) {
     int i = strtol(strings[x].c_str(), nullptr, 16);
     string.push_back((UChar)i);
@@ -290,119 +293,119 @@
   return string;
 }
 
-static ParagraphDirection parseParagraphDirection(const std::string& str) {
-  int i = parseInt(str);
+static ParagraphDirection ParseParagraphDirection(const std::string& str) {
+  int i = ParseInt(str);
   switch (i) {
     case 0:
-      return DirectionLTR;
+      return kDirectionLTR;
     case 1:
-      return DirectionRTL;
+      return kDirectionRTL;
     case 2:
-      return DirectionAutoLTR;
+      return kDirectionAutoLTR;
     default:
-      return DirectionNone;
+      return kDirectionNone;
   }
 }
 
-static int parseSuppresedChars(const std::string& str) {
-  std::vector<std::string> strings = parseStringList(str);
-  int suppresedChars = 0;
+static int ParseSuppresedChars(const std::string& str) {
+  std::vector<std::string> strings = ParseStringList(str);
+  int suppresed_chars = 0;
   for (size_t x = 0; x < strings.size(); x++) {
     if (strings[x] == "x")
-      suppresedChars++;
+      suppresed_chars++;
   }
-  return suppresedChars;
+  return suppresed_chars;
 }
 
 template <class Runner>
-void CharacterHarness<Runner>::parse(std::istream& bidiTestFile) {
+void CharacterHarness<Runner>::Parse(std::istream& bidi_test_file) {
   std::string line;
-  size_t lineNumber = 0;
-  while (std::getline(bidiTestFile, line)) {
-    lineNumber++;
+  size_t line_number = 0;
+  while (std::getline(bidi_test_file, line)) {
+    line_number++;
 
-    const std::string originalLine = line;
-    size_t commentStart = line.find_first_of('#');
-    if (commentStart != std::string::npos)
-      line = line.substr(0, commentStart);
-    trim(line);
+    const std::string original_line = line;
+    size_t comment_start = line.find_first_of('#');
+    if (comment_start != std::string::npos)
+      line = line.substr(0, comment_start);
+    Trim(line);
     if (line.empty())
       continue;
 
     // Field 0: list of uchars as 4 char strings
-    size_t separatorIndex = line.find_first_of(';');
-    if (separatorIndex == std::string::npos) {
-      parseError(originalLine, lineNumber);
+    size_t separator_index = line.find_first_of(';');
+    if (separator_index == std::string::npos) {
+      ParseError(original_line, line_number);
       continue;
     }
 
-    std::basic_string<UChar> testString =
-        parseUCharHexadecimalList(line.substr(0, separatorIndex));
-    if (testString.empty()) {
-      parseError(originalLine, lineNumber);
+    std::basic_string<UChar> test_string =
+        ParseUCharHexadecimalList(line.substr(0, separator_index));
+    if (test_string.empty()) {
+      ParseError(original_line, line_number);
       continue;
     }
-    line = line.substr(separatorIndex + 1);
+    line = line.substr(separator_index + 1);
 
     // Field 1: paragraph direction (0 LTR, 1 RTL, 2 AutoLTR)
-    separatorIndex = line.find_first_of(';');
-    if (separatorIndex == std::string::npos) {
-      parseError(originalLine, lineNumber);
+    separator_index = line.find_first_of(';');
+    if (separator_index == std::string::npos) {
+      ParseError(original_line, line_number);
       continue;
     }
 
-    ParagraphDirection paragraphDirection =
-        parseParagraphDirection(line.substr(0, separatorIndex));
-    if (paragraphDirection == DirectionNone) {
-      parseError(originalLine, lineNumber);
+    ParagraphDirection paragraph_direction =
+        ParseParagraphDirection(line.substr(0, separator_index));
+    if (paragraph_direction == kDirectionNone) {
+      ParseError(original_line, line_number);
       continue;
     }
-    line = line.substr(separatorIndex + 1);
+    line = line.substr(separator_index + 1);
 
     // Field 2: resolved paragraph embedding level
-    separatorIndex = line.find_first_of(';');
-    if (separatorIndex == std::string::npos) {
-      parseError(originalLine, lineNumber);
+    separator_index = line.find_first_of(';');
+    if (separator_index == std::string::npos) {
+      ParseError(original_line, line_number);
       continue;
     }
 
-    int paragraphEmbeddingLevel = parseInt(line.substr(0, separatorIndex));
-    if (paragraphEmbeddingLevel < 0) {
-      parseError(originalLine, lineNumber);
+    int paragraph_embedding_level = ParseInt(line.substr(0, separator_index));
+    if (paragraph_embedding_level < 0) {
+      ParseError(original_line, line_number);
       continue;
     }
-    line = line.substr(separatorIndex + 1);
+    line = line.substr(separator_index + 1);
 
     // Field 3: List of resolved levels
-    separatorIndex = line.find_first_of(';');
-    if (separatorIndex == std::string::npos) {
-      parseError(originalLine, lineNumber);
+    separator_index = line.find_first_of(';');
+    if (separator_index == std::string::npos) {
+      ParseError(original_line, line_number);
       continue;
     }
 
-    int supressedChars = parseSuppresedChars(line.substr(0, separatorIndex));
-    std::vector<int> levels = parseLevels(line.substr(0, separatorIndex));
-    if (testString.size() != levels.size()) {
-      parseError(originalLine, lineNumber);
+    int supressed_chars = ParseSuppresedChars(line.substr(0, separator_index));
+    std::vector<int> levels = ParseLevels(line.substr(0, separator_index));
+    if (test_string.size() != levels.size()) {
+      ParseError(original_line, line_number);
       continue;
     }
-    line = line.substr(separatorIndex + 1);
+    line = line.substr(separator_index + 1);
 
     // Field 4: visual ordering of characters
-    separatorIndex = line.find_first_of(';');
-    if (separatorIndex != std::string::npos) {
-      parseError(originalLine, lineNumber);
+    separator_index = line.find_first_of(';');
+    if (separator_index != std::string::npos) {
+      ParseError(original_line, line_number);
       continue;
     }
 
-    std::vector<int> visualOrdering = parseIntList(line);
-    if (testString.size() - supressedChars != visualOrdering.size()) {
-      parseError(originalLine, lineNumber);
+    std::vector<int> visual_ordering = ParseIntList(line);
+    if (test_string.size() - supressed_chars != visual_ordering.size()) {
+      ParseError(original_line, line_number);
       continue;
     }
 
-    m_runner.RunTest(testString, visualOrdering, levels, paragraphDirection,
-                     originalLine, lineNumber);
+    runner_.RunTest(test_string, visual_ordering, levels, paragraph_direction,
+                     original_line, line_number);
   }
 }
 
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/common.py b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/common.py
index d9a641d..17751ff 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/common.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/common.py
@@ -11,7 +11,11 @@
 
 
 WPT_DEST_NAME = 'wpt'
-WPT_GH_REPO_URL_TEMPLATE = 'https://{}@github.com/w3c/web-platform-tests.git'
+WPT_GH_ORG = 'w3c'
+WPT_GH_REPO_NAME = 'web-platform-tests'
+WPT_GH_URL = 'https://github.com/%s/%s/' % (WPT_GH_ORG, WPT_GH_REPO_NAME)
+WPT_GH_SSH_URL_TEMPLATE = 'https://{}@github.com/%s/%s.git' % (WPT_GH_ORG, WPT_GH_REPO_NAME)
+WPT_REVISION_FOOTER = 'WPT-Export-Revision:'
 
 # TODO(qyearsley): This directory should be able to be constructed with
 # WebKitFinder and WPT_DEST_NAME, plus the string "external".
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/gerrit.py b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/gerrit.py
index a9007997a..859ae347 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/gerrit.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/gerrit.py
@@ -12,7 +12,7 @@
 URL_BASE = 'https://chromium-review.googlesource.com'
 
 
-class Gerrit(object):
+class GerritAPI(object):
     """A utility class for the Chromium code review API.
 
     Wraps the API for Chromium's Gerrit instance at chromium-review.googlesource.com.
@@ -23,13 +23,18 @@
         self.user = user
         self.token = token
 
-    def api_get(self, url):
+    def get(self, path, raw=False):
+        url = URL_BASE + path
         raw_data = self.host.web.get_binary(url)
-        return json.loads(raw_data[5:])  # strip JSONP preamble
+        if raw:
+            return raw_data
 
-    def api_post(self, url, data):
-        if not (self.user and self.token):
-            raise 'Gerrit user and token requried for authenticated routes.'
+        # Gerrit API responses are prefixed by a 5-character JSONP preamble
+        return json.loads(raw_data[5:])
+
+    def post(self, path, data):
+        url = URL_BASE + path
+        assert self.user and self.token, 'Gerrit user and token required for authenticated routes.'
 
         b64auth = base64.b64encode('{}:{}'.format(self.user, self.token))
         headers = {
@@ -38,54 +43,76 @@
         }
         return self.host.web.request('POST', url, data=json.dumps(data), headers=headers)
 
-    def cl_url(self, number):
-        return 'https://chromium-review.googlesource.com/c/{}/'.format(number)
+    def query_exportable_open_cls(self, limit=200):
+        path = ('/changes/?q=project:\"chromium/src\"+status:open'
+                '&o=CURRENT_FILES&o=CURRENT_REVISION&o=COMMIT_FOOTERS'
+                '&o=DETAILED_ACCOUNTS&n={}').format(limit)
+        open_cls_data = self.get(path)
+        open_cls = [GerritCL(data, self) for data in open_cls_data]
 
-    def get_diff(self, cl):
-        """Get full diff for latest revision of CL."""
-        revision = cl['revisions'].items()[0][1]
-        revision_id = cl['revisions'].items()[0][0]
-        files = revision['files'].keys()
-        diff = ''
+        return [cl for cl in open_cls if cl.is_exportable()]
 
-        for filename in files:
-            url = '{url_base}/changes/{change_id}/revisions/{revision_id}/files/{file_id}/diff'.format(
-                url_base=URL_BASE,
-                change_id=cl['id'],
-                revision_id=revision_id,
-                file_id=urllib.quote_plus(filename),
-            )
-            data = self.api_get(url)
-            diff += data
 
-        return diff
+class GerritCL(object):
+    """A data wrapper for a Chromium Gerrit CL."""
 
-    def post_comment(self, cl_id, revision_id, message):
-        url = '{url_base}/a/changes/{change_id}/revisions/{revision_id}/review'.format(
-            url_base=URL_BASE,
-            change_id=cl_id,
-            revision_id=revision_id,
+    def __init__(self, data, api):
+        assert data['change_id']
+        self._data = data
+        self.api = api
+
+    @property
+    def url(self):
+        return 'https://chromium-review.googlesource.com/c/%s' % self._data['_number']
+
+    @property
+    def subject(self):
+        return self._data['subject']
+
+    @property
+    def change_id(self):
+        return self._data['change_id']
+
+    @property
+    def owner_email(self):
+        return self._data['owner']['email']
+
+    @property
+    def current_revision_sha(self):
+        return self._data['current_revision']
+
+    @property
+    def current_revision(self):
+        return self._data['revisions'][self.current_revision_sha]
+
+    def latest_commit_message_with_footers(self):
+        return self.current_revision['commit_with_footers']
+
+    @property
+    def current_revision_description(self):
+        return self.current_revision['description']
+
+    def post_comment(self, message):
+        path = '/a/changes/{change_id}/revisions/current/review'.format(
+            change_id=self.change_id,
         )
-        return self.api_post(url, {'message': message})
+        return self.api.post(path, {'message': message})
 
-    def query_open_cls(self, limit=200):
-        url = ('{}/changes/?q=project:\"chromium/src\"+status:open'
-               '&o=CURRENT_FILES&o=CURRENT_REVISION&o=COMMIT_FOOTERS&n={}').format(URL_BASE, limit)
-        open_cls = self.api_get(url)
+    def is_exportable(self):
+        files = self.current_revision['files'].keys()
 
-        return [cl for cl in open_cls if self.is_exportable(cl)]
-
-    def is_exportable(self, cl):
-        revision = cl['revisions'].items()[0][1]
-        files = revision['files'].keys()
-
-        if cl['subject'].startswith('Import wpt@'):
+        # Guard against accidental CLs that touch thousands of files.
+        if len(files) > 1000:
+            _log.info('Rejecting CL with over 1000 files: %s (ID: %s) ', self.subject, self.change_id)
             return False
 
-        if 'Import' in cl['subject']:
+        if self.subject.startswith('Import wpt@'):
             return False
 
-        if 'NOEXPORT=true' in revision['commit_with_footers']:
+        if 'Import' in self.subject:
+            return False
+
+        if 'NOEXPORT=true' in self.current_revision['commit_with_footers']:
             return False
 
         files_in_wpt = [f for f in files if f.startswith('third_party/WebKit/LayoutTests/external/wpt')]
@@ -106,3 +133,8 @@
             and not filename.startswith('.')
             and not filename.endswith('.json')
         )
+
+    def get_patch(self):
+        """Gets patch for latest revision of CL."""
+        path = '/changes/{change_id}/revisions/current/patch'.format(change_id=self.change_id)
+        return base64.b64decode(self.api.get(path, raw=True))
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/gerrit_mock.py b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/gerrit_mock.py
index 72e523b..65978b7 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/gerrit_mock.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/gerrit_mock.py
@@ -3,12 +3,18 @@
 # found in the LICENSE file.
 
 
-class MockGerrit(object):
+class MockGerritAPI(object):
 
     def __init__(self, host, user, token):
         self.host = host
         self.user = user
         self.token = token
 
-    def query_open_cls(self):
+    def query_exportable_open_cls(self):
         return []
+
+    def get(self, path, raw=False):  # pylint: disable=unused-argument
+        return '' if raw else {}
+
+    def post(self, path, data):  # pylint: disable=unused-argument
+        return {}
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/local_wpt.py b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/local_wpt.py
index cfdc3e42..20e4844 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/local_wpt.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/local_wpt.py
@@ -8,7 +8,7 @@
 
 from webkitpy.common.system.executive import ScriptError
 from webkitpy.w3c.chromium_commit import ChromiumCommit
-from webkitpy.w3c.common import WPT_GH_REPO_URL_TEMPLATE, CHROMIUM_WPT_DIR
+from webkitpy.w3c.common import WPT_GH_SSH_URL_TEMPLATE, CHROMIUM_WPT_DIR
 
 
 _log = logging.getLogger(__name__)
@@ -36,7 +36,7 @@
             self.run(['git', 'checkout', 'origin/master'])
         else:
             _log.info('Cloning GitHub w3c/web-platform-tests into %s', self.path)
-            remote_url = WPT_GH_REPO_URL_TEMPLATE.format(self.gh_token)
+            remote_url = WPT_GH_SSH_URL_TEMPLATE.format(self.gh_token)
             self.host.executive.run_command(['git', 'clone', remote_url, self.path])
 
     def run(self, command, **kwargs):
@@ -62,7 +62,7 @@
         self.run(['git', 'clean', '-fdx'])
         self.run(['git', 'checkout', 'origin/master'])
 
-    def create_branch_with_patch(self, branch_name, message, patch, author):
+    def create_branch_with_patch(self, branch_name, message, patch, author, force_push=False):
         """Commits the given patch and pushes to the upstream repo.
 
         Args:
@@ -70,9 +70,20 @@
             message: Commit message string.
             patch: A patch that can be applied by git apply.
             author: The git commit author.
+            force_push: Applies the -f flag in `git push`.
         """
         self.clean()
 
+        try:
+            # This won't be exercised in production because wpt-exporter
+            # always runs on a clean machine. But it's useful when running
+            # locally since branches stick around.
+            _log.info('Deleting old branch %s', branch_name)
+            self.run(['git', 'branch', '-D', branch_name])
+        except ScriptError:
+            # Ignore errors if branch not found.
+            pass
+
         _log.info('Creating local branch %s', branch_name)
         self.run(['git', 'checkout', '-b', branch_name])
 
@@ -84,7 +95,13 @@
         self.run(['git', 'apply', '-'], input=patch)
         self.run(['git', 'add', '.'])
         self.run(['git', 'commit', '--author', author, '-am', message])
-        self.run(['git', 'push', 'origin', branch_name])
+
+        # Force push is necessary when updating a PR with a new patch
+        # from Gerrit.
+        if force_push:
+            self.run(['git', 'push', '-f', 'origin', branch_name])
+        else:
+            self.run(['git', 'push', 'origin', branch_name])
 
     def test_patch(self, patch, chromium_commit=None):
         """Returns the expected output of a patch against origin/master.
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/local_wpt_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/local_wpt_unittest.py
index c5ebd58..cf623b9 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/local_wpt_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/local_wpt_unittest.py
@@ -93,6 +93,7 @@
             ['git', 'reset', '--hard', 'HEAD'],
             ['git', 'clean', '-fdx'],
             ['git', 'checkout', 'origin/master'],
+            ['git', 'branch', '-D', 'chromium-export-decafbad'],
             ['git', 'checkout', '-b', 'chromium-export-decafbad'],
             ['git', 'apply', '-'],
             ['git', 'add', '.'],
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_exporter.py b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_exporter.py
index 4760610..6e1a364 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_exporter.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_exporter.py
@@ -5,15 +5,18 @@
 import logging
 
 from webkitpy.w3c.local_wpt import LocalWPT
-from webkitpy.w3c.common import exportable_commits_over_last_n_commits
-from webkitpy.w3c.gerrit import Gerrit
+from webkitpy.w3c.common import (
+    exportable_commits_over_last_n_commits,
+    WPT_GH_URL,
+    WPT_REVISION_FOOTER
+)
+from webkitpy.w3c.gerrit import GerritAPI, GerritCL
 from webkitpy.w3c.wpt_github import WPTGitHub, MergeError
 
 _log = logging.getLogger(__name__)
 
 PR_HISTORY_WINDOW = 100
 COMMIT_HISTORY_WINDOW = 5000
-WPT_URL = 'https://github.com/w3c/web-platform-tests/'
 
 
 class TestExporter(object):
@@ -22,7 +25,7 @@
         self.host = host
         self.wpt_github = WPTGitHub(host, gh_user, gh_token, pr_history_window=PR_HISTORY_WINDOW)
 
-        self.gerrit = Gerrit(self.host, gerrit_user, gerrit_token)
+        self.gerrit = GerritAPI(self.host, gerrit_user, gerrit_token)
 
         self.dry_run = dry_run
         self.local_wpt = LocalWPT(self.host, gh_token)
@@ -33,39 +36,43 @@
 
         The exporter will look in chronological order at every commit in Chromium.
         """
-        open_gerrit_cls = self.gerrit.query_open_cls()
+        open_gerrit_cls = self.gerrit.query_exportable_open_cls()
         self.process_gerrit_cls(open_gerrit_cls)
 
         exportable_commits = self.get_exportable_commits(limit=COMMIT_HISTORY_WINDOW)
         for exportable_commit in exportable_commits:
             pull_request = self.wpt_github.pr_with_position(exportable_commit.position)
+
             if pull_request:
                 if pull_request.state == 'open':
                     self.merge_pull_request(pull_request)
-                    # TODO(jeffcarp): if this was from Gerrit, comment back on the Gerrit CL that the PR was merged
                 else:
                     _log.info('Pull request is not open: #%d %s', pull_request.number, pull_request.title)
             else:
                 self.create_pull_request(exportable_commit)
 
     def process_gerrit_cls(self, gerrit_cls):
-        """Iterates through Gerrit CLs and prints their statuses.
-
-        Right now this method does nothing. In the future it will create PRs for CLs and help
-        transition them when they're landed.
-        """
+        """Creates or updates PRs for Gerrit CLs."""
         for cl in gerrit_cls:
-            cl_url = 'https://chromium-review.googlesource.com/c/%s' % cl['_number']
-            _log.info('Found Gerrit in-flight CL: "%s" %s', cl['subject'], cl_url)
+            _log.info('Found Gerrit in-flight CL: "%s" %s', cl.subject, cl.url)
 
             # Check if CL already has a corresponding PR
-            pull_request = self.wpt_github.pr_with_change_id(cl['change_id'])
+            pull_request = self.wpt_github.pr_with_change_id(cl.change_id)
 
             if pull_request:
-                pr_url = '{}pull/{}'.format(WPT_URL, pull_request.number)
+                pr_url = '{}pull/{}'.format(WPT_GH_URL, pull_request.number)
                 _log.info('In-flight PR found: %s', pr_url)
+
+                pr_cl_revision = self.wpt_github.extract_metadata(WPT_REVISION_FOOTER + ' ', pull_request.body)
+                if cl.current_revision_sha == pr_cl_revision:
+                    _log.info('PR revision matches CL revision. Nothing to do here.')
+                    continue
+
+                _log.info('New revision found, updating PR...')
+                self.create_or_update_pull_request_from_cl(cl, pull_request)
             else:
-                _log.info('No in-flight PR found for CL.')
+                _log.info('No in-flight PR found for CL. Creating...')
+                self.create_or_update_pull_request_from_cl(cl)
 
 
     def get_exportable_commits(self, limit):
@@ -73,7 +80,7 @@
 
     def merge_pull_request(self, pull_request):
         _log.info('In-flight PR found: %s', pull_request.title)
-        _log.info('https://github.com/w3c/web-platform-tests/pull/%d', pull_request.number)
+        _log.info('%spull/%d', WPT_GH_URL, pull_request.number)
 
         if self.dry_run:
             _log.info('[dry_run] Would have attempted to merge PR')
@@ -91,6 +98,17 @@
             # This is in the try block because if a PR can't be merged, we shouldn't
             # delete its branch.
             self.wpt_github.delete_remote_branch(branch)
+
+            change_id = self.wpt_github.extract_metadata('Change-Id: ', pull_request.body)
+            if change_id:
+                cl = GerritCL(data={'change_id': change_id}, api=self.gerrit)
+                pr_url = '{}pull/{}'.format(WPT_GH_URL, pull_request.number)
+                cl.post_comment((
+                    'The WPT PR for this CL has been merged upstream! {pr_url}'
+                ).format(
+                    pr_url=pr_url
+                ))
+
         except MergeError:
             _log.info('Could not merge PR.')
 
@@ -154,3 +172,61 @@
             _log.info('Add label response (status %s): %s', status_code, data)
 
         return response_data
+
+    def create_or_update_pull_request_from_cl(self, cl, pull_request=None):
+        patch = cl.get_patch()
+        updating = bool(pull_request)
+        action_str = 'updating' if updating else 'creating'
+
+        if self.local_wpt.test_patch(patch) == '':
+            _log.error('Gerrit CL patch did not apply cleanly.')
+            _log.error('First 500 characters of patch: %s', patch[0:500])
+            return
+
+        if self.dry_run:
+            _log.info('[dry_run] Stopping before %s PR from CL', action_str)
+            _log.info('\n\n[dry_run] subject:')
+            _log.info(cl.subject)
+            _log.debug('\n\n[dry_run] patch[0:500]:')
+            _log.debug(patch[0:500])
+            return
+
+        message = cl.latest_commit_message_with_footers()
+
+        # Annotate revision footer for Exporter's later use.
+        message = '\n'.join([line for line in message.split('\n') if WPT_REVISION_FOOTER not in line])
+        message += '\n{} {}'.format(WPT_REVISION_FOOTER, cl.current_revision_sha)
+
+        branch_name = 'chromium-export-cl-{id}'.format(id=cl.change_id)
+        self.local_wpt.create_branch_with_patch(branch_name, message, patch, cl.owner_email, force_push=True)
+
+        if updating:
+            response_data = self.wpt_github.update_pr(pull_request.number, cl.subject, message)
+            _log.debug('Update PR response: %s', response_data)
+
+            # TODO(jeffcarp): Turn PullRequest into a class with a .url method
+
+            cl.post_comment((
+                'Successfully updated WPT GitHub pull request with '
+                'new revision "{subject}": {pr_url}'
+            ).format(
+                subject=cl.current_revision_description,
+                pr_url='%spull/%d' % (WPT_GH_URL, pull_request.number),
+            ))
+        else:
+            response_data = self.wpt_github.create_pr(branch_name, cl.subject, message)
+            _log.debug('Create PR response: %s', response_data)
+
+            data, status_code = self.wpt_github.add_label(response_data['number'])
+            _log.info('Add label response (status %s): %s', status_code, data)
+
+            cl.post_comment((
+                'Exportable changes to web-platform-tests were detected in this CL '
+                'and a pull request in the upstream repo has been made: {pr_url}.\n\n'
+                'Travis CI has been kicked off and if it fails, we will let you know here. '
+                'If this CL lands and Travis CI is green, we will auto-merge the PR.'
+            ).format(
+                pr_url='%spull/%d' % (WPT_GH_URL, response_data['number'])
+            ))
+
+        return response_data
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_exporter_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_exporter_unittest.py
index 73b01179..b4e07fdb 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_exporter_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_exporter_unittest.py
@@ -10,7 +10,8 @@
 from webkitpy.w3c.test_exporter import TestExporter
 from webkitpy.w3c.wpt_github import PullRequest
 from webkitpy.w3c.wpt_github_mock import MockWPTGitHub
-from webkitpy.w3c.gerrit_mock import MockGerrit
+from webkitpy.w3c.gerrit import GerritCL
+from webkitpy.w3c.gerrit_mock import MockGerritAPI
 
 
 class TestExporterTest(unittest.TestCase):
@@ -28,7 +29,7 @@
         test_exporter.wpt_github = MockWPTGitHub(pull_requests=[
             PullRequest(title='title1', number=1234, body='', state='open'),
         ])
-        test_exporter.gerrit = MockGerrit(host, 'gerrit-username', 'gerrit-token')
+        test_exporter.gerrit = MockGerritAPI(host, 'gerrit-username', 'gerrit-token')
         test_exporter.get_exportable_commits = lambda limit: [
             ChromiumCommit(host, position='refs/heads/master@{#458475}'),
             ChromiumCommit(host, position='refs/heads/master@{#458476}'),
@@ -71,16 +72,19 @@
         host.executive = MockExecutive(run_command_fn=mock_command)
         test_exporter = TestExporter(host, 'gh-username', 'gh-token', gerrit_user=None, gerrit_token=None)
         test_exporter.wpt_github = MockWPTGitHub(pull_requests=[], create_pr_fail_index=1)
-        test_exporter.gerrit = MockGerrit(host, 'gerrit-username', 'gerrit-token')
+        test_exporter.gerrit = MockGerritAPI(host, 'gerrit-username', 'gerrit-token')
         test_exporter.run()
 
         self.assertEqual(test_exporter.wpt_github.calls, [
             'pr_with_position',
             'create_pr',
+            'add_label',
             'pr_with_position',
             'create_pr',
+            'add_label',
             'pr_with_position',
             'create_pr',
+            'add_label',
         ])
         self.assertEqual(test_exporter.wpt_github.pull_requests_created, [
             ('chromium-export-c881563d73', 'older fake text', 'older fake text'),
@@ -119,7 +123,7 @@
                 state='open'
             ),
         ], unsuccessful_merge_index=0)
-        test_exporter.gerrit = MockGerrit(host, 'gerrit-username', 'gerrit-token')
+        test_exporter.gerrit = MockGerritAPI(host, 'gerrit-username', 'gerrit-token')
         test_exporter.get_exportable_commits = lambda limit: [
             ChromiumCommit(host, position='refs/heads/master@{#458475}'),
             ChromiumCommit(host, position='refs/heads/master@{#458476}'),
@@ -133,6 +137,7 @@
             'merge_pull_request',
             'pr_with_position',
             'create_pr',
+            'add_label',
             'pr_with_position',
             'pr_with_position',
             'get_pr_branch',
@@ -144,3 +149,98 @@
              'git show text\nCr-Commit-Position: refs/heads/master@{#458476}',
              'git show text\nCr-Commit-Position: refs/heads/master@{#458476}'),
         ])
+
+    def test_new_gerrit_cl(self):
+        host = MockHost()
+        test_exporter = TestExporter(host, 'gh-username', 'gh-token', gerrit_user=None,
+                                     gerrit_token=None, dry_run=False)
+        test_exporter.wpt_github = MockWPTGitHub(pull_requests=[])
+        test_exporter.get_exportable_commits = lambda limit: []
+        test_exporter.gerrit = MockGerritAPI(host, 'gerrit-username', 'gerrit-token')
+        test_exporter.gerrit.query_exportable_open_cls = lambda: [
+            GerritCL(data={
+                'change_id': '1',
+                'subject': 'subject',
+                '_number': '1',
+                'current_revision': '1',
+                'revisions': {
+                    '1': {'commit_with_footers': 'a commit with footers'}
+                },
+                'owner': {'email': 'test@chromium.org'},
+            }, api=test_exporter.gerrit),
+        ]
+        test_exporter.run()
+        self.assertEqual(test_exporter.wpt_github.calls, [
+            'pr_with_change_id',
+            'create_pr',
+            'add_label',
+        ])
+        self.assertEqual(test_exporter.wpt_github.pull_requests_created, [
+            ('chromium-export-cl-1',
+             'subject',
+             'a commit with footers\nWPT-Export-Revision: 1'),
+        ])
+
+    def test_gerrit_cl_no_update_if_pr_with_same_revision(self):
+        host = MockHost()
+        test_exporter = TestExporter(host, 'gh-username', 'gh-token', gerrit_user=None,
+                                     gerrit_token=None, dry_run=False)
+        test_exporter.wpt_github = MockWPTGitHub(pull_requests=[
+            PullRequest(title='title1', number=1234,
+                        body='description\nWPT-Export-Revision: 1', state='open'),
+        ])
+        test_exporter.get_exportable_commits = lambda limit: []
+        test_exporter.gerrit = MockGerritAPI(host, 'gerrit-username', 'gerrit-token')
+        test_exporter.gerrit.query_exportable_open_cls = lambda: [
+            GerritCL(data={
+                'change_id': '1',
+                'subject': 'subject',
+                '_number': '1',
+                'current_revision': '1',
+                'revisions': {
+                    '1': {'commit_with_footers': 'a commit with footers'}
+                },
+                'owner': {'email': 'test@chromium.org'},
+            }, api=test_exporter.gerrit),
+        ]
+        test_exporter.run()
+        self.assertEqual(test_exporter.wpt_github.calls, [
+            'pr_with_change_id',
+        ])
+        self.assertEqual(test_exporter.wpt_github.pull_requests_created, [])
+
+    def test_gerrit_cl_updates_if_cl_has_new_revision(self):
+        host = MockHost()
+        test_exporter = TestExporter(host, 'gh-username', 'gh-token', gerrit_user=None,
+                                     gerrit_token=None, dry_run=False)
+        test_exporter.wpt_github = MockWPTGitHub(pull_requests=[
+            PullRequest(title='title1', number=1234,
+                        body='description\nWPT-Export-Revision: 1', state='open'),
+        ])
+        test_exporter.get_exportable_commits = lambda limit: []
+        test_exporter.gerrit = MockGerritAPI(host, 'gerrit-username', 'gerrit-token')
+        test_exporter.gerrit.query_exportable_open_cls = lambda: [
+            GerritCL(data={
+                'change_id': '1',
+                'subject': 'subject',
+                '_number': '1',
+                'current_revision': '2',
+                'revisions': {
+                    '1': {
+                        'commit_with_footers': 'a commit with footers 1',
+                        'description': 'subject 1',
+                    },
+                    '2': {
+                        'commit_with_footers': 'a commit with footers 2',
+                        'description': 'subject 2',
+                    },
+                },
+                'owner': {'email': 'test@chromium.org'},
+            }, api=test_exporter.gerrit),
+        ]
+        test_exporter.run()
+        self.assertEqual(test_exporter.wpt_github.calls, [
+            'pr_with_change_id',
+            'update_pr',
+        ])
+        self.assertEqual(test_exporter.wpt_github.pull_requests_created, [])
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/wpt_github.py b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/wpt_github.py
index 2c4c37ca..b0f59ca 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/wpt_github.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/wpt_github.py
@@ -8,6 +8,7 @@
 import urllib2
 
 from collections import namedtuple
+from webkitpy.w3c.common import WPT_GH_ORG, WPT_GH_REPO_NAME
 from webkitpy.common.memoized import memoized
 
 
@@ -63,7 +64,7 @@
         assert desc_title
         assert body
 
-        path = '/repos/w3c/web-platform-tests/pulls'
+        path = '/repos/%s/%s/pulls' % (WPT_GH_ORG, WPT_GH_REPO_NAME)
         body = {
             'title': desc_title,
             'body': body,
@@ -77,13 +78,45 @@
 
         return data
 
+    def update_pr(self, pr_number, desc_title, body):
+        """Updates a PR on GitHub.
+
+        API doc: https://developer.github.com/v3/pulls/#update-a-pull-request
+
+        Returns:
+            A raw response object if successful, None if not.
+        """
+        path = '/repos/{}/{}/pulls/{}'.format(
+            WPT_GH_ORG,
+            WPT_GH_REPO_NAME,
+            pr_number
+        )
+        body = {
+            'title': desc_title,
+            'body': body,
+        }
+        data, status_code = self.request(path, method='PATCH', body=body)
+
+        if status_code != 201:
+            return None
+
+        return data
+
     def add_label(self, number):
-        path = '/repos/w3c/web-platform-tests/issues/%d/labels' % number
+        path = '/repos/%s/%s/issues/%d/labels' % (
+            WPT_GH_ORG,
+            WPT_GH_REPO_NAME,
+            number
+        )
         body = [EXPORT_LABEL]
         return self.request(path, method='POST', body=body)
 
     def in_flight_pull_requests(self):
-        path = '/search/issues?q=repo:w3c/web-platform-tests%20is:open%20type:pr%20label:{}'.format(EXPORT_LABEL)
+        path = '/search/issues?q=repo:{}/{}%20is:open%20type:pr%20label:{}'.format(
+            WPT_GH_ORG,
+            WPT_GH_REPO_NAME,
+            EXPORT_LABEL
+        )
         data, status_code = self.request(path, method='GET')
         if status_code == 200:
             return [self.make_pr_from_item(item) for item in data['items']]
@@ -101,10 +134,17 @@
     def all_pull_requests(self):
         # TODO(jeffcarp): Add pagination to fetch >99 PRs
         assert self._pr_history_window <= 100, 'Maximum GitHub page size exceeded.'
-        path = ('/search/issues'
-                '?q=repo:w3c/web-platform-tests%20type:pr%20label:{}'
-                '&page=1'
-                '&per_page={}').format(EXPORT_LABEL, self._pr_history_window)
+        path = (
+            '/search/issues'
+            '?q=repo:{}/{}%20type:pr%20label:{}'
+            '&page=1'
+            '&per_page={}'
+        ).format(
+            WPT_GH_ORG,
+            WPT_GH_REPO_NAME,
+            EXPORT_LABEL,
+            self._pr_history_window
+        )
         data, status_code = self.request(path, method='GET')
         if status_code == 200:
             return [self.make_pr_from_item(item) for item in data['items']]
@@ -112,7 +152,11 @@
             raise Exception('Non-200 status code (%s): %s' % (status_code, data))
 
     def get_pr_branch(self, pr_number):
-        path = '/repos/w3c/web-platform-tests/pulls/{}'.format(pr_number)
+        path = '/repos/{}/{}/pulls/{}'.format(
+            WPT_GH_ORG,
+            WPT_GH_REPO_NAME,
+            pr_number
+        )
         data, status_code = self.request(path, method='GET')
         if status_code == 200:
             return data['head']['ref']
@@ -120,7 +164,11 @@
             raise Exception('Non-200 status code (%s): %s' % (status_code, data))
 
     def merge_pull_request(self, pull_request_number):
-        path = '/repos/w3c/web-platform-tests/pulls/%d/merge' % pull_request_number
+        path = '/repos/%s/%s/pulls/%d/merge' % (
+            WPT_GH_ORG,
+            WPT_GH_REPO_NAME,
+            pull_request_number
+        )
         body = {
             # This currently will noop because the feature is in an opt-in beta.
             # Once it leaves beta this will start working.
@@ -142,7 +190,11 @@
 
     def delete_remote_branch(self, remote_branch_name):
         # TODO(jeffcarp): Unit test this method
-        path = '/repos/w3c/web-platform-tests/git/refs/heads/%s' % remote_branch_name
+        path = '/repos/%s/%s/git/refs/heads/%s' % (
+            WPT_GH_ORG,
+            WPT_GH_REPO_NAME,
+            remote_branch_name
+        )
         data, status_code = self.request(path, method='DELETE')
 
         if status_code != 204:
@@ -153,19 +205,19 @@
 
     def pr_with_change_id(self, target_change_id):
         for pull_request in self.all_pull_requests():
-            change_id = self._extract_metadata('Change-Id: ', pull_request.body)
+            change_id = self.extract_metadata('Change-Id: ', pull_request.body)
             if change_id == target_change_id:
                 return pull_request
         return None
 
     def pr_with_position(self, position):
         for pull_request in self.all_pull_requests():
-            pr_commit_position = self._extract_metadata('Cr-Commit-Position: ', pull_request.body)
+            pr_commit_position = self.extract_metadata('Cr-Commit-Position: ', pull_request.body)
             if position == pr_commit_position:
                 return pull_request
         return None
 
-    def _extract_metadata(self, tag, commit_body):
+    def extract_metadata(self, tag, commit_body):
         for line in commit_body.splitlines():
             if line.startswith(tag):
                 return line[len(tag):]
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/wpt_github_mock.py b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/wpt_github_mock.py
index 69abba4..9060d20 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/wpt_github_mock.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/wpt_github_mock.py
@@ -32,20 +32,25 @@
     def create_pr(self, remote_branch_name, desc_title, body):
         self.calls.append('create_pr')
 
-        assert remote_branch_name
-        assert desc_title
-        assert body
-
         if self.create_pr_fail_index != self.create_pr_index:
             self.pull_requests_created.append((remote_branch_name, desc_title, body))
 
         self.create_pr_index += 1
 
-        return {}
+        return {'number': 5678}
+
+    def update_pr(self, pr_number, desc_title, body):  # pylint: disable=unused-argument
+        self.calls.append('update_pr')
+
+        return {'number': 5678}
 
     def delete_remote_branch(self, _):
         self.calls.append('delete_remote_branch')
 
+    def add_label(self, _):
+        self.calls.append('add_label')
+        return {}, 200
+
     def get_pr_branch(self, number):
         self.calls.append('get_pr_branch')
         return 'fake branch for PR {}'.format(number)
@@ -61,3 +66,9 @@
         for pr in self.pull_requests:
             if change_id in pr.body:
                 return pr
+
+    def extract_metadata(self, tag, commit_body):
+        for line in commit_body.splitlines():
+            if line.startswith(tag):
+                return line[len(tag):]
+        return None
diff --git a/third_party/WebKit/Tools/Scripts/wpt-export b/third_party/WebKit/Tools/Scripts/wpt-export
index eb26a13..f2057d3 100755
--- a/third_party/WebKit/Tools/Scripts/wpt-export
+++ b/third_party/WebKit/Tools/Scripts/wpt-export
@@ -44,34 +44,46 @@
         help='Gerrit username (found on settings page or in .gitcookies).')
     parser.add_argument('--gerrit-token', default=None,
         help='Gerrit API key (found on settings page or in .gitcookies).')
+
+    # TODO(jeffcarp): Remove this after SSHing into wpt-exporter and updating file
     parser.add_argument(
         '--github-credentials-json',
-        help='A JSON file with schema {"GH_USER": "", "GH_TOKEN": ""}. '
-             'This will override credentials that were passed via command line or env var.')
+        help='Deprecated. Use --credentials-json.')
+
+    parser.add_argument(
+        '--credentials-json',
+        help='A JSON file with an object containing zero or more of the following '
+             'keys that can override command line flags: '
+             'GH_USER, GH_TOKEN, GERRIT_USER, GERRIT_TOKEN')
     args = parser.parse_args()
     host = Host()
-    gh_user = args.gh_user
-    gh_token = args.gh_token
+    credentials = {
+        'GH_USER': args.gh_user,
+        'GH_TOKEN': args.gh_token,
+    }
+    credentials_json = None
 
-    if not gh_user:
-        gh_user = host.environ.get('GH_USER')
-    if not gh_token:
-        gh_token = host.environ.get('GH_TOKEN')
     if args.github_credentials_json:
-        with open(args.github_credentials_json, 'r') as f:
-            contents = json.load(f)
-            gh_user = contents['GH_USER']
-            gh_token = contents['GH_TOKEN']
+        credentials_json = args.github_credentials_json
+    if args.credentials_json:
+        credentials_json = args.credentials_json
 
-    if not (gh_user and gh_token):
+    if credentials_json:
+        with open(credentials_json, 'r') as f:
+            contents = json.load(f)
+            for key in ('GH_USER', 'GH_TOKEN', 'GERRIT_USER', 'GERRIT_TOKEN'):
+                if key in contents:
+                    credentials[key] = contents[key]
+
+    if not (credentials['GH_USER'] and credentials['GH_TOKEN']):
         parser.error('Must provide both gh_user and gh_token for GitHub.')
 
     TestExporter(
-        host,
-        gh_user,
-        gh_token,
-        args.gerrit_user,
-        args.gerrit_token,
+        host=host,
+        gh_user=credentials['GH_USER'],
+        gh_token=credentials['GH_TOKEN'],
+        gerrit_user=credentials['GERRIT_USER'],
+        gerrit_token=credentials['GERRIT_TOKEN'],
         dry_run=args.dry_run
     ).run()
 
diff --git a/third_party/WebKit/public/BUILD.gn b/third_party/WebKit/public/BUILD.gn
index face788..c7c70638 100644
--- a/third_party/WebKit/public/BUILD.gn
+++ b/third_party/WebKit/public/BUILD.gn
@@ -736,6 +736,8 @@
     "//url/mojo:url_mojom_origin",
   ]
 
+  component_output_prefix = "blink_mojo_bindings"
+
   # The chromium variant must be linked with content and use the same export
   # settings in component build because of the WebBluetoothDeviceId typemap
   # inside content.
@@ -768,6 +770,8 @@
     "//url/mojo:url_mojom_gurl",
   ]
 
+  component_output_prefix = "blink_android_mojo_bindings"
+
   # See comment above.
   export_class_attribute = "CONTENT_EXPORT"
   export_define = "CONTENT_IMPLEMENTATION=1"
@@ -793,6 +797,8 @@
     "//cc/ipc:interfaces",
   ]
 
+  component_output_prefix = "blink_offscreen_canvas_mojo_bindings"
+
   # See comment above
   export_class_attribute = "CONTENT_EXPORT"
   export_define = "CONTENT_IMPLEMENTATION=1"
diff --git a/third_party/gvr-android-sdk/test-libraries/controller_test_api.aar.sha1 b/third_party/gvr-android-sdk/test-libraries/controller_test_api.aar.sha1
index 8fd6fe8..5496f33d 100644
--- a/third_party/gvr-android-sdk/test-libraries/controller_test_api.aar.sha1
+++ b/third_party/gvr-android-sdk/test-libraries/controller_test_api.aar.sha1
@@ -1 +1 @@
-2ef2878590caee4101008b2e4092133c69659c8e
\ No newline at end of file
+510b3f34e3a9ee4128e4329608ba59464549f6ca
\ No newline at end of file
diff --git a/third_party/libwebp/BUILD.gn b/third_party/libwebp/BUILD.gn
index 2b8ef9d..51b17875 100644
--- a/third_party/libwebp/BUILD.gn
+++ b/third_party/libwebp/BUILD.gn
@@ -78,6 +78,22 @@
   ]
 }
 
+static_library("libwebp_mux") {
+  sources = [
+    "mux/anim_encode.c",
+    "mux/muxedit.c",
+    "mux/muxinternal.c",
+    "mux/muxread.c",
+  ]
+  all_dependent_configs = [ ":libwebp_config" ]
+  configs -= [ "//build/config/compiler:chromium_code" ]
+  configs += [ "//build/config/compiler:no_chromium_code" ]
+  deps = [
+    ":libwebp_utils",
+    ":libwebp_webp",
+  ]
+}
+
 static_library("libwebp_dsp") {
   sources = [
     "dsp/alpha_processing.c",
@@ -343,6 +359,7 @@
     ":libwebp_demux",
     ":libwebp_dsp",
     ":libwebp_enc",
+    ":libwebp_mux",
     ":libwebp_utils",
   ]
   public_configs = [ ":libwebp_config" ]
diff --git a/third_party/libwebp/README.chromium b/third_party/libwebp/README.chromium
index 10d00d4..b1889cc 100644
--- a/third_party/libwebp/README.chromium
+++ b/third_party/libwebp/README.chromium
@@ -18,7 +18,7 @@
 
 Local changes:
  * Removed examples/, documentation and build related files, keeping only
-   the contents of src/ less mux/ which is unused.
+   the contents of src/.
  * Merged COPYING/PATENTS to LICENSE
  * Disabled "-frename-registers" flag for ARM64 linux build as clang(3.9.0)
    build fails with error - unsupported flag.
diff --git a/third_party/libwebp/mux/anim_encode.c b/third_party/libwebp/mux/anim_encode.c
new file mode 100644
index 0000000..60663887
--- /dev/null
+++ b/third_party/libwebp/mux/anim_encode.c
@@ -0,0 +1,1579 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+//  AnimEncoder implementation.
+//
+
+#include <assert.h>
+#include <limits.h>
+#include <math.h>    // for pow()
+#include <stdio.h>
+#include <stdlib.h>  // for abs()
+
+#include "../mux/animi.h"
+#include "../utils/utils.h"
+#include "../webp/decode.h"
+#include "../webp/encode.h"
+#include "../webp/format_constants.h"
+#include "../webp/mux.h"
+
+#if defined(_MSC_VER) && _MSC_VER < 1900
+#define snprintf _snprintf
+#endif
+
+#define ERROR_STR_MAX_LENGTH 100
+
+//------------------------------------------------------------------------------
+// Internal structs.
+
+// Stores frame rectangle dimensions.
+typedef struct {
+  int x_offset_, y_offset_, width_, height_;
+} FrameRect;
+
+// Used to store two candidates of encoded data for an animation frame. One of
+// the two will be chosen later.
+typedef struct {
+  WebPMuxFrameInfo sub_frame_;  // Encoded frame rectangle.
+  WebPMuxFrameInfo key_frame_;  // Encoded frame if it is a key-frame.
+  int is_key_frame_;            // True if 'key_frame' has been chosen.
+} EncodedFrame;
+
+struct WebPAnimEncoder {
+  const int canvas_width_;                  // Canvas width.
+  const int canvas_height_;                 // Canvas height.
+  const WebPAnimEncoderOptions options_;    // Global encoding options.
+
+  FrameRect prev_rect_;               // Previous WebP frame rectangle.
+  WebPConfig last_config_;            // Cached in case a re-encode is needed.
+  WebPConfig last_config_reversed_;   // If 'last_config_' uses lossless, then
+                                      // this config uses lossy and vice versa;
+                                      // only valid if 'options_.allow_mixed'
+                                      // is true.
+
+  WebPPicture* curr_canvas_;          // Only pointer; we don't own memory.
+
+  // Canvas buffers.
+  WebPPicture curr_canvas_copy_;      // Possibly modified current canvas.
+  int curr_canvas_copy_modified_;     // True if pixels in 'curr_canvas_copy_'
+                                      // differ from those in 'curr_canvas_'.
+
+  WebPPicture prev_canvas_;           // Previous canvas.
+  WebPPicture prev_canvas_disposed_;  // Previous canvas disposed to background.
+
+  // Encoded data.
+  EncodedFrame* encoded_frames_;      // Array of encoded frames.
+  size_t size_;             // Number of allocated frames.
+  size_t start_;            // Frame start index.
+  size_t count_;            // Number of valid frames.
+  size_t flush_count_;      // If >0, 'flush_count' frames starting from
+                            // 'start' are ready to be added to mux.
+
+  // key-frame related.
+  int64_t best_delta_;      // min(canvas size - frame size) over the frames.
+                            // Can be negative in certain cases due to
+                            // transparent pixels in a frame.
+  int keyframe_;            // Index of selected key-frame relative to 'start_'.
+  int count_since_key_frame_;     // Frames seen since the last key-frame.
+
+  int first_timestamp_;           // Timestamp of the first frame.
+  int prev_timestamp_;            // Timestamp of the last added frame.
+  int prev_candidate_undecided_;  // True if it's not yet decided if previous
+                                  // frame would be a sub-frame or a key-frame.
+
+  // Misc.
+  int is_first_frame_;  // True if first frame is yet to be added/being added.
+  int got_null_frame_;  // True if WebPAnimEncoderAdd() has already been called
+                        // with a NULL frame.
+
+  size_t in_frame_count_;   // Number of input frames processed so far.
+  size_t out_frame_count_;  // Number of frames added to mux so far. This may be
+                            // different from 'in_frame_count_' due to merging.
+
+  WebPMux* mux_;        // Muxer to assemble the WebP bitstream.
+  char error_str_[ERROR_STR_MAX_LENGTH];  // Error string. Empty if no error.
+};
+
+// -----------------------------------------------------------------------------
+// Life of WebPAnimEncoder object.
+
+#define DELTA_INFINITY      (1ULL << 32)
+#define KEYFRAME_NONE       (-1)
+
+// Reset the counters in the WebPAnimEncoder.
+static void ResetCounters(WebPAnimEncoder* const enc) {
+  enc->start_ = 0;
+  enc->count_ = 0;
+  enc->flush_count_ = 0;
+  enc->best_delta_ = DELTA_INFINITY;
+  enc->keyframe_ = KEYFRAME_NONE;
+}
+
+static void DisableKeyframes(WebPAnimEncoderOptions* const enc_options) {
+  enc_options->kmax = INT_MAX;
+  enc_options->kmin = enc_options->kmax - 1;
+}
+
+#define MAX_CACHED_FRAMES 30
+
+static void SanitizeEncoderOptions(WebPAnimEncoderOptions* const enc_options) {
+  int print_warning = enc_options->verbose;
+
+  if (enc_options->minimize_size) {
+    DisableKeyframes(enc_options);
+  }
+
+  if (enc_options->kmax == 1) {  // All frames will be key-frames.
+    enc_options->kmin = 0;
+    enc_options->kmax = 0;
+    return;
+  } else if (enc_options->kmax <= 0) {
+    DisableKeyframes(enc_options);
+    print_warning = 0;
+  }
+
+  if (enc_options->kmin >= enc_options->kmax) {
+    enc_options->kmin = enc_options->kmax - 1;
+    if (print_warning) {
+      fprintf(stderr, "WARNING: Setting kmin = %d, so that kmin < kmax.\n",
+              enc_options->kmin);
+    }
+  } else {
+    const int kmin_limit = enc_options->kmax / 2 + 1;
+    if (enc_options->kmin < kmin_limit && kmin_limit < enc_options->kmax) {
+      // This ensures that enc.keyframe + kmin >= kmax is always true. So, we
+      // can flush all the frames in the 'count_since_key_frame == kmax' case.
+      enc_options->kmin = kmin_limit;
+      if (print_warning) {
+        fprintf(stderr,
+                "WARNING: Setting kmin = %d, so that kmin >= kmax / 2 + 1.\n",
+                enc_options->kmin);
+      }
+    }
+  }
+  // Limit the max number of frames that are allocated.
+  if (enc_options->kmax - enc_options->kmin > MAX_CACHED_FRAMES) {
+    enc_options->kmin = enc_options->kmax - MAX_CACHED_FRAMES;
+    if (print_warning) {
+      fprintf(stderr,
+              "WARNING: Setting kmin = %d, so that kmax - kmin <= %d.\n",
+              enc_options->kmin, MAX_CACHED_FRAMES);
+    }
+  }
+  assert(enc_options->kmin < enc_options->kmax);
+}
+
+#undef MAX_CACHED_FRAMES
+
+static void DefaultEncoderOptions(WebPAnimEncoderOptions* const enc_options) {
+  enc_options->anim_params.loop_count = 0;
+  enc_options->anim_params.bgcolor = 0xffffffff;  // White.
+  enc_options->minimize_size = 0;
+  DisableKeyframes(enc_options);
+  enc_options->allow_mixed = 0;
+  enc_options->verbose = 0;
+}
+
+int WebPAnimEncoderOptionsInitInternal(WebPAnimEncoderOptions* enc_options,
+                                       int abi_version) {
+  if (enc_options == NULL ||
+      WEBP_ABI_IS_INCOMPATIBLE(abi_version, WEBP_MUX_ABI_VERSION)) {
+    return 0;
+  }
+  DefaultEncoderOptions(enc_options);
+  return 1;
+}
+
+// This starting value is more fit to WebPCleanupTransparentAreaLossless().
+#define TRANSPARENT_COLOR   0x00000000
+
+static void ClearRectangle(WebPPicture* const picture,
+                           int left, int top, int width, int height) {
+  int j;
+  for (j = top; j < top + height; ++j) {
+    uint32_t* const dst = picture->argb + j * picture->argb_stride;
+    int i;
+    for (i = left; i < left + width; ++i) {
+      dst[i] = TRANSPARENT_COLOR;
+    }
+  }
+}
+
+static void WebPUtilClearPic(WebPPicture* const picture,
+                             const FrameRect* const rect) {
+  if (rect != NULL) {
+    ClearRectangle(picture, rect->x_offset_, rect->y_offset_,
+                   rect->width_, rect->height_);
+  } else {
+    ClearRectangle(picture, 0, 0, picture->width, picture->height);
+  }
+}
+
+static void MarkNoError(WebPAnimEncoder* const enc) {
+  enc->error_str_[0] = '\0';  // Empty string.
+}
+
+static void MarkError(WebPAnimEncoder* const enc, const char* str) {
+  if (snprintf(enc->error_str_, ERROR_STR_MAX_LENGTH, "%s.", str) < 0) {
+    assert(0);  // FIX ME!
+  }
+}
+
+static void MarkError2(WebPAnimEncoder* const enc,
+                       const char* str, int error_code) {
+  if (snprintf(enc->error_str_, ERROR_STR_MAX_LENGTH, "%s: %d.", str,
+               error_code) < 0) {
+    assert(0);  // FIX ME!
+  }
+}
+
+WebPAnimEncoder* WebPAnimEncoderNewInternal(
+    int width, int height, const WebPAnimEncoderOptions* enc_options,
+    int abi_version) {
+  WebPAnimEncoder* enc;
+
+  if (WEBP_ABI_IS_INCOMPATIBLE(abi_version, WEBP_MUX_ABI_VERSION)) {
+    return NULL;
+  }
+  if (width <= 0 || height <= 0 ||
+      (width * (uint64_t)height) >= MAX_IMAGE_AREA) {
+    return NULL;
+  }
+
+  enc = (WebPAnimEncoder*)WebPSafeCalloc(1, sizeof(*enc));
+  if (enc == NULL) return NULL;
+  // sanity inits, so we can call WebPAnimEncoderDelete():
+  enc->encoded_frames_ = NULL;
+  enc->mux_ = NULL;
+  MarkNoError(enc);
+
+  // Dimensions and options.
+  *(int*)&enc->canvas_width_ = width;
+  *(int*)&enc->canvas_height_ = height;
+  if (enc_options != NULL) {
+    *(WebPAnimEncoderOptions*)&enc->options_ = *enc_options;
+    SanitizeEncoderOptions((WebPAnimEncoderOptions*)&enc->options_);
+  } else {
+    DefaultEncoderOptions((WebPAnimEncoderOptions*)&enc->options_);
+  }
+
+  // Canvas buffers.
+  if (!WebPPictureInit(&enc->curr_canvas_copy_) ||
+      !WebPPictureInit(&enc->prev_canvas_) ||
+      !WebPPictureInit(&enc->prev_canvas_disposed_)) {
+    goto Err;
+  }
+  enc->curr_canvas_copy_.width = width;
+  enc->curr_canvas_copy_.height = height;
+  enc->curr_canvas_copy_.use_argb = 1;
+  if (!WebPPictureAlloc(&enc->curr_canvas_copy_) ||
+      !WebPPictureCopy(&enc->curr_canvas_copy_, &enc->prev_canvas_) ||
+      !WebPPictureCopy(&enc->curr_canvas_copy_, &enc->prev_canvas_disposed_)) {
+    goto Err;
+  }
+  WebPUtilClearPic(&enc->prev_canvas_, NULL);
+  enc->curr_canvas_copy_modified_ = 1;
+
+  // Encoded frames.
+  ResetCounters(enc);
+  // Note: one extra storage is for the previous frame.
+  enc->size_ = enc->options_.kmax - enc->options_.kmin + 1;
+  // We need space for at least 2 frames. But when kmin, kmax are both zero,
+  // enc->size_ will be 1. So we handle that special case below.
+  if (enc->size_ < 2) enc->size_ = 2;
+  enc->encoded_frames_ =
+      (EncodedFrame*)WebPSafeCalloc(enc->size_, sizeof(*enc->encoded_frames_));
+  if (enc->encoded_frames_ == NULL) goto Err;
+
+  enc->mux_ = WebPMuxNew();
+  if (enc->mux_ == NULL) goto Err;
+
+  enc->count_since_key_frame_ = 0;
+  enc->first_timestamp_ = 0;
+  enc->prev_timestamp_ = 0;
+  enc->prev_candidate_undecided_ = 0;
+  enc->is_first_frame_ = 1;
+  enc->got_null_frame_ = 0;
+
+  return enc;  // All OK.
+
+ Err:
+  WebPAnimEncoderDelete(enc);
+  return NULL;
+}
+
+// Release the data contained by 'encoded_frame'.
+static void FrameRelease(EncodedFrame* const encoded_frame) {
+  if (encoded_frame != NULL) {
+    WebPDataClear(&encoded_frame->sub_frame_.bitstream);
+    WebPDataClear(&encoded_frame->key_frame_.bitstream);
+    memset(encoded_frame, 0, sizeof(*encoded_frame));
+  }
+}
+
+void WebPAnimEncoderDelete(WebPAnimEncoder* enc) {
+  if (enc != NULL) {
+    WebPPictureFree(&enc->curr_canvas_copy_);
+    WebPPictureFree(&enc->prev_canvas_);
+    WebPPictureFree(&enc->prev_canvas_disposed_);
+    if (enc->encoded_frames_ != NULL) {
+      size_t i;
+      for (i = 0; i < enc->size_; ++i) {
+        FrameRelease(&enc->encoded_frames_[i]);
+      }
+      WebPSafeFree(enc->encoded_frames_);
+    }
+    WebPMuxDelete(enc->mux_);
+    WebPSafeFree(enc);
+  }
+}
+
+// -----------------------------------------------------------------------------
+// Frame addition.
+
+// Returns cached frame at the given 'position'.
+static EncodedFrame* GetFrame(const WebPAnimEncoder* const enc,
+                              size_t position) {
+  assert(enc->start_ + position < enc->size_);
+  return &enc->encoded_frames_[enc->start_ + position];
+}
+
+typedef int (*ComparePixelsFunc)(const uint32_t*, int, const uint32_t*, int,
+                                 int, int);
+
+// Returns true if 'length' number of pixels in 'src' and 'dst' are equal,
+// assuming the given step sizes between pixels.
+// 'max_allowed_diff' is unused and only there to allow function pointer use.
+static WEBP_INLINE int ComparePixelsLossless(const uint32_t* src, int src_step,
+                                             const uint32_t* dst, int dst_step,
+                                             int length, int max_allowed_diff) {
+  (void)max_allowed_diff;
+  assert(length > 0);
+  while (length-- > 0) {
+    if (*src != *dst) {
+      return 0;
+    }
+    src += src_step;
+    dst += dst_step;
+  }
+  return 1;
+}
+
+// Helper to check if each channel in 'src' and 'dst' is at most off by
+// 'max_allowed_diff'.
+static WEBP_INLINE int PixelsAreSimilar(uint32_t src, uint32_t dst,
+                                        int max_allowed_diff) {
+  const int src_a = (src >> 24) & 0xff;
+  const int src_r = (src >> 16) & 0xff;
+  const int src_g = (src >> 8) & 0xff;
+  const int src_b = (src >> 0) & 0xff;
+  const int dst_a = (dst >> 24) & 0xff;
+  const int dst_r = (dst >> 16) & 0xff;
+  const int dst_g = (dst >> 8) & 0xff;
+  const int dst_b = (dst >> 0) & 0xff;
+
+  return (src_a == dst_a) &&
+         (abs(src_r - dst_r) * dst_a <= (max_allowed_diff * 255)) &&
+         (abs(src_g - dst_g) * dst_a <= (max_allowed_diff * 255)) &&
+         (abs(src_b - dst_b) * dst_a <= (max_allowed_diff * 255));
+}
+
+// Returns true if 'length' number of pixels in 'src' and 'dst' are within an
+// error bound, assuming the given step sizes between pixels.
+static WEBP_INLINE int ComparePixelsLossy(const uint32_t* src, int src_step,
+                                          const uint32_t* dst, int dst_step,
+                                          int length, int max_allowed_diff) {
+  assert(length > 0);
+  while (length-- > 0) {
+    if (!PixelsAreSimilar(*src, *dst, max_allowed_diff)) {
+      return 0;
+    }
+    src += src_step;
+    dst += dst_step;
+  }
+  return 1;
+}
+
+static int IsEmptyRect(const FrameRect* const rect) {
+  return (rect->width_ == 0) || (rect->height_ == 0);
+}
+
+static int QualityToMaxDiff(float quality) {
+  const double val = pow(quality / 100., 0.5);
+  const double max_diff = 31 * (1 - val) + 1 * val;
+  return (int)(max_diff + 0.5);
+}
+
+// Assumes that an initial valid guess of change rectangle 'rect' is passed.
+static void MinimizeChangeRectangle(const WebPPicture* const src,
+                                    const WebPPicture* const dst,
+                                    FrameRect* const rect,
+                                    int is_lossless, float quality) {
+  int i, j;
+  const ComparePixelsFunc compare_pixels =
+      is_lossless ? ComparePixelsLossless : ComparePixelsLossy;
+  const int max_allowed_diff_lossy = QualityToMaxDiff(quality);
+  const int max_allowed_diff = is_lossless ? 0 : max_allowed_diff_lossy;
+
+  // Sanity checks.
+  assert(src->width == dst->width && src->height == dst->height);
+  assert(rect->x_offset_ + rect->width_ <= dst->width);
+  assert(rect->y_offset_ + rect->height_ <= dst->height);
+
+  // Left boundary.
+  for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) {
+    const uint32_t* const src_argb =
+        &src->argb[rect->y_offset_ * src->argb_stride + i];
+    const uint32_t* const dst_argb =
+        &dst->argb[rect->y_offset_ * dst->argb_stride + i];
+    if (compare_pixels(src_argb, src->argb_stride, dst_argb, dst->argb_stride,
+                       rect->height_, max_allowed_diff)) {
+      --rect->width_;  // Redundant column.
+      ++rect->x_offset_;
+    } else {
+      break;
+    }
+  }
+  if (rect->width_ == 0) goto NoChange;
+
+  // Right boundary.
+  for (i = rect->x_offset_ + rect->width_ - 1; i >= rect->x_offset_; --i) {
+    const uint32_t* const src_argb =
+        &src->argb[rect->y_offset_ * src->argb_stride + i];
+    const uint32_t* const dst_argb =
+        &dst->argb[rect->y_offset_ * dst->argb_stride + i];
+    if (compare_pixels(src_argb, src->argb_stride, dst_argb, dst->argb_stride,
+                       rect->height_, max_allowed_diff)) {
+      --rect->width_;  // Redundant column.
+    } else {
+      break;
+    }
+  }
+  if (rect->width_ == 0) goto NoChange;
+
+  // Top boundary.
+  for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) {
+    const uint32_t* const src_argb =
+        &src->argb[j * src->argb_stride + rect->x_offset_];
+    const uint32_t* const dst_argb =
+        &dst->argb[j * dst->argb_stride + rect->x_offset_];
+    if (compare_pixels(src_argb, 1, dst_argb, 1, rect->width_,
+                       max_allowed_diff)) {
+      --rect->height_;  // Redundant row.
+      ++rect->y_offset_;
+    } else {
+      break;
+    }
+  }
+  if (rect->height_ == 0) goto NoChange;
+
+  // Bottom boundary.
+  for (j = rect->y_offset_ + rect->height_ - 1; j >= rect->y_offset_; --j) {
+    const uint32_t* const src_argb =
+        &src->argb[j * src->argb_stride + rect->x_offset_];
+    const uint32_t* const dst_argb =
+        &dst->argb[j * dst->argb_stride + rect->x_offset_];
+    if (compare_pixels(src_argb, 1, dst_argb, 1, rect->width_,
+                       max_allowed_diff)) {
+      --rect->height_;  // Redundant row.
+    } else {
+      break;
+    }
+  }
+  if (rect->height_ == 0) goto NoChange;
+
+  if (IsEmptyRect(rect)) {
+ NoChange:
+    rect->x_offset_ = 0;
+    rect->y_offset_ = 0;
+    rect->width_ = 0;
+    rect->height_ = 0;
+  }
+}
+
+// Snap rectangle to even offsets (and adjust dimensions if needed).
+static WEBP_INLINE void SnapToEvenOffsets(FrameRect* const rect) {
+  rect->width_ += (rect->x_offset_ & 1);
+  rect->height_ += (rect->y_offset_ & 1);
+  rect->x_offset_ &= ~1;
+  rect->y_offset_ &= ~1;
+}
+
+typedef struct {
+  int should_try_;               // Should try this set of parameters.
+  int empty_rect_allowed_;       // Frame with empty rectangle can be skipped.
+  FrameRect rect_ll_;            // Frame rectangle for lossless compression.
+  WebPPicture sub_frame_ll_;     // Sub-frame pic for lossless compression.
+  FrameRect rect_lossy_;         // Frame rectangle for lossy compression.
+                                 // Could be smaller than rect_ll_ as pixels
+                                 // with small diffs can be ignored.
+  WebPPicture sub_frame_lossy_;  // Sub-frame pic for lossless compression.
+} SubFrameParams;
+
+static int SubFrameParamsInit(SubFrameParams* const params,
+                              int should_try, int empty_rect_allowed) {
+  params->should_try_ = should_try;
+  params->empty_rect_allowed_ = empty_rect_allowed;
+  if (!WebPPictureInit(&params->sub_frame_ll_) ||
+      !WebPPictureInit(&params->sub_frame_lossy_)) {
+    return 0;
+  }
+  return 1;
+}
+
+static void SubFrameParamsFree(SubFrameParams* const params) {
+  WebPPictureFree(&params->sub_frame_ll_);
+  WebPPictureFree(&params->sub_frame_lossy_);
+}
+
+// Given previous and current canvas, picks the optimal rectangle for the
+// current frame based on 'is_lossless' and other parameters. Assumes that the
+// initial guess 'rect' is valid.
+static int GetSubRect(const WebPPicture* const prev_canvas,
+                      const WebPPicture* const curr_canvas, int is_key_frame,
+                      int is_first_frame, int empty_rect_allowed,
+                      int is_lossless, float quality, FrameRect* const rect,
+                      WebPPicture* const sub_frame) {
+  if (!is_key_frame || is_first_frame) {  // Optimize frame rectangle.
+    // Note: This behaves as expected for first frame, as 'prev_canvas' is
+    // initialized to a fully transparent canvas in the beginning.
+    MinimizeChangeRectangle(prev_canvas, curr_canvas, rect,
+                            is_lossless, quality);
+  }
+
+  if (IsEmptyRect(rect)) {
+    if (empty_rect_allowed) {  // No need to get 'sub_frame'.
+      return 1;
+    } else {                   // Force a 1x1 rectangle.
+      rect->width_ = 1;
+      rect->height_ = 1;
+      assert(rect->x_offset_ == 0);
+      assert(rect->y_offset_ == 0);
+    }
+  }
+
+  SnapToEvenOffsets(rect);
+  return WebPPictureView(curr_canvas, rect->x_offset_, rect->y_offset_,
+                         rect->width_, rect->height_, sub_frame);
+}
+
+// Picks optimal frame rectangle for both lossless and lossy compression. The
+// initial guess for frame rectangles will be the full canvas.
+static int GetSubRects(const WebPPicture* const prev_canvas,
+                       const WebPPicture* const curr_canvas, int is_key_frame,
+                       int is_first_frame, float quality,
+                       SubFrameParams* const params) {
+  // Lossless frame rectangle.
+  params->rect_ll_.x_offset_ = 0;
+  params->rect_ll_.y_offset_ = 0;
+  params->rect_ll_.width_ = curr_canvas->width;
+  params->rect_ll_.height_ = curr_canvas->height;
+  if (!GetSubRect(prev_canvas, curr_canvas, is_key_frame, is_first_frame,
+                  params->empty_rect_allowed_, 1, quality,
+                  &params->rect_ll_, &params->sub_frame_ll_)) {
+    return 0;
+  }
+  // Lossy frame rectangle.
+  params->rect_lossy_ = params->rect_ll_;  // seed with lossless rect.
+  return GetSubRect(prev_canvas, curr_canvas, is_key_frame, is_first_frame,
+                    params->empty_rect_allowed_, 0, quality,
+                    &params->rect_lossy_, &params->sub_frame_lossy_);
+}
+
+static WEBP_INLINE int clip(int v, int min_v, int max_v) {
+  return (v < min_v) ? min_v : (v > max_v) ? max_v : v;
+}
+
+int WebPAnimEncoderRefineRect(
+    const WebPPicture* const prev_canvas, const WebPPicture* const curr_canvas,
+    int is_lossless, float quality, int* const x_offset, int* const y_offset,
+    int* const width, int* const height) {
+  FrameRect rect;
+  const int right = clip(*x_offset + *width, 0, curr_canvas->width);
+  const int left = clip(*x_offset, 0, curr_canvas->width - 1);
+  const int bottom = clip(*y_offset + *height, 0, curr_canvas->height);
+  const int top = clip(*y_offset, 0, curr_canvas->height - 1);
+  if (prev_canvas == NULL || curr_canvas == NULL ||
+      prev_canvas->width != curr_canvas->width ||
+      prev_canvas->height != curr_canvas->height ||
+      !prev_canvas->use_argb || !curr_canvas->use_argb) {
+    return 0;
+  }
+  rect.x_offset_ = left;
+  rect.y_offset_ = top;
+  rect.width_ = clip(right - left, 0, curr_canvas->width - rect.x_offset_);
+  rect.height_ = clip(bottom - top, 0, curr_canvas->height - rect.y_offset_);
+  MinimizeChangeRectangle(prev_canvas, curr_canvas, &rect, is_lossless,
+                          quality);
+  SnapToEvenOffsets(&rect);
+  *x_offset = rect.x_offset_;
+  *y_offset = rect.y_offset_;
+  *width = rect.width_;
+  *height = rect.height_;
+  return 1;
+}
+
+static void DisposeFrameRectangle(int dispose_method,
+                                  const FrameRect* const rect,
+                                  WebPPicture* const curr_canvas) {
+  assert(rect != NULL);
+  if (dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
+    WebPUtilClearPic(curr_canvas, rect);
+  }
+}
+
+static uint32_t RectArea(const FrameRect* const rect) {
+  return (uint32_t)rect->width_ * rect->height_;
+}
+
+static int IsLosslessBlendingPossible(const WebPPicture* const src,
+                                      const WebPPicture* const dst,
+                                      const FrameRect* const rect) {
+  int i, j;
+  assert(src->width == dst->width && src->height == dst->height);
+  assert(rect->x_offset_ + rect->width_ <= dst->width);
+  assert(rect->y_offset_ + rect->height_ <= dst->height);
+  for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) {
+    for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) {
+      const uint32_t src_pixel = src->argb[j * src->argb_stride + i];
+      const uint32_t dst_pixel = dst->argb[j * dst->argb_stride + i];
+      const uint32_t dst_alpha = dst_pixel >> 24;
+      if (dst_alpha != 0xff && src_pixel != dst_pixel) {
+        // In this case, if we use blending, we can't attain the desired
+        // 'dst_pixel' value for this pixel. So, blending is not possible.
+        return 0;
+      }
+    }
+  }
+  return 1;
+}
+
+static int IsLossyBlendingPossible(const WebPPicture* const src,
+                                   const WebPPicture* const dst,
+                                   const FrameRect* const rect,
+                                   float quality) {
+  const int max_allowed_diff_lossy = QualityToMaxDiff(quality);
+  int i, j;
+  assert(src->width == dst->width && src->height == dst->height);
+  assert(rect->x_offset_ + rect->width_ <= dst->width);
+  assert(rect->y_offset_ + rect->height_ <= dst->height);
+  for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) {
+    for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) {
+      const uint32_t src_pixel = src->argb[j * src->argb_stride + i];
+      const uint32_t dst_pixel = dst->argb[j * dst->argb_stride + i];
+      const uint32_t dst_alpha = dst_pixel >> 24;
+      if (dst_alpha != 0xff &&
+          !PixelsAreSimilar(src_pixel, dst_pixel, max_allowed_diff_lossy)) {
+        // In this case, if we use blending, we can't attain the desired
+        // 'dst_pixel' value for this pixel. So, blending is not possible.
+        return 0;
+      }
+    }
+  }
+  return 1;
+}
+
+// For pixels in 'rect', replace those pixels in 'dst' that are same as 'src' by
+// transparent pixels.
+// Returns true if at least one pixel gets modified.
+static int IncreaseTransparency(const WebPPicture* const src,
+                                const FrameRect* const rect,
+                                WebPPicture* const dst) {
+  int i, j;
+  int modified = 0;
+  assert(src != NULL && dst != NULL && rect != NULL);
+  assert(src->width == dst->width && src->height == dst->height);
+  for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) {
+    const uint32_t* const psrc = src->argb + j * src->argb_stride;
+    uint32_t* const pdst = dst->argb + j * dst->argb_stride;
+    for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) {
+      if (psrc[i] == pdst[i] && pdst[i] != TRANSPARENT_COLOR) {
+        pdst[i] = TRANSPARENT_COLOR;
+        modified = 1;
+      }
+    }
+  }
+  return modified;
+}
+
+#undef TRANSPARENT_COLOR
+
+// Replace similar blocks of pixels by a 'see-through' transparent block
+// with uniform average color.
+// Assumes lossy compression is being used.
+// Returns true if at least one pixel gets modified.
+static int FlattenSimilarBlocks(const WebPPicture* const src,
+                                const FrameRect* const rect,
+                                WebPPicture* const dst, float quality) {
+  const int max_allowed_diff_lossy = QualityToMaxDiff(quality);
+  int i, j;
+  int modified = 0;
+  const int block_size = 8;
+  const int y_start = (rect->y_offset_ + block_size) & ~(block_size - 1);
+  const int y_end = (rect->y_offset_ + rect->height_) & ~(block_size - 1);
+  const int x_start = (rect->x_offset_ + block_size) & ~(block_size - 1);
+  const int x_end = (rect->x_offset_ + rect->width_) & ~(block_size - 1);
+  assert(src != NULL && dst != NULL && rect != NULL);
+  assert(src->width == dst->width && src->height == dst->height);
+  assert((block_size & (block_size - 1)) == 0);  // must be a power of 2
+  // Iterate over each block and count similar pixels.
+  for (j = y_start; j < y_end; j += block_size) {
+    for (i = x_start; i < x_end; i += block_size) {
+      int cnt = 0;
+      int avg_r = 0, avg_g = 0, avg_b = 0;
+      int x, y;
+      const uint32_t* const psrc = src->argb + j * src->argb_stride + i;
+      uint32_t* const pdst = dst->argb + j * dst->argb_stride + i;
+      for (y = 0; y < block_size; ++y) {
+        for (x = 0; x < block_size; ++x) {
+          const uint32_t src_pixel = psrc[x + y * src->argb_stride];
+          const int alpha = src_pixel >> 24;
+          if (alpha == 0xff &&
+              PixelsAreSimilar(src_pixel, pdst[x + y * dst->argb_stride],
+                               max_allowed_diff_lossy)) {
+            ++cnt;
+            avg_r += (src_pixel >> 16) & 0xff;
+            avg_g += (src_pixel >> 8) & 0xff;
+            avg_b += (src_pixel >> 0) & 0xff;
+          }
+        }
+      }
+      // If we have a fully similar block, we replace it with an
+      // average transparent block. This compresses better in lossy mode.
+      if (cnt == block_size * block_size) {
+        const uint32_t color = (0x00          << 24) |
+                               ((avg_r / cnt) << 16) |
+                               ((avg_g / cnt) <<  8) |
+                               ((avg_b / cnt) <<  0);
+        for (y = 0; y < block_size; ++y) {
+          for (x = 0; x < block_size; ++x) {
+            pdst[x + y * dst->argb_stride] = color;
+          }
+        }
+        modified = 1;
+      }
+    }
+  }
+  return modified;
+}
+
+static int EncodeFrame(const WebPConfig* const config, WebPPicture* const pic,
+                       WebPMemoryWriter* const memory) {
+  pic->use_argb = 1;
+  pic->writer = WebPMemoryWrite;
+  pic->custom_ptr = memory;
+  if (!WebPEncode(config, pic)) {
+    return 0;
+  }
+  return 1;
+}
+
+// Struct representing a candidate encoded frame including its metadata.
+typedef struct {
+  WebPMemoryWriter  mem_;
+  WebPMuxFrameInfo  info_;
+  FrameRect         rect_;
+  int               evaluate_;  // True if this candidate should be evaluated.
+} Candidate;
+
+// Generates a candidate encoded frame given a picture and metadata.
+static WebPEncodingError EncodeCandidate(WebPPicture* const sub_frame,
+                                         const FrameRect* const rect,
+                                         const WebPConfig* const encoder_config,
+                                         int use_blending,
+                                         Candidate* const candidate) {
+  WebPConfig config = *encoder_config;
+  WebPEncodingError error_code = VP8_ENC_OK;
+  assert(candidate != NULL);
+  memset(candidate, 0, sizeof(*candidate));
+
+  // Set frame rect and info.
+  candidate->rect_ = *rect;
+  candidate->info_.id = WEBP_CHUNK_ANMF;
+  candidate->info_.x_offset = rect->x_offset_;
+  candidate->info_.y_offset = rect->y_offset_;
+  candidate->info_.dispose_method = WEBP_MUX_DISPOSE_NONE;  // Set later.
+  candidate->info_.blend_method =
+      use_blending ? WEBP_MUX_BLEND : WEBP_MUX_NO_BLEND;
+  candidate->info_.duration = 0;  // Set in next call to WebPAnimEncoderAdd().
+
+  // Encode picture.
+  WebPMemoryWriterInit(&candidate->mem_);
+
+  if (!config.lossless && use_blending) {
+    // Disable filtering to avoid blockiness in reconstructed frames at the
+    // time of decoding.
+    config.autofilter = 0;
+    config.filter_strength = 0;
+  }
+  if (!EncodeFrame(&config, sub_frame, &candidate->mem_)) {
+    error_code = sub_frame->error_code;
+    goto Err;
+  }
+
+  candidate->evaluate_ = 1;
+  return error_code;
+
+ Err:
+  WebPMemoryWriterClear(&candidate->mem_);
+  return error_code;
+}
+
+static void CopyCurrentCanvas(WebPAnimEncoder* const enc) {
+  if (enc->curr_canvas_copy_modified_) {
+    WebPCopyPixels(enc->curr_canvas_, &enc->curr_canvas_copy_);
+    enc->curr_canvas_copy_.progress_hook = enc->curr_canvas_->progress_hook;
+    enc->curr_canvas_copy_.user_data = enc->curr_canvas_->user_data;
+    enc->curr_canvas_copy_modified_ = 0;
+  }
+}
+
+enum {
+  LL_DISP_NONE = 0,
+  LL_DISP_BG,
+  LOSSY_DISP_NONE,
+  LOSSY_DISP_BG,
+  CANDIDATE_COUNT
+};
+
+#define MIN_COLORS_LOSSY     31  // Don't try lossy below this threshold.
+#define MAX_COLORS_LOSSLESS 194  // Don't try lossless above this threshold.
+
+// Generates candidates for a given dispose method given pre-filled sub-frame
+// 'params'.
+static WebPEncodingError GenerateCandidates(
+    WebPAnimEncoder* const enc, Candidate candidates[CANDIDATE_COUNT],
+    WebPMuxAnimDispose dispose_method, int is_lossless, int is_key_frame,
+    SubFrameParams* const params,
+    const WebPConfig* const config_ll, const WebPConfig* const config_lossy) {
+  WebPEncodingError error_code = VP8_ENC_OK;
+  const int is_dispose_none = (dispose_method == WEBP_MUX_DISPOSE_NONE);
+  Candidate* const candidate_ll =
+      is_dispose_none ? &candidates[LL_DISP_NONE] : &candidates[LL_DISP_BG];
+  Candidate* const candidate_lossy = is_dispose_none
+                                     ? &candidates[LOSSY_DISP_NONE]
+                                     : &candidates[LOSSY_DISP_BG];
+  WebPPicture* const curr_canvas = &enc->curr_canvas_copy_;
+  const WebPPicture* const prev_canvas =
+      is_dispose_none ? &enc->prev_canvas_ : &enc->prev_canvas_disposed_;
+  int use_blending_ll, use_blending_lossy;
+  int evaluate_ll, evaluate_lossy;
+
+  CopyCurrentCanvas(enc);
+  use_blending_ll =
+      !is_key_frame &&
+      IsLosslessBlendingPossible(prev_canvas, curr_canvas, &params->rect_ll_);
+  use_blending_lossy =
+      !is_key_frame &&
+      IsLossyBlendingPossible(prev_canvas, curr_canvas, &params->rect_lossy_,
+                              config_lossy->quality);
+
+  // Pick candidates to be tried.
+  if (!enc->options_.allow_mixed) {
+    evaluate_ll = is_lossless;
+    evaluate_lossy = !is_lossless;
+  } else if (enc->options_.minimize_size) {
+    evaluate_ll = 1;
+    evaluate_lossy = 1;
+  } else {  // Use a heuristic for trying lossless and/or lossy compression.
+    const int num_colors = WebPGetColorPalette(&params->sub_frame_ll_, NULL);
+    evaluate_ll = (num_colors < MAX_COLORS_LOSSLESS);
+    evaluate_lossy = (num_colors >= MIN_COLORS_LOSSY);
+  }
+
+  // Generate candidates.
+  if (evaluate_ll) {
+    CopyCurrentCanvas(enc);
+    if (use_blending_ll) {
+      enc->curr_canvas_copy_modified_ =
+          IncreaseTransparency(prev_canvas, &params->rect_ll_, curr_canvas);
+    }
+    error_code = EncodeCandidate(&params->sub_frame_ll_, &params->rect_ll_,
+                                 config_ll, use_blending_ll, candidate_ll);
+    if (error_code != VP8_ENC_OK) return error_code;
+  }
+  if (evaluate_lossy) {
+    CopyCurrentCanvas(enc);
+    if (use_blending_lossy) {
+      enc->curr_canvas_copy_modified_ =
+          FlattenSimilarBlocks(prev_canvas, &params->rect_lossy_, curr_canvas,
+                               config_lossy->quality);
+    }
+    error_code =
+        EncodeCandidate(&params->sub_frame_lossy_, &params->rect_lossy_,
+                        config_lossy, use_blending_lossy, candidate_lossy);
+    if (error_code != VP8_ENC_OK) return error_code;
+    enc->curr_canvas_copy_modified_ = 1;
+  }
+  return error_code;
+}
+
+#undef MIN_COLORS_LOSSY
+#undef MAX_COLORS_LOSSLESS
+
+static void GetEncodedData(const WebPMemoryWriter* const memory,
+                           WebPData* const encoded_data) {
+  encoded_data->bytes = memory->mem;
+  encoded_data->size  = memory->size;
+}
+
+// Sets dispose method of the previous frame to be 'dispose_method'.
+static void SetPreviousDisposeMethod(WebPAnimEncoder* const enc,
+                                     WebPMuxAnimDispose dispose_method) {
+  const size_t position = enc->count_ - 2;
+  EncodedFrame* const prev_enc_frame = GetFrame(enc, position);
+  assert(enc->count_ >= 2);  // As current and previous frames are in enc.
+
+  if (enc->prev_candidate_undecided_) {
+    assert(dispose_method == WEBP_MUX_DISPOSE_NONE);
+    prev_enc_frame->sub_frame_.dispose_method = dispose_method;
+    prev_enc_frame->key_frame_.dispose_method = dispose_method;
+  } else {
+    WebPMuxFrameInfo* const prev_info = prev_enc_frame->is_key_frame_
+                                        ? &prev_enc_frame->key_frame_
+                                        : &prev_enc_frame->sub_frame_;
+    prev_info->dispose_method = dispose_method;
+  }
+}
+
+static int IncreasePreviousDuration(WebPAnimEncoder* const enc, int duration) {
+  const size_t position = enc->count_ - 1;
+  EncodedFrame* const prev_enc_frame = GetFrame(enc, position);
+  int new_duration;
+
+  assert(enc->count_ >= 1);
+  assert(prev_enc_frame->sub_frame_.duration ==
+         prev_enc_frame->key_frame_.duration);
+  assert(prev_enc_frame->sub_frame_.duration ==
+         (prev_enc_frame->sub_frame_.duration & (MAX_DURATION - 1)));
+  assert(duration == (duration & (MAX_DURATION - 1)));
+
+  new_duration = prev_enc_frame->sub_frame_.duration + duration;
+  if (new_duration >= MAX_DURATION) {  // Special case.
+    // Separate out previous frame from earlier merged frames to avoid overflow.
+    // We add a 1x1 transparent frame for the previous frame, with blending on.
+    const FrameRect rect = { 0, 0, 1, 1 };
+    const uint8_t lossless_1x1_bytes[] = {
+      0x52, 0x49, 0x46, 0x46, 0x14, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50,
+      0x56, 0x50, 0x38, 0x4c, 0x08, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00,
+      0x10, 0x88, 0x88, 0x08
+    };
+    const WebPData lossless_1x1 = {
+        lossless_1x1_bytes, sizeof(lossless_1x1_bytes)
+    };
+    const uint8_t lossy_1x1_bytes[] = {
+      0x52, 0x49, 0x46, 0x46, 0x40, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50,
+      0x56, 0x50, 0x38, 0x58, 0x0a, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x4c, 0x50, 0x48, 0x02, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x56, 0x50, 0x38, 0x20, 0x18, 0x00, 0x00, 0x00,
+      0x30, 0x01, 0x00, 0x9d, 0x01, 0x2a, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00,
+      0x34, 0x25, 0xa4, 0x00, 0x03, 0x70, 0x00, 0xfe, 0xfb, 0xfd, 0x50, 0x00
+    };
+    const WebPData lossy_1x1 = { lossy_1x1_bytes, sizeof(lossy_1x1_bytes) };
+    const int can_use_lossless =
+        (enc->last_config_.lossless || enc->options_.allow_mixed);
+    EncodedFrame* const curr_enc_frame = GetFrame(enc, enc->count_);
+    curr_enc_frame->is_key_frame_ = 0;
+    curr_enc_frame->sub_frame_.id = WEBP_CHUNK_ANMF;
+    curr_enc_frame->sub_frame_.x_offset = 0;
+    curr_enc_frame->sub_frame_.y_offset = 0;
+    curr_enc_frame->sub_frame_.dispose_method = WEBP_MUX_DISPOSE_NONE;
+    curr_enc_frame->sub_frame_.blend_method = WEBP_MUX_BLEND;
+    curr_enc_frame->sub_frame_.duration = duration;
+    if (!WebPDataCopy(can_use_lossless ? &lossless_1x1 : &lossy_1x1,
+                      &curr_enc_frame->sub_frame_.bitstream)) {
+      return 0;
+    }
+    ++enc->count_;
+    ++enc->count_since_key_frame_;
+    enc->flush_count_ = enc->count_ - 1;
+    enc->prev_candidate_undecided_ = 0;
+    enc->prev_rect_ = rect;
+  } else {                           // Regular case.
+    // Increase duration of the previous frame by 'duration'.
+    prev_enc_frame->sub_frame_.duration = new_duration;
+    prev_enc_frame->key_frame_.duration = new_duration;
+  }
+  return 1;
+}
+
+// Pick the candidate encoded frame with smallest size and release other
+// candidates.
+// TODO(later): Perhaps a rough SSIM/PSNR produced by the encoder should
+// also be a criteria, in addition to sizes.
+static void PickBestCandidate(WebPAnimEncoder* const enc,
+                              Candidate* const candidates, int is_key_frame,
+                              EncodedFrame* const encoded_frame) {
+  int i;
+  int best_idx = -1;
+  size_t best_size = ~0;
+  for (i = 0; i < CANDIDATE_COUNT; ++i) {
+    if (candidates[i].evaluate_) {
+      const size_t candidate_size = candidates[i].mem_.size;
+      if (candidate_size < best_size) {
+        best_idx = i;
+        best_size = candidate_size;
+      }
+    }
+  }
+  assert(best_idx != -1);
+  for (i = 0; i < CANDIDATE_COUNT; ++i) {
+    if (candidates[i].evaluate_) {
+      if (i == best_idx) {
+        WebPMuxFrameInfo* const dst = is_key_frame
+                                      ? &encoded_frame->key_frame_
+                                      : &encoded_frame->sub_frame_;
+        *dst = candidates[i].info_;
+        GetEncodedData(&candidates[i].mem_, &dst->bitstream);
+        if (!is_key_frame) {
+          // Note: Previous dispose method only matters for non-keyframes.
+          // Also, we don't want to modify previous dispose method that was
+          // selected when a non key-frame was assumed.
+          const WebPMuxAnimDispose prev_dispose_method =
+              (best_idx == LL_DISP_NONE || best_idx == LOSSY_DISP_NONE)
+                  ? WEBP_MUX_DISPOSE_NONE
+                  : WEBP_MUX_DISPOSE_BACKGROUND;
+          SetPreviousDisposeMethod(enc, prev_dispose_method);
+        }
+        enc->prev_rect_ = candidates[i].rect_;  // save for next frame.
+      } else {
+        WebPMemoryWriterClear(&candidates[i].mem_);
+        candidates[i].evaluate_ = 0;
+      }
+    }
+  }
+}
+
+// Depending on the configuration, tries different compressions
+// (lossy/lossless), dispose methods, blending methods etc to encode the current
+// frame and outputs the best one in 'encoded_frame'.
+// 'frame_skipped' will be set to true if this frame should actually be skipped.
+static WebPEncodingError SetFrame(WebPAnimEncoder* const enc,
+                                  const WebPConfig* const config,
+                                  int is_key_frame,
+                                  EncodedFrame* const encoded_frame,
+                                  int* const frame_skipped) {
+  int i;
+  WebPEncodingError error_code = VP8_ENC_OK;
+  const WebPPicture* const curr_canvas = &enc->curr_canvas_copy_;
+  const WebPPicture* const prev_canvas = &enc->prev_canvas_;
+  Candidate candidates[CANDIDATE_COUNT];
+  const int is_lossless = config->lossless;
+  const int consider_lossless = is_lossless || enc->options_.allow_mixed;
+  const int consider_lossy = !is_lossless || enc->options_.allow_mixed;
+  const int is_first_frame = enc->is_first_frame_;
+
+  // First frame cannot be skipped as there is no 'previous frame' to merge it
+  // to. So, empty rectangle is not allowed for the first frame.
+  const int empty_rect_allowed_none = !is_first_frame;
+
+  // Even if there is exact pixel match between 'disposed previous canvas' and
+  // 'current canvas', we can't skip current frame, as there may not be exact
+  // pixel match between 'previous canvas' and 'current canvas'. So, we don't
+  // allow empty rectangle in this case.
+  const int empty_rect_allowed_bg = 0;
+
+  // If current frame is a key-frame, dispose method of previous frame doesn't
+  // matter, so we don't try dispose to background.
+  // Also, if key-frame insertion is on, and previous frame could be picked as
+  // either a sub-frame or a key-frame, then we can't be sure about what frame
+  // rectangle would be disposed. In that case too, we don't try dispose to
+  // background.
+  const int dispose_bg_possible =
+      !is_key_frame && !enc->prev_candidate_undecided_;
+
+  SubFrameParams dispose_none_params;
+  SubFrameParams dispose_bg_params;
+
+  WebPConfig config_ll = *config;
+  WebPConfig config_lossy = *config;
+  config_ll.lossless = 1;
+  config_lossy.lossless = 0;
+  enc->last_config_ = *config;
+  enc->last_config_reversed_ = config->lossless ? config_lossy : config_ll;
+  *frame_skipped = 0;
+
+  if (!SubFrameParamsInit(&dispose_none_params, 1, empty_rect_allowed_none) ||
+      !SubFrameParamsInit(&dispose_bg_params, 0, empty_rect_allowed_bg)) {
+    return VP8_ENC_ERROR_INVALID_CONFIGURATION;
+  }
+
+  memset(candidates, 0, sizeof(candidates));
+
+  // Change-rectangle assuming previous frame was DISPOSE_NONE.
+  if (!GetSubRects(prev_canvas, curr_canvas, is_key_frame, is_first_frame,
+                   config_lossy.quality, &dispose_none_params)) {
+    error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION;
+    goto Err;
+  }
+
+  if ((consider_lossless && IsEmptyRect(&dispose_none_params.rect_ll_)) ||
+      (consider_lossy && IsEmptyRect(&dispose_none_params.rect_lossy_))) {
+    // Don't encode the frame at all. Instead, the duration of the previous
+    // frame will be increased later.
+    assert(empty_rect_allowed_none);
+    *frame_skipped = 1;
+    goto End;
+  }
+
+  if (dispose_bg_possible) {
+    // Change-rectangle assuming previous frame was DISPOSE_BACKGROUND.
+    WebPPicture* const prev_canvas_disposed = &enc->prev_canvas_disposed_;
+    WebPCopyPixels(prev_canvas, prev_canvas_disposed);
+    DisposeFrameRectangle(WEBP_MUX_DISPOSE_BACKGROUND, &enc->prev_rect_,
+                          prev_canvas_disposed);
+
+    if (!GetSubRects(prev_canvas_disposed, curr_canvas, is_key_frame,
+                     is_first_frame, config_lossy.quality,
+                     &dispose_bg_params)) {
+      error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION;
+      goto Err;
+    }
+    assert(!IsEmptyRect(&dispose_bg_params.rect_ll_));
+    assert(!IsEmptyRect(&dispose_bg_params.rect_lossy_));
+
+    if (enc->options_.minimize_size) {  // Try both dispose methods.
+      dispose_bg_params.should_try_ = 1;
+      dispose_none_params.should_try_ = 1;
+    } else if ((is_lossless &&
+                RectArea(&dispose_bg_params.rect_ll_) <
+                    RectArea(&dispose_none_params.rect_ll_)) ||
+               (!is_lossless &&
+                RectArea(&dispose_bg_params.rect_lossy_) <
+                    RectArea(&dispose_none_params.rect_lossy_))) {
+      dispose_bg_params.should_try_ = 1;  // Pick DISPOSE_BACKGROUND.
+      dispose_none_params.should_try_ = 0;
+    }
+  }
+
+  if (dispose_none_params.should_try_) {
+    error_code = GenerateCandidates(
+        enc, candidates, WEBP_MUX_DISPOSE_NONE, is_lossless, is_key_frame,
+        &dispose_none_params, &config_ll, &config_lossy);
+    if (error_code != VP8_ENC_OK) goto Err;
+  }
+
+  if (dispose_bg_params.should_try_) {
+    assert(!enc->is_first_frame_);
+    assert(dispose_bg_possible);
+    error_code = GenerateCandidates(
+        enc, candidates, WEBP_MUX_DISPOSE_BACKGROUND, is_lossless, is_key_frame,
+        &dispose_bg_params, &config_ll, &config_lossy);
+    if (error_code != VP8_ENC_OK) goto Err;
+  }
+
+  PickBestCandidate(enc, candidates, is_key_frame, encoded_frame);
+
+  goto End;
+
+ Err:
+  for (i = 0; i < CANDIDATE_COUNT; ++i) {
+    if (candidates[i].evaluate_) {
+      WebPMemoryWriterClear(&candidates[i].mem_);
+    }
+  }
+
+ End:
+  SubFrameParamsFree(&dispose_none_params);
+  SubFrameParamsFree(&dispose_bg_params);
+  return error_code;
+}
+
+// Calculate the penalty incurred if we encode given frame as a key frame
+// instead of a sub-frame.
+static int64_t KeyFramePenalty(const EncodedFrame* const encoded_frame) {
+  return ((int64_t)encoded_frame->key_frame_.bitstream.size -
+          encoded_frame->sub_frame_.bitstream.size);
+}
+
+static int CacheFrame(WebPAnimEncoder* const enc,
+                      const WebPConfig* const config) {
+  int ok = 0;
+  int frame_skipped = 0;
+  WebPEncodingError error_code = VP8_ENC_OK;
+  const size_t position = enc->count_;
+  EncodedFrame* const encoded_frame = GetFrame(enc, position);
+
+  ++enc->count_;
+
+  if (enc->is_first_frame_) {  // Add this as a key-frame.
+    error_code = SetFrame(enc, config, 1, encoded_frame, &frame_skipped);
+    if (error_code != VP8_ENC_OK) goto End;
+    assert(frame_skipped == 0);  // First frame can't be skipped, even if empty.
+    assert(position == 0 && enc->count_ == 1);
+    encoded_frame->is_key_frame_ = 1;
+    enc->flush_count_ = 0;
+    enc->count_since_key_frame_ = 0;
+    enc->prev_candidate_undecided_ = 0;
+  } else {
+    ++enc->count_since_key_frame_;
+    if (enc->count_since_key_frame_ <= enc->options_.kmin) {
+      // Add this as a frame rectangle.
+      error_code = SetFrame(enc, config, 0, encoded_frame, &frame_skipped);
+      if (error_code != VP8_ENC_OK) goto End;
+      if (frame_skipped) goto Skip;
+      encoded_frame->is_key_frame_ = 0;
+      enc->flush_count_ = enc->count_ - 1;
+      enc->prev_candidate_undecided_ = 0;
+    } else {
+      int64_t curr_delta;
+      FrameRect prev_rect_key, prev_rect_sub;
+
+      // Add this as a frame rectangle to enc.
+      error_code = SetFrame(enc, config, 0, encoded_frame, &frame_skipped);
+      if (error_code != VP8_ENC_OK) goto End;
+      if (frame_skipped) goto Skip;
+      prev_rect_sub = enc->prev_rect_;
+
+
+      // Add this as a key-frame to enc, too.
+      error_code = SetFrame(enc, config, 1, encoded_frame, &frame_skipped);
+      if (error_code != VP8_ENC_OK) goto End;
+      assert(frame_skipped == 0);  // Key-frame cannot be an empty rectangle.
+      prev_rect_key = enc->prev_rect_;
+
+      // Analyze size difference of the two variants.
+      curr_delta = KeyFramePenalty(encoded_frame);
+      if (curr_delta <= enc->best_delta_) {  // Pick this as the key-frame.
+        if (enc->keyframe_ != KEYFRAME_NONE) {
+          EncodedFrame* const old_keyframe = GetFrame(enc, enc->keyframe_);
+          assert(old_keyframe->is_key_frame_);
+          old_keyframe->is_key_frame_ = 0;
+        }
+        encoded_frame->is_key_frame_ = 1;
+        enc->prev_candidate_undecided_ = 1;
+        enc->keyframe_ = (int)position;
+        enc->best_delta_ = curr_delta;
+        enc->flush_count_ = enc->count_ - 1;  // We can flush previous frames.
+      } else {
+        encoded_frame->is_key_frame_ = 0;
+        enc->prev_candidate_undecided_ = 0;
+      }
+      // Note: We need '>=' below because when kmin and kmax are both zero,
+      // count_since_key_frame will always be > kmax.
+      if (enc->count_since_key_frame_ >= enc->options_.kmax) {
+        enc->flush_count_ = enc->count_ - 1;
+        enc->count_since_key_frame_ = 0;
+        enc->keyframe_ = KEYFRAME_NONE;
+        enc->best_delta_ = DELTA_INFINITY;
+      }
+      if (!enc->prev_candidate_undecided_) {
+        enc->prev_rect_ =
+            encoded_frame->is_key_frame_ ? prev_rect_key : prev_rect_sub;
+      }
+    }
+  }
+
+  // Update previous to previous and previous canvases for next call.
+  WebPCopyPixels(enc->curr_canvas_, &enc->prev_canvas_);
+  enc->is_first_frame_ = 0;
+
+ Skip:
+  ok = 1;
+  ++enc->in_frame_count_;
+
+ End:
+  if (!ok || frame_skipped) {
+    FrameRelease(encoded_frame);
+    // We reset some counters, as the frame addition failed/was skipped.
+    --enc->count_;
+    if (!enc->is_first_frame_) --enc->count_since_key_frame_;
+    if (!ok) {
+      MarkError2(enc, "ERROR adding frame. WebPEncodingError", error_code);
+    }
+  }
+  enc->curr_canvas_->error_code = error_code;   // report error_code
+  assert(ok || error_code != VP8_ENC_OK);
+  return ok;
+}
+
+static int FlushFrames(WebPAnimEncoder* const enc) {
+  while (enc->flush_count_ > 0) {
+    WebPMuxError err;
+    EncodedFrame* const curr = GetFrame(enc, 0);
+    const WebPMuxFrameInfo* const info =
+        curr->is_key_frame_ ? &curr->key_frame_ : &curr->sub_frame_;
+    assert(enc->mux_ != NULL);
+    err = WebPMuxPushFrame(enc->mux_, info, 1);
+    if (err != WEBP_MUX_OK) {
+      MarkError2(enc, "ERROR adding frame. WebPMuxError", err);
+      return 0;
+    }
+    if (enc->options_.verbose) {
+      fprintf(stderr, "INFO: Added frame. offset:%d,%d dispose:%d blend:%d\n",
+              info->x_offset, info->y_offset, info->dispose_method,
+              info->blend_method);
+    }
+    ++enc->out_frame_count_;
+    FrameRelease(curr);
+    ++enc->start_;
+    --enc->flush_count_;
+    --enc->count_;
+    if (enc->keyframe_ != KEYFRAME_NONE) --enc->keyframe_;
+  }
+
+  if (enc->count_ == 1 && enc->start_ != 0) {
+    // Move enc->start to index 0.
+    const int enc_start_tmp = (int)enc->start_;
+    EncodedFrame temp = enc->encoded_frames_[0];
+    enc->encoded_frames_[0] = enc->encoded_frames_[enc_start_tmp];
+    enc->encoded_frames_[enc_start_tmp] = temp;
+    FrameRelease(&enc->encoded_frames_[enc_start_tmp]);
+    enc->start_ = 0;
+  }
+  return 1;
+}
+
+#undef DELTA_INFINITY
+#undef KEYFRAME_NONE
+
+int WebPAnimEncoderAdd(WebPAnimEncoder* enc, WebPPicture* frame, int timestamp,
+                       const WebPConfig* encoder_config) {
+  WebPConfig config;
+  int ok;
+
+  if (enc == NULL) {
+    return 0;
+  }
+  MarkNoError(enc);
+
+  if (!enc->is_first_frame_) {
+    // Make sure timestamps are non-decreasing (integer wrap-around is OK).
+    const uint32_t prev_frame_duration =
+        (uint32_t)timestamp - enc->prev_timestamp_;
+    if (prev_frame_duration >= MAX_DURATION) {
+      if (frame != NULL) {
+        frame->error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION;
+      }
+      MarkError(enc, "ERROR adding frame: timestamps must be non-decreasing");
+      return 0;
+    }
+    if (!IncreasePreviousDuration(enc, (int)prev_frame_duration)) {
+      return 0;
+    }
+  } else {
+    enc->first_timestamp_ = timestamp;
+  }
+
+  if (frame == NULL) {  // Special: last call.
+    enc->got_null_frame_ = 1;
+    enc->prev_timestamp_ = timestamp;
+    return 1;
+  }
+
+  if (frame->width != enc->canvas_width_ ||
+      frame->height != enc->canvas_height_) {
+    frame->error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION;
+    MarkError(enc, "ERROR adding frame: Invalid frame dimensions");
+    return 0;
+  }
+
+  if (!frame->use_argb) {  // Convert frame from YUV(A) to ARGB.
+    if (enc->options_.verbose) {
+      fprintf(stderr, "WARNING: Converting frame from YUV(A) to ARGB format; "
+              "this incurs a small loss.\n");
+    }
+    if (!WebPPictureYUVAToARGB(frame)) {
+      MarkError(enc, "ERROR converting frame from YUV(A) to ARGB");
+      return 0;
+    }
+  }
+
+  if (encoder_config != NULL) {
+    if (!WebPValidateConfig(encoder_config)) {
+      MarkError(enc, "ERROR adding frame: Invalid WebPConfig");
+      return 0;
+    }
+    config = *encoder_config;
+  } else {
+    WebPConfigInit(&config);
+    config.lossless = 1;
+  }
+  assert(enc->curr_canvas_ == NULL);
+  enc->curr_canvas_ = frame;  // Store reference.
+  assert(enc->curr_canvas_copy_modified_ == 1);
+  CopyCurrentCanvas(enc);
+
+  ok = CacheFrame(enc, &config) && FlushFrames(enc);
+
+  enc->curr_canvas_ = NULL;
+  enc->curr_canvas_copy_modified_ = 1;
+  if (ok) {
+    enc->prev_timestamp_ = timestamp;
+  }
+  return ok;
+}
+
+// -----------------------------------------------------------------------------
+// Bitstream assembly.
+
+static int DecodeFrameOntoCanvas(const WebPMuxFrameInfo* const frame,
+                                 WebPPicture* const canvas) {
+  const WebPData* const image = &frame->bitstream;
+  WebPPicture sub_image;
+  WebPDecoderConfig config;
+  WebPInitDecoderConfig(&config);
+  WebPUtilClearPic(canvas, NULL);
+  if (WebPGetFeatures(image->bytes, image->size, &config.input) !=
+      VP8_STATUS_OK) {
+    return 0;
+  }
+  if (!WebPPictureView(canvas, frame->x_offset, frame->y_offset,
+                       config.input.width, config.input.height, &sub_image)) {
+    return 0;
+  }
+  config.output.is_external_memory = 1;
+  config.output.colorspace = MODE_BGRA;
+  config.output.u.RGBA.rgba = (uint8_t*)sub_image.argb;
+  config.output.u.RGBA.stride = sub_image.argb_stride * 4;
+  config.output.u.RGBA.size = config.output.u.RGBA.stride * sub_image.height;
+
+  if (WebPDecode(image->bytes, image->size, &config) != VP8_STATUS_OK) {
+    return 0;
+  }
+  return 1;
+}
+
+static int FrameToFullCanvas(WebPAnimEncoder* const enc,
+                             const WebPMuxFrameInfo* const frame,
+                             WebPData* const full_image) {
+  WebPPicture* const canvas_buf = &enc->curr_canvas_copy_;
+  WebPMemoryWriter mem1, mem2;
+  WebPMemoryWriterInit(&mem1);
+  WebPMemoryWriterInit(&mem2);
+
+  if (!DecodeFrameOntoCanvas(frame, canvas_buf)) goto Err;
+  if (!EncodeFrame(&enc->last_config_, canvas_buf, &mem1)) goto Err;
+  GetEncodedData(&mem1, full_image);
+
+  if (enc->options_.allow_mixed) {
+    if (!EncodeFrame(&enc->last_config_reversed_, canvas_buf, &mem2)) goto Err;
+    if (mem2.size < mem1.size) {
+      GetEncodedData(&mem2, full_image);
+      WebPMemoryWriterClear(&mem1);
+    } else {
+      WebPMemoryWriterClear(&mem2);
+    }
+  }
+  return 1;
+
+ Err:
+  WebPMemoryWriterClear(&mem1);
+  WebPMemoryWriterClear(&mem2);
+  return 0;
+}
+
+// Convert a single-frame animation to a non-animated image if appropriate.
+// TODO(urvang): Can we pick one of the two heuristically (based on frame
+// rectangle and/or presence of alpha)?
+static WebPMuxError OptimizeSingleFrame(WebPAnimEncoder* const enc,
+                                        WebPData* const webp_data) {
+  WebPMuxError err = WEBP_MUX_OK;
+  int canvas_width, canvas_height;
+  WebPMuxFrameInfo frame;
+  WebPData full_image;
+  WebPData webp_data2;
+  WebPMux* const mux = WebPMuxCreate(webp_data, 0);
+  if (mux == NULL) return WEBP_MUX_BAD_DATA;
+  assert(enc->out_frame_count_ == 1);
+  WebPDataInit(&frame.bitstream);
+  WebPDataInit(&full_image);
+  WebPDataInit(&webp_data2);
+
+  err = WebPMuxGetFrame(mux, 1, &frame);
+  if (err != WEBP_MUX_OK) goto End;
+  if (frame.id != WEBP_CHUNK_ANMF) goto End;  // Non-animation: nothing to do.
+  err = WebPMuxGetCanvasSize(mux, &canvas_width, &canvas_height);
+  if (err != WEBP_MUX_OK) goto End;
+  if (!FrameToFullCanvas(enc, &frame, &full_image)) {
+    err = WEBP_MUX_BAD_DATA;
+    goto End;
+  }
+  err = WebPMuxSetImage(mux, &full_image, 1);
+  if (err != WEBP_MUX_OK) goto End;
+  err = WebPMuxAssemble(mux, &webp_data2);
+  if (err != WEBP_MUX_OK) goto End;
+
+  if (webp_data2.size < webp_data->size) {  // Pick 'webp_data2' if smaller.
+    WebPDataClear(webp_data);
+    *webp_data = webp_data2;
+    WebPDataInit(&webp_data2);
+  }
+
+ End:
+  WebPDataClear(&frame.bitstream);
+  WebPDataClear(&full_image);
+  WebPMuxDelete(mux);
+  WebPDataClear(&webp_data2);
+  return err;
+}
+
+int WebPAnimEncoderAssemble(WebPAnimEncoder* enc, WebPData* webp_data) {
+  WebPMux* mux;
+  WebPMuxError err;
+
+  if (enc == NULL) {
+    return 0;
+  }
+  MarkNoError(enc);
+
+  if (webp_data == NULL) {
+    MarkError(enc, "ERROR assembling: NULL input");
+    return 0;
+  }
+
+  if (enc->in_frame_count_ == 0) {
+    MarkError(enc, "ERROR: No frames to assemble");
+    return 0;
+  }
+
+  if (!enc->got_null_frame_ && enc->in_frame_count_ > 1 && enc->count_ > 0) {
+    // set duration of the last frame to be avg of durations of previous frames.
+    const double delta_time = enc->prev_timestamp_ - enc->first_timestamp_;
+    const int average_duration = (int)(delta_time / (enc->in_frame_count_ - 1));
+    if (!IncreasePreviousDuration(enc, average_duration)) {
+      return 0;
+    }
+  }
+
+  // Flush any remaining frames.
+  enc->flush_count_ = enc->count_;
+  if (!FlushFrames(enc)) {
+    return 0;
+  }
+
+  // Set definitive canvas size.
+  mux = enc->mux_;
+  err = WebPMuxSetCanvasSize(mux, enc->canvas_width_, enc->canvas_height_);
+  if (err != WEBP_MUX_OK) goto Err;
+
+  err = WebPMuxSetAnimationParams(mux, &enc->options_.anim_params);
+  if (err != WEBP_MUX_OK) goto Err;
+
+  // Assemble into a WebP bitstream.
+  err = WebPMuxAssemble(mux, webp_data);
+  if (err != WEBP_MUX_OK) goto Err;
+
+  if (enc->out_frame_count_ == 1) {
+    err = OptimizeSingleFrame(enc, webp_data);
+    if (err != WEBP_MUX_OK) goto Err;
+  }
+  return 1;
+
+ Err:
+  MarkError2(enc, "ERROR assembling WebP", err);
+  return 0;
+}
+
+const char* WebPAnimEncoderGetError(WebPAnimEncoder* enc) {
+  if (enc == NULL) return NULL;
+  return enc->error_str_;
+}
+
+// -----------------------------------------------------------------------------
diff --git a/third_party/libwebp/mux/animi.h b/third_party/libwebp/mux/animi.h
new file mode 100644
index 0000000..cecaf1f
--- /dev/null
+++ b/third_party/libwebp/mux/animi.h
@@ -0,0 +1,43 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Internal header for animation related functions.
+//
+// Author: Hui Su (huisu@google.com)
+
+#ifndef WEBP_MUX_ANIMI_H_
+#define WEBP_MUX_ANIMI_H_
+
+#include "../webp/mux.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Picks the optimal rectangle between two pictures, starting with initial
+// values of offsets and dimensions that are passed in. The initial
+// values will be clipped, if necessary, to make sure the rectangle is
+// within the canvas. "use_argb" must be true for both pictures.
+// Parameters:
+//   prev_canvas, curr_canvas - (in) two input pictures to compare.
+//   is_lossless, quality - (in) encoding settings.
+//   x_offset, y_offset, width, height - (in/out) rectangle between the two
+//                                                input pictures.
+// Returns true on success.
+int WebPAnimEncoderRefineRect(
+    const struct WebPPicture* const prev_canvas,
+    const struct WebPPicture* const curr_canvas,
+    int is_lossless, float quality, int* const x_offset, int* const y_offset,
+    int* const width, int* const height);
+
+#ifdef __cplusplus
+}    // extern "C"
+#endif
+
+#endif  /* WEBP_MUX_ANIMI_H_ */
diff --git a/third_party/libwebp/mux/muxedit.c b/third_party/libwebp/mux/muxedit.c
new file mode 100644
index 0000000..d2c5305
--- /dev/null
+++ b/third_party/libwebp/mux/muxedit.c
@@ -0,0 +1,657 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Set and delete APIs for mux.
+//
+// Authors: Urvang (urvang@google.com)
+//          Vikas (vikasa@google.com)
+
+#include <assert.h>
+#include "./muxi.h"
+#include "../utils/utils.h"
+
+//------------------------------------------------------------------------------
+// Life of a mux object.
+
+static void MuxInit(WebPMux* const mux) {
+  assert(mux != NULL);
+  memset(mux, 0, sizeof(*mux));
+  mux->canvas_width_ = 0;     // just to be explicit
+  mux->canvas_height_ = 0;
+}
+
+WebPMux* WebPNewInternal(int version) {
+  if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_MUX_ABI_VERSION)) {
+    return NULL;
+  } else {
+    WebPMux* const mux = (WebPMux*)WebPSafeMalloc(1ULL, sizeof(WebPMux));
+    if (mux != NULL) MuxInit(mux);
+    return mux;
+  }
+}
+
+// Delete all images in 'wpi_list'.
+static void DeleteAllImages(WebPMuxImage** const wpi_list) {
+  while (*wpi_list != NULL) {
+    *wpi_list = MuxImageDelete(*wpi_list);
+  }
+}
+
+static void MuxRelease(WebPMux* const mux) {
+  assert(mux != NULL);
+  DeleteAllImages(&mux->images_);
+  ChunkListDelete(&mux->vp8x_);
+  ChunkListDelete(&mux->iccp_);
+  ChunkListDelete(&mux->anim_);
+  ChunkListDelete(&mux->exif_);
+  ChunkListDelete(&mux->xmp_);
+  ChunkListDelete(&mux->unknown_);
+}
+
+void WebPMuxDelete(WebPMux* mux) {
+  if (mux != NULL) {
+    MuxRelease(mux);
+    WebPSafeFree(mux);
+  }
+}
+
+//------------------------------------------------------------------------------
+// Helper method(s).
+
+// Handy MACRO, makes MuxSet() very symmetric to MuxGet().
+#define SWITCH_ID_LIST(INDEX, LIST)                                            \
+  if (idx == (INDEX)) {                                                        \
+    err = ChunkAssignData(&chunk, data, copy_data, tag);                       \
+    if (err == WEBP_MUX_OK) {                                                  \
+      err = ChunkSetNth(&chunk, (LIST), nth);                                  \
+    }                                                                          \
+    return err;                                                                \
+  }
+
+static WebPMuxError MuxSet(WebPMux* const mux, uint32_t tag, uint32_t nth,
+                           const WebPData* const data, int copy_data) {
+  WebPChunk chunk;
+  WebPMuxError err = WEBP_MUX_NOT_FOUND;
+  const CHUNK_INDEX idx = ChunkGetIndexFromTag(tag);
+  assert(mux != NULL);
+  assert(!IsWPI(kChunks[idx].id));
+
+  ChunkInit(&chunk);
+  SWITCH_ID_LIST(IDX_VP8X,    &mux->vp8x_);
+  SWITCH_ID_LIST(IDX_ICCP,    &mux->iccp_);
+  SWITCH_ID_LIST(IDX_ANIM,    &mux->anim_);
+  SWITCH_ID_LIST(IDX_EXIF,    &mux->exif_);
+  SWITCH_ID_LIST(IDX_XMP,     &mux->xmp_);
+  SWITCH_ID_LIST(IDX_UNKNOWN, &mux->unknown_);
+  return err;
+}
+#undef SWITCH_ID_LIST
+
+// Create data for frame given image data, offsets and duration.
+static WebPMuxError CreateFrameData(
+    int width, int height, const WebPMuxFrameInfo* const info,
+    WebPData* const frame) {
+  uint8_t* frame_bytes;
+  const size_t frame_size = kChunks[IDX_ANMF].size;
+
+  assert(width > 0 && height > 0 && info->duration >= 0);
+  assert(info->dispose_method == (info->dispose_method & 1));
+  // Note: assertion on upper bounds is done in PutLE24().
+
+  frame_bytes = (uint8_t*)WebPSafeMalloc(1ULL, frame_size);
+  if (frame_bytes == NULL) return WEBP_MUX_MEMORY_ERROR;
+
+  PutLE24(frame_bytes + 0, info->x_offset / 2);
+  PutLE24(frame_bytes + 3, info->y_offset / 2);
+
+  PutLE24(frame_bytes + 6, width - 1);
+  PutLE24(frame_bytes + 9, height - 1);
+  PutLE24(frame_bytes + 12, info->duration);
+  frame_bytes[15] =
+      (info->blend_method == WEBP_MUX_NO_BLEND ? 2 : 0) |
+      (info->dispose_method == WEBP_MUX_DISPOSE_BACKGROUND ? 1 : 0);
+
+  frame->bytes = frame_bytes;
+  frame->size = frame_size;
+  return WEBP_MUX_OK;
+}
+
+// Outputs image data given a bitstream. The bitstream can either be a
+// single-image WebP file or raw VP8/VP8L data.
+// Also outputs 'is_lossless' to be true if the given bitstream is lossless.
+static WebPMuxError GetImageData(const WebPData* const bitstream,
+                                 WebPData* const image, WebPData* const alpha,
+                                 int* const is_lossless) {
+  WebPDataInit(alpha);  // Default: no alpha.
+  if (bitstream->size < TAG_SIZE ||
+      memcmp(bitstream->bytes, "RIFF", TAG_SIZE)) {
+    // It is NOT webp file data. Return input data as is.
+    *image = *bitstream;
+  } else {
+    // It is webp file data. Extract image data from it.
+    const WebPMuxImage* wpi;
+    WebPMux* const mux = WebPMuxCreate(bitstream, 0);
+    if (mux == NULL) return WEBP_MUX_BAD_DATA;
+    wpi = mux->images_;
+    assert(wpi != NULL && wpi->img_ != NULL);
+    *image = wpi->img_->data_;
+    if (wpi->alpha_ != NULL) {
+      *alpha = wpi->alpha_->data_;
+    }
+    WebPMuxDelete(mux);
+  }
+  *is_lossless = VP8LCheckSignature(image->bytes, image->size);
+  return WEBP_MUX_OK;
+}
+
+static WebPMuxError DeleteChunks(WebPChunk** chunk_list, uint32_t tag) {
+  WebPMuxError err = WEBP_MUX_NOT_FOUND;
+  assert(chunk_list);
+  while (*chunk_list) {
+    WebPChunk* const chunk = *chunk_list;
+    if (chunk->tag_ == tag) {
+      *chunk_list = ChunkDelete(chunk);
+      err = WEBP_MUX_OK;
+    } else {
+      chunk_list = &chunk->next_;
+    }
+  }
+  return err;
+}
+
+static WebPMuxError MuxDeleteAllNamedData(WebPMux* const mux, uint32_t tag) {
+  const WebPChunkId id = ChunkGetIdFromTag(tag);
+  assert(mux != NULL);
+  if (IsWPI(id)) return WEBP_MUX_INVALID_ARGUMENT;
+  return DeleteChunks(MuxGetChunkListFromId(mux, id), tag);
+}
+
+//------------------------------------------------------------------------------
+// Set API(s).
+
+WebPMuxError WebPMuxSetChunk(WebPMux* mux, const char fourcc[4],
+                             const WebPData* chunk_data, int copy_data) {
+  uint32_t tag;
+  WebPMuxError err;
+  if (mux == NULL || fourcc == NULL || chunk_data == NULL ||
+      chunk_data->bytes == NULL || chunk_data->size > MAX_CHUNK_PAYLOAD) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+  tag = ChunkGetTagFromFourCC(fourcc);
+
+  // Delete existing chunk(s) with the same 'fourcc'.
+  err = MuxDeleteAllNamedData(mux, tag);
+  if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;
+
+  // Add the given chunk.
+  return MuxSet(mux, tag, 1, chunk_data, copy_data);
+}
+
+// Creates a chunk from given 'data' and sets it as 1st chunk in 'chunk_list'.
+static WebPMuxError AddDataToChunkList(
+    const WebPData* const data, int copy_data, uint32_t tag,
+    WebPChunk** chunk_list) {
+  WebPChunk chunk;
+  WebPMuxError err;
+  ChunkInit(&chunk);
+  err = ChunkAssignData(&chunk, data, copy_data, tag);
+  if (err != WEBP_MUX_OK) goto Err;
+  err = ChunkSetNth(&chunk, chunk_list, 1);
+  if (err != WEBP_MUX_OK) goto Err;
+  return WEBP_MUX_OK;
+ Err:
+  ChunkRelease(&chunk);
+  return err;
+}
+
+// Extracts image & alpha data from the given bitstream and then sets wpi.alpha_
+// and wpi.img_ appropriately.
+static WebPMuxError SetAlphaAndImageChunks(
+    const WebPData* const bitstream, int copy_data, WebPMuxImage* const wpi) {
+  int is_lossless = 0;
+  WebPData image, alpha;
+  WebPMuxError err = GetImageData(bitstream, &image, &alpha, &is_lossless);
+  const int image_tag =
+      is_lossless ? kChunks[IDX_VP8L].tag : kChunks[IDX_VP8].tag;
+  if (err != WEBP_MUX_OK) return err;
+  if (alpha.bytes != NULL) {
+    err = AddDataToChunkList(&alpha, copy_data, kChunks[IDX_ALPHA].tag,
+                             &wpi->alpha_);
+    if (err != WEBP_MUX_OK) return err;
+  }
+  err = AddDataToChunkList(&image, copy_data, image_tag, &wpi->img_);
+  if (err != WEBP_MUX_OK) return err;
+  return MuxImageFinalize(wpi) ? WEBP_MUX_OK : WEBP_MUX_INVALID_ARGUMENT;
+}
+
+WebPMuxError WebPMuxSetImage(WebPMux* mux, const WebPData* bitstream,
+                             int copy_data) {
+  WebPMuxImage wpi;
+  WebPMuxError err;
+
+  // Sanity checks.
+  if (mux == NULL || bitstream == NULL || bitstream->bytes == NULL ||
+      bitstream->size > MAX_CHUNK_PAYLOAD) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+
+  if (mux->images_ != NULL) {
+    // Only one 'simple image' can be added in mux. So, remove present images.
+    DeleteAllImages(&mux->images_);
+  }
+
+  MuxImageInit(&wpi);
+  err = SetAlphaAndImageChunks(bitstream, copy_data, &wpi);
+  if (err != WEBP_MUX_OK) goto Err;
+
+  // Add this WebPMuxImage to mux.
+  err = MuxImagePush(&wpi, &mux->images_);
+  if (err != WEBP_MUX_OK) goto Err;
+
+  // All is well.
+  return WEBP_MUX_OK;
+
+ Err:  // Something bad happened.
+  MuxImageRelease(&wpi);
+  return err;
+}
+
+WebPMuxError WebPMuxPushFrame(WebPMux* mux, const WebPMuxFrameInfo* info,
+                              int copy_data) {
+  WebPMuxImage wpi;
+  WebPMuxError err;
+  const WebPData* const bitstream = &info->bitstream;
+
+  // Sanity checks.
+  if (mux == NULL || info == NULL) return WEBP_MUX_INVALID_ARGUMENT;
+
+  if (info->id != WEBP_CHUNK_ANMF) return WEBP_MUX_INVALID_ARGUMENT;
+
+  if (bitstream->bytes == NULL || bitstream->size > MAX_CHUNK_PAYLOAD) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+
+  if (mux->images_ != NULL) {
+    const WebPMuxImage* const image = mux->images_;
+    const uint32_t image_id = (image->header_ != NULL) ?
+        ChunkGetIdFromTag(image->header_->tag_) : WEBP_CHUNK_IMAGE;
+    if (image_id != info->id) {
+      return WEBP_MUX_INVALID_ARGUMENT;  // Conflicting frame types.
+    }
+  }
+
+  MuxImageInit(&wpi);
+  err = SetAlphaAndImageChunks(bitstream, copy_data, &wpi);
+  if (err != WEBP_MUX_OK) goto Err;
+  assert(wpi.img_ != NULL);  // As SetAlphaAndImageChunks() was successful.
+
+  {
+    WebPData frame;
+    const uint32_t tag = kChunks[IDX_ANMF].tag;
+    WebPMuxFrameInfo tmp = *info;
+    tmp.x_offset &= ~1;  // Snap offsets to even.
+    tmp.y_offset &= ~1;
+    if (tmp.x_offset < 0 || tmp.x_offset >= MAX_POSITION_OFFSET ||
+        tmp.y_offset < 0 || tmp.y_offset >= MAX_POSITION_OFFSET ||
+        (tmp.duration < 0 || tmp.duration >= MAX_DURATION) ||
+        tmp.dispose_method != (tmp.dispose_method & 1)) {
+      err = WEBP_MUX_INVALID_ARGUMENT;
+      goto Err;
+    }
+    err = CreateFrameData(wpi.width_, wpi.height_, &tmp, &frame);
+    if (err != WEBP_MUX_OK) goto Err;
+    // Add frame chunk (with copy_data = 1).
+    err = AddDataToChunkList(&frame, 1, tag, &wpi.header_);
+    WebPDataClear(&frame);  // frame owned by wpi.header_ now.
+    if (err != WEBP_MUX_OK) goto Err;
+  }
+
+  // Add this WebPMuxImage to mux.
+  err = MuxImagePush(&wpi, &mux->images_);
+  if (err != WEBP_MUX_OK) goto Err;
+
+  // All is well.
+  return WEBP_MUX_OK;
+
+ Err:  // Something bad happened.
+  MuxImageRelease(&wpi);
+  return err;
+}
+
+WebPMuxError WebPMuxSetAnimationParams(WebPMux* mux,
+                                       const WebPMuxAnimParams* params) {
+  WebPMuxError err;
+  uint8_t data[ANIM_CHUNK_SIZE];
+  const WebPData anim = { data, ANIM_CHUNK_SIZE };
+
+  if (mux == NULL || params == NULL) return WEBP_MUX_INVALID_ARGUMENT;
+  if (params->loop_count < 0 || params->loop_count >= MAX_LOOP_COUNT) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+
+  // Delete any existing ANIM chunk(s).
+  err = MuxDeleteAllNamedData(mux, kChunks[IDX_ANIM].tag);
+  if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;
+
+  // Set the animation parameters.
+  PutLE32(data, params->bgcolor);
+  PutLE16(data + 4, params->loop_count);
+  return MuxSet(mux, kChunks[IDX_ANIM].tag, 1, &anim, 1);
+}
+
+WebPMuxError WebPMuxSetCanvasSize(WebPMux* mux,
+                                  int width, int height) {
+  WebPMuxError err;
+  if (mux == NULL) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+  if (width < 0 || height < 0 ||
+      width > MAX_CANVAS_SIZE || height > MAX_CANVAS_SIZE) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+  if (width * (uint64_t)height >= MAX_IMAGE_AREA) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+  if ((width * height) == 0 && (width | height) != 0) {
+    // one of width / height is zero, but not both -> invalid!
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+  // If we already assembled a VP8X chunk, invalidate it.
+  err = MuxDeleteAllNamedData(mux, kChunks[IDX_VP8X].tag);
+  if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;
+
+  mux->canvas_width_ = width;
+  mux->canvas_height_ = height;
+  return WEBP_MUX_OK;
+}
+
+//------------------------------------------------------------------------------
+// Delete API(s).
+
+WebPMuxError WebPMuxDeleteChunk(WebPMux* mux, const char fourcc[4]) {
+  if (mux == NULL || fourcc == NULL) return WEBP_MUX_INVALID_ARGUMENT;
+  return MuxDeleteAllNamedData(mux, ChunkGetTagFromFourCC(fourcc));
+}
+
+WebPMuxError WebPMuxDeleteFrame(WebPMux* mux, uint32_t nth) {
+  if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT;
+  return MuxImageDeleteNth(&mux->images_, nth);
+}
+
+//------------------------------------------------------------------------------
+// Assembly of the WebP RIFF file.
+
+static WebPMuxError GetFrameInfo(
+    const WebPChunk* const frame_chunk,
+    int* const x_offset, int* const y_offset, int* const duration) {
+  const WebPData* const data = &frame_chunk->data_;
+  const size_t expected_data_size = ANMF_CHUNK_SIZE;
+  assert(frame_chunk->tag_ == kChunks[IDX_ANMF].tag);
+  assert(frame_chunk != NULL);
+  if (data->size != expected_data_size) return WEBP_MUX_INVALID_ARGUMENT;
+
+  *x_offset = 2 * GetLE24(data->bytes + 0);
+  *y_offset = 2 * GetLE24(data->bytes + 3);
+  *duration = GetLE24(data->bytes + 12);
+  return WEBP_MUX_OK;
+}
+
+static WebPMuxError GetImageInfo(const WebPMuxImage* const wpi,
+                                 int* const x_offset, int* const y_offset,
+                                 int* const duration,
+                                 int* const width, int* const height) {
+  const WebPChunk* const frame_chunk = wpi->header_;
+  WebPMuxError err;
+  assert(wpi != NULL);
+  assert(frame_chunk != NULL);
+
+  // Get offsets and duration from ANMF chunk.
+  err = GetFrameInfo(frame_chunk, x_offset, y_offset, duration);
+  if (err != WEBP_MUX_OK) return err;
+
+  // Get width and height from VP8/VP8L chunk.
+  if (width != NULL) *width = wpi->width_;
+  if (height != NULL) *height = wpi->height_;
+  return WEBP_MUX_OK;
+}
+
+// Returns the tightest dimension for the canvas considering the image list.
+static WebPMuxError GetAdjustedCanvasSize(const WebPMux* const mux,
+                                          int* const width, int* const height) {
+  WebPMuxImage* wpi = NULL;
+  assert(mux != NULL);
+  assert(width != NULL && height != NULL);
+
+  wpi = mux->images_;
+  assert(wpi != NULL);
+  assert(wpi->img_ != NULL);
+
+  if (wpi->next_ != NULL) {
+    int max_x = 0, max_y = 0;
+    // if we have a chain of wpi's, header_ is necessarily set
+    assert(wpi->header_ != NULL);
+    // Aggregate the bounding box for animation frames.
+    for (; wpi != NULL; wpi = wpi->next_) {
+      int x_offset = 0, y_offset = 0, duration = 0, w = 0, h = 0;
+      const WebPMuxError err = GetImageInfo(wpi, &x_offset, &y_offset,
+                                            &duration, &w, &h);
+      const int max_x_pos = x_offset + w;
+      const int max_y_pos = y_offset + h;
+      if (err != WEBP_MUX_OK) return err;
+      assert(x_offset < MAX_POSITION_OFFSET);
+      assert(y_offset < MAX_POSITION_OFFSET);
+
+      if (max_x_pos > max_x) max_x = max_x_pos;
+      if (max_y_pos > max_y) max_y = max_y_pos;
+    }
+    *width = max_x;
+    *height = max_y;
+  } else {
+    // For a single image, canvas dimensions are same as image dimensions.
+    *width = wpi->width_;
+    *height = wpi->height_;
+  }
+  return WEBP_MUX_OK;
+}
+
+// VP8X format:
+// Total Size : 10,
+// Flags  : 4 bytes,
+// Width  : 3 bytes,
+// Height : 3 bytes.
+static WebPMuxError CreateVP8XChunk(WebPMux* const mux) {
+  WebPMuxError err = WEBP_MUX_OK;
+  uint32_t flags = 0;
+  int width = 0;
+  int height = 0;
+  uint8_t data[VP8X_CHUNK_SIZE];
+  const WebPData vp8x = { data, VP8X_CHUNK_SIZE };
+  const WebPMuxImage* images = NULL;
+
+  assert(mux != NULL);
+  images = mux->images_;  // First image.
+  if (images == NULL || images->img_ == NULL ||
+      images->img_->data_.bytes == NULL) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+
+  // If VP8X chunk(s) is(are) already present, remove them (and later add new
+  // VP8X chunk with updated flags).
+  err = MuxDeleteAllNamedData(mux, kChunks[IDX_VP8X].tag);
+  if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;
+
+  // Set flags.
+  if (mux->iccp_ != NULL && mux->iccp_->data_.bytes != NULL) {
+    flags |= ICCP_FLAG;
+  }
+  if (mux->exif_ != NULL && mux->exif_->data_.bytes != NULL) {
+    flags |= EXIF_FLAG;
+  }
+  if (mux->xmp_ != NULL && mux->xmp_->data_.bytes != NULL) {
+    flags |= XMP_FLAG;
+  }
+  if (images->header_ != NULL) {
+    if (images->header_->tag_ == kChunks[IDX_ANMF].tag) {
+      // This is an image with animation.
+      flags |= ANIMATION_FLAG;
+    }
+  }
+  if (MuxImageCount(images, WEBP_CHUNK_ALPHA) > 0) {
+    flags |= ALPHA_FLAG;  // Some images have an alpha channel.
+  }
+
+  err = GetAdjustedCanvasSize(mux, &width, &height);
+  if (err != WEBP_MUX_OK) return err;
+
+  if (width <= 0 || height <= 0) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+  if (width > MAX_CANVAS_SIZE || height > MAX_CANVAS_SIZE) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+
+  if (mux->canvas_width_ != 0 || mux->canvas_height_ != 0) {
+    if (width > mux->canvas_width_ || height > mux->canvas_height_) {
+      return WEBP_MUX_INVALID_ARGUMENT;
+    }
+    width = mux->canvas_width_;
+    height = mux->canvas_height_;
+  }
+
+  if (flags == 0 && mux->unknown_ == NULL) {
+    // For simple file format, VP8X chunk should not be added.
+    return WEBP_MUX_OK;
+  }
+
+  if (MuxHasAlpha(images)) {
+    // This means some frames explicitly/implicitly contain alpha.
+    // Note: This 'flags' update must NOT be done for a lossless image
+    // without a VP8X chunk!
+    flags |= ALPHA_FLAG;
+  }
+
+  PutLE32(data + 0, flags);   // VP8X chunk flags.
+  PutLE24(data + 4, width - 1);   // canvas width.
+  PutLE24(data + 7, height - 1);  // canvas height.
+
+  return MuxSet(mux, kChunks[IDX_VP8X].tag, 1, &vp8x, 1);
+}
+
+// Cleans up 'mux' by removing any unnecessary chunks.
+static WebPMuxError MuxCleanup(WebPMux* const mux) {
+  int num_frames;
+  int num_anim_chunks;
+
+  // If we have an image with a single frame, and its rectangle
+  // covers the whole canvas, convert it to a non-animated image
+  // (to avoid writing ANMF chunk unnecessarily).
+  WebPMuxError err = WebPMuxNumChunks(mux, kChunks[IDX_ANMF].id, &num_frames);
+  if (err != WEBP_MUX_OK) return err;
+  if (num_frames == 1) {
+    WebPMuxImage* frame = NULL;
+    err = MuxImageGetNth((const WebPMuxImage**)&mux->images_, 1, &frame);
+    assert(err == WEBP_MUX_OK);  // We know that one frame does exist.
+    assert(frame != NULL);
+    if (frame->header_ != NULL &&
+        ((mux->canvas_width_ == 0 && mux->canvas_height_ == 0) ||
+         (frame->width_ == mux->canvas_width_ &&
+          frame->height_ == mux->canvas_height_))) {
+      assert(frame->header_->tag_ == kChunks[IDX_ANMF].tag);
+      ChunkDelete(frame->header_);  // Removes ANMF chunk.
+      frame->header_ = NULL;
+      num_frames = 0;
+    }
+  }
+  // Remove ANIM chunk if this is a non-animated image.
+  err = WebPMuxNumChunks(mux, kChunks[IDX_ANIM].id, &num_anim_chunks);
+  if (err != WEBP_MUX_OK) return err;
+  if (num_anim_chunks >= 1 && num_frames == 0) {
+    err = MuxDeleteAllNamedData(mux, kChunks[IDX_ANIM].tag);
+    if (err != WEBP_MUX_OK) return err;
+  }
+  return WEBP_MUX_OK;
+}
+
+// Total size of a list of images.
+static size_t ImageListDiskSize(const WebPMuxImage* wpi_list) {
+  size_t size = 0;
+  while (wpi_list != NULL) {
+    size += MuxImageDiskSize(wpi_list);
+    wpi_list = wpi_list->next_;
+  }
+  return size;
+}
+
+// Write out the given list of images into 'dst'.
+static uint8_t* ImageListEmit(const WebPMuxImage* wpi_list, uint8_t* dst) {
+  while (wpi_list != NULL) {
+    dst = MuxImageEmit(wpi_list, dst);
+    wpi_list = wpi_list->next_;
+  }
+  return dst;
+}
+
+WebPMuxError WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data) {
+  size_t size = 0;
+  uint8_t* data = NULL;
+  uint8_t* dst = NULL;
+  WebPMuxError err;
+
+  if (assembled_data == NULL) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+  // Clean up returned data, in case something goes wrong.
+  memset(assembled_data, 0, sizeof(*assembled_data));
+
+  if (mux == NULL) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+
+  // Finalize mux.
+  err = MuxCleanup(mux);
+  if (err != WEBP_MUX_OK) return err;
+  err = CreateVP8XChunk(mux);
+  if (err != WEBP_MUX_OK) return err;
+
+  // Allocate data.
+  size = ChunkListDiskSize(mux->vp8x_) + ChunkListDiskSize(mux->iccp_)
+       + ChunkListDiskSize(mux->anim_) + ImageListDiskSize(mux->images_)
+       + ChunkListDiskSize(mux->exif_) + ChunkListDiskSize(mux->xmp_)
+       + ChunkListDiskSize(mux->unknown_) + RIFF_HEADER_SIZE;
+
+  data = (uint8_t*)WebPSafeMalloc(1ULL, size);
+  if (data == NULL) return WEBP_MUX_MEMORY_ERROR;
+
+  // Emit header & chunks.
+  dst = MuxEmitRiffHeader(data, size);
+  dst = ChunkListEmit(mux->vp8x_, dst);
+  dst = ChunkListEmit(mux->iccp_, dst);
+  dst = ChunkListEmit(mux->anim_, dst);
+  dst = ImageListEmit(mux->images_, dst);
+  dst = ChunkListEmit(mux->exif_, dst);
+  dst = ChunkListEmit(mux->xmp_, dst);
+  dst = ChunkListEmit(mux->unknown_, dst);
+  assert(dst == data + size);
+
+  // Validate mux.
+  err = MuxValidate(mux);
+  if (err != WEBP_MUX_OK) {
+    WebPSafeFree(data);
+    data = NULL;
+    size = 0;
+  }
+
+  // Finalize data.
+  assembled_data->bytes = data;
+  assembled_data->size = size;
+
+  return err;
+}
+
+//------------------------------------------------------------------------------
diff --git a/third_party/libwebp/mux/muxi.h b/third_party/libwebp/mux/muxi.h
new file mode 100644
index 0000000..e6606aa5
--- /dev/null
+++ b/third_party/libwebp/mux/muxi.h
@@ -0,0 +1,230 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Internal header for mux library.
+//
+// Author: Urvang (urvang@google.com)
+
+#ifndef WEBP_MUX_MUXI_H_
+#define WEBP_MUX_MUXI_H_
+
+#include <stdlib.h>
+#include "../dec/vp8i_dec.h"
+#include "../dec/vp8li_dec.h"
+#include "../webp/mux.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//------------------------------------------------------------------------------
+// Defines and constants.
+
+#define MUX_MAJ_VERSION 0
+#define MUX_MIN_VERSION 4
+#define MUX_REV_VERSION 0
+
+// Chunk object.
+typedef struct WebPChunk WebPChunk;
+struct WebPChunk {
+  uint32_t        tag_;
+  int             owner_;  // True if *data_ memory is owned internally.
+                           // VP8X, ANIM, and other internally created chunks
+                           // like ANMF are always owned.
+  WebPData        data_;
+  WebPChunk*      next_;
+};
+
+// MuxImage object. Store a full WebP image (including ANMF chunk, ALPH
+// chunk and VP8/VP8L chunk),
+typedef struct WebPMuxImage WebPMuxImage;
+struct WebPMuxImage {
+  WebPChunk*  header_;      // Corresponds to WEBP_CHUNK_ANMF.
+  WebPChunk*  alpha_;       // Corresponds to WEBP_CHUNK_ALPHA.
+  WebPChunk*  img_;         // Corresponds to WEBP_CHUNK_IMAGE.
+  WebPChunk*  unknown_;     // Corresponds to WEBP_CHUNK_UNKNOWN.
+  int         width_;
+  int         height_;
+  int         has_alpha_;   // Through ALPH chunk or as part of VP8L.
+  int         is_partial_;  // True if only some of the chunks are filled.
+  WebPMuxImage* next_;
+};
+
+// Main mux object. Stores data chunks.
+struct WebPMux {
+  WebPMuxImage*   images_;
+  WebPChunk*      iccp_;
+  WebPChunk*      exif_;
+  WebPChunk*      xmp_;
+  WebPChunk*      anim_;
+  WebPChunk*      vp8x_;
+
+  WebPChunk*      unknown_;
+  int             canvas_width_;
+  int             canvas_height_;
+};
+
+// CHUNK_INDEX enum: used for indexing within 'kChunks' (defined below) only.
+// Note: the reason for having two enums ('WebPChunkId' and 'CHUNK_INDEX') is to
+// allow two different chunks to have the same id (e.g. WebPChunkId
+// 'WEBP_CHUNK_IMAGE' can correspond to CHUNK_INDEX 'IDX_VP8' or 'IDX_VP8L').
+typedef enum {
+  IDX_VP8X = 0,
+  IDX_ICCP,
+  IDX_ANIM,
+  IDX_ANMF,
+  IDX_ALPHA,
+  IDX_VP8,
+  IDX_VP8L,
+  IDX_EXIF,
+  IDX_XMP,
+  IDX_UNKNOWN,
+
+  IDX_NIL,
+  IDX_LAST_CHUNK
+} CHUNK_INDEX;
+
+#define NIL_TAG 0x00000000u  // To signal void chunk.
+
+typedef struct {
+  uint32_t      tag;
+  WebPChunkId   id;
+  uint32_t      size;
+} ChunkInfo;
+
+extern const ChunkInfo kChunks[IDX_LAST_CHUNK];
+
+//------------------------------------------------------------------------------
+// Chunk object management.
+
+// Initialize.
+void ChunkInit(WebPChunk* const chunk);
+
+// Get chunk index from chunk tag. Returns IDX_UNKNOWN if not found.
+CHUNK_INDEX ChunkGetIndexFromTag(uint32_t tag);
+
+// Get chunk id from chunk tag. Returns WEBP_CHUNK_UNKNOWN if not found.
+WebPChunkId ChunkGetIdFromTag(uint32_t tag);
+
+// Convert a fourcc string to a tag.
+uint32_t ChunkGetTagFromFourCC(const char fourcc[4]);
+
+// Get chunk index from fourcc. Returns IDX_UNKNOWN if given fourcc is unknown.
+CHUNK_INDEX ChunkGetIndexFromFourCC(const char fourcc[4]);
+
+// Search for nth chunk with given 'tag' in the chunk list.
+// nth = 0 means "last of the list".
+WebPChunk* ChunkSearchList(WebPChunk* first, uint32_t nth, uint32_t tag);
+
+// Fill the chunk with the given data.
+WebPMuxError ChunkAssignData(WebPChunk* chunk, const WebPData* const data,
+                             int copy_data, uint32_t tag);
+
+// Sets 'chunk' at nth position in the 'chunk_list'.
+// nth = 0 has the special meaning "last of the list".
+// On success ownership is transferred from 'chunk' to the 'chunk_list'.
+WebPMuxError ChunkSetNth(WebPChunk* chunk, WebPChunk** chunk_list,
+                         uint32_t nth);
+
+// Releases chunk and returns chunk->next_.
+WebPChunk* ChunkRelease(WebPChunk* const chunk);
+
+// Deletes given chunk & returns chunk->next_.
+WebPChunk* ChunkDelete(WebPChunk* const chunk);
+
+// Deletes all chunks in the given chunk list.
+void ChunkListDelete(WebPChunk** const chunk_list);
+
+// Returns size of the chunk including chunk header and padding byte (if any).
+static WEBP_INLINE size_t SizeWithPadding(size_t chunk_size) {
+  return CHUNK_HEADER_SIZE + ((chunk_size + 1) & ~1U);
+}
+
+// Size of a chunk including header and padding.
+static WEBP_INLINE size_t ChunkDiskSize(const WebPChunk* chunk) {
+  const size_t data_size = chunk->data_.size;
+  assert(data_size < MAX_CHUNK_PAYLOAD);
+  return SizeWithPadding(data_size);
+}
+
+// Total size of a list of chunks.
+size_t ChunkListDiskSize(const WebPChunk* chunk_list);
+
+// Write out the given list of chunks into 'dst'.
+uint8_t* ChunkListEmit(const WebPChunk* chunk_list, uint8_t* dst);
+
+//------------------------------------------------------------------------------
+// MuxImage object management.
+
+// Initialize.
+void MuxImageInit(WebPMuxImage* const wpi);
+
+// Releases image 'wpi' and returns wpi->next.
+WebPMuxImage* MuxImageRelease(WebPMuxImage* const wpi);
+
+// Delete image 'wpi' and return the next image in the list or NULL.
+// 'wpi' can be NULL.
+WebPMuxImage* MuxImageDelete(WebPMuxImage* const wpi);
+
+// Count number of images matching the given tag id in the 'wpi_list'.
+// If id == WEBP_CHUNK_NIL, all images will be matched.
+int MuxImageCount(const WebPMuxImage* wpi_list, WebPChunkId id);
+
+// Update width/height/has_alpha info from chunks within wpi.
+// Also remove ALPH chunk if not needed.
+int MuxImageFinalize(WebPMuxImage* const wpi);
+
+// Check if given ID corresponds to an image related chunk.
+static WEBP_INLINE int IsWPI(WebPChunkId id) {
+  switch (id) {
+    case WEBP_CHUNK_ANMF:
+    case WEBP_CHUNK_ALPHA:
+    case WEBP_CHUNK_IMAGE:  return 1;
+    default:        return 0;
+  }
+}
+
+// Pushes 'wpi' at the end of 'wpi_list'.
+WebPMuxError MuxImagePush(const WebPMuxImage* wpi, WebPMuxImage** wpi_list);
+
+// Delete nth image in the image list.
+WebPMuxError MuxImageDeleteNth(WebPMuxImage** wpi_list, uint32_t nth);
+
+// Get nth image in the image list.
+WebPMuxError MuxImageGetNth(const WebPMuxImage** wpi_list, uint32_t nth,
+                            WebPMuxImage** wpi);
+
+// Total size of the given image.
+size_t MuxImageDiskSize(const WebPMuxImage* const wpi);
+
+// Write out the given image into 'dst'.
+uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst);
+
+//------------------------------------------------------------------------------
+// Helper methods for mux.
+
+// Checks if the given image list contains at least one image with alpha.
+int MuxHasAlpha(const WebPMuxImage* images);
+
+// Write out RIFF header into 'data', given total data size 'size'.
+uint8_t* MuxEmitRiffHeader(uint8_t* const data, size_t size);
+
+// Returns the list where chunk with given ID is to be inserted in mux.
+WebPChunk** MuxGetChunkListFromId(const WebPMux* mux, WebPChunkId id);
+
+// Validates the given mux object.
+WebPMuxError MuxValidate(const WebPMux* const mux);
+
+//------------------------------------------------------------------------------
+
+#ifdef __cplusplus
+}    // extern "C"
+#endif
+
+#endif  /* WEBP_MUX_MUXI_H_ */
diff --git a/third_party/libwebp/mux/muxinternal.c b/third_party/libwebp/mux/muxinternal.c
new file mode 100644
index 0000000..387b57e
--- /dev/null
+++ b/third_party/libwebp/mux/muxinternal.c
@@ -0,0 +1,538 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Internal objects and utils for mux.
+//
+// Authors: Urvang (urvang@google.com)
+//          Vikas (vikasa@google.com)
+
+#include <assert.h>
+#include "./muxi.h"
+#include "../utils/utils.h"
+
+#define UNDEFINED_CHUNK_SIZE ((uint32_t)(-1))
+
+const ChunkInfo kChunks[] = {
+  { MKFOURCC('V', 'P', '8', 'X'),  WEBP_CHUNK_VP8X,    VP8X_CHUNK_SIZE },
+  { MKFOURCC('I', 'C', 'C', 'P'),  WEBP_CHUNK_ICCP,    UNDEFINED_CHUNK_SIZE },
+  { MKFOURCC('A', 'N', 'I', 'M'),  WEBP_CHUNK_ANIM,    ANIM_CHUNK_SIZE },
+  { MKFOURCC('A', 'N', 'M', 'F'),  WEBP_CHUNK_ANMF,    ANMF_CHUNK_SIZE },
+  { MKFOURCC('A', 'L', 'P', 'H'),  WEBP_CHUNK_ALPHA,   UNDEFINED_CHUNK_SIZE },
+  { MKFOURCC('V', 'P', '8', ' '),  WEBP_CHUNK_IMAGE,   UNDEFINED_CHUNK_SIZE },
+  { MKFOURCC('V', 'P', '8', 'L'),  WEBP_CHUNK_IMAGE,   UNDEFINED_CHUNK_SIZE },
+  { MKFOURCC('E', 'X', 'I', 'F'),  WEBP_CHUNK_EXIF,    UNDEFINED_CHUNK_SIZE },
+  { MKFOURCC('X', 'M', 'P', ' '),  WEBP_CHUNK_XMP,     UNDEFINED_CHUNK_SIZE },
+  { NIL_TAG,                       WEBP_CHUNK_UNKNOWN, UNDEFINED_CHUNK_SIZE },
+
+  { NIL_TAG,                       WEBP_CHUNK_NIL,     UNDEFINED_CHUNK_SIZE }
+};
+
+//------------------------------------------------------------------------------
+
+int WebPGetMuxVersion(void) {
+  return (MUX_MAJ_VERSION << 16) | (MUX_MIN_VERSION << 8) | MUX_REV_VERSION;
+}
+
+//------------------------------------------------------------------------------
+// Life of a chunk object.
+
+void ChunkInit(WebPChunk* const chunk) {
+  assert(chunk);
+  memset(chunk, 0, sizeof(*chunk));
+  chunk->tag_ = NIL_TAG;
+}
+
+WebPChunk* ChunkRelease(WebPChunk* const chunk) {
+  WebPChunk* next;
+  if (chunk == NULL) return NULL;
+  if (chunk->owner_) {
+    WebPDataClear(&chunk->data_);
+  }
+  next = chunk->next_;
+  ChunkInit(chunk);
+  return next;
+}
+
+//------------------------------------------------------------------------------
+// Chunk misc methods.
+
+CHUNK_INDEX ChunkGetIndexFromTag(uint32_t tag) {
+  int i;
+  for (i = 0; kChunks[i].tag != NIL_TAG; ++i) {
+    if (tag == kChunks[i].tag) return (CHUNK_INDEX)i;
+  }
+  return IDX_UNKNOWN;
+}
+
+WebPChunkId ChunkGetIdFromTag(uint32_t tag) {
+  int i;
+  for (i = 0; kChunks[i].tag != NIL_TAG; ++i) {
+    if (tag == kChunks[i].tag) return kChunks[i].id;
+  }
+  return WEBP_CHUNK_UNKNOWN;
+}
+
+uint32_t ChunkGetTagFromFourCC(const char fourcc[4]) {
+  return MKFOURCC(fourcc[0], fourcc[1], fourcc[2], fourcc[3]);
+}
+
+CHUNK_INDEX ChunkGetIndexFromFourCC(const char fourcc[4]) {
+  const uint32_t tag = ChunkGetTagFromFourCC(fourcc);
+  return ChunkGetIndexFromTag(tag);
+}
+
+//------------------------------------------------------------------------------
+// Chunk search methods.
+
+// Returns next chunk in the chunk list with the given tag.
+static WebPChunk* ChunkSearchNextInList(WebPChunk* chunk, uint32_t tag) {
+  while (chunk != NULL && chunk->tag_ != tag) {
+    chunk = chunk->next_;
+  }
+  return chunk;
+}
+
+WebPChunk* ChunkSearchList(WebPChunk* first, uint32_t nth, uint32_t tag) {
+  uint32_t iter = nth;
+  first = ChunkSearchNextInList(first, tag);
+  if (first == NULL) return NULL;
+
+  while (--iter != 0) {
+    WebPChunk* next_chunk = ChunkSearchNextInList(first->next_, tag);
+    if (next_chunk == NULL) break;
+    first = next_chunk;
+  }
+  return ((nth > 0) && (iter > 0)) ? NULL : first;
+}
+
+// Outputs a pointer to 'prev_chunk->next_',
+//   where 'prev_chunk' is the pointer to the chunk at position (nth - 1).
+// Returns true if nth chunk was found.
+static int ChunkSearchListToSet(WebPChunk** chunk_list, uint32_t nth,
+                                WebPChunk*** const location) {
+  uint32_t count = 0;
+  assert(chunk_list != NULL);
+  *location = chunk_list;
+
+  while (*chunk_list != NULL) {
+    WebPChunk* const cur_chunk = *chunk_list;
+    ++count;
+    if (count == nth) return 1;  // Found.
+    chunk_list = &cur_chunk->next_;
+    *location = chunk_list;
+  }
+
+  // *chunk_list is ok to be NULL if adding at last location.
+  return (nth == 0 || (count == nth - 1)) ? 1 : 0;
+}
+
+//------------------------------------------------------------------------------
+// Chunk writer methods.
+
+WebPMuxError ChunkAssignData(WebPChunk* chunk, const WebPData* const data,
+                             int copy_data, uint32_t tag) {
+  // For internally allocated chunks, always copy data & make it owner of data.
+  if (tag == kChunks[IDX_VP8X].tag || tag == kChunks[IDX_ANIM].tag) {
+    copy_data = 1;
+  }
+
+  ChunkRelease(chunk);
+
+  if (data != NULL) {
+    if (copy_data) {        // Copy data.
+      if (!WebPDataCopy(data, &chunk->data_)) return WEBP_MUX_MEMORY_ERROR;
+      chunk->owner_ = 1;    // Chunk is owner of data.
+    } else {                // Don't copy data.
+      chunk->data_ = *data;
+    }
+  }
+  chunk->tag_ = tag;
+  return WEBP_MUX_OK;
+}
+
+WebPMuxError ChunkSetNth(WebPChunk* chunk, WebPChunk** chunk_list,
+                         uint32_t nth) {
+  WebPChunk* new_chunk;
+
+  if (!ChunkSearchListToSet(chunk_list, nth, &chunk_list)) {
+    return WEBP_MUX_NOT_FOUND;
+  }
+
+  new_chunk = (WebPChunk*)WebPSafeMalloc(1ULL, sizeof(*new_chunk));
+  if (new_chunk == NULL) return WEBP_MUX_MEMORY_ERROR;
+  *new_chunk = *chunk;
+  chunk->owner_ = 0;
+  new_chunk->next_ = *chunk_list;
+  *chunk_list = new_chunk;
+  return WEBP_MUX_OK;
+}
+
+//------------------------------------------------------------------------------
+// Chunk deletion method(s).
+
+WebPChunk* ChunkDelete(WebPChunk* const chunk) {
+  WebPChunk* const next = ChunkRelease(chunk);
+  WebPSafeFree(chunk);
+  return next;
+}
+
+void ChunkListDelete(WebPChunk** const chunk_list) {
+  while (*chunk_list != NULL) {
+    *chunk_list = ChunkDelete(*chunk_list);
+  }
+}
+
+//------------------------------------------------------------------------------
+// Chunk serialization methods.
+
+static uint8_t* ChunkEmit(const WebPChunk* const chunk, uint8_t* dst) {
+  const size_t chunk_size = chunk->data_.size;
+  assert(chunk);
+  assert(chunk->tag_ != NIL_TAG);
+  PutLE32(dst + 0, chunk->tag_);
+  PutLE32(dst + TAG_SIZE, (uint32_t)chunk_size);
+  assert(chunk_size == (uint32_t)chunk_size);
+  memcpy(dst + CHUNK_HEADER_SIZE, chunk->data_.bytes, chunk_size);
+  if (chunk_size & 1)
+    dst[CHUNK_HEADER_SIZE + chunk_size] = 0;  // Add padding.
+  return dst + ChunkDiskSize(chunk);
+}
+
+uint8_t* ChunkListEmit(const WebPChunk* chunk_list, uint8_t* dst) {
+  while (chunk_list != NULL) {
+    dst = ChunkEmit(chunk_list, dst);
+    chunk_list = chunk_list->next_;
+  }
+  return dst;
+}
+
+size_t ChunkListDiskSize(const WebPChunk* chunk_list) {
+  size_t size = 0;
+  while (chunk_list != NULL) {
+    size += ChunkDiskSize(chunk_list);
+    chunk_list = chunk_list->next_;
+  }
+  return size;
+}
+
+//------------------------------------------------------------------------------
+// Life of a MuxImage object.
+
+void MuxImageInit(WebPMuxImage* const wpi) {
+  assert(wpi);
+  memset(wpi, 0, sizeof(*wpi));
+}
+
+WebPMuxImage* MuxImageRelease(WebPMuxImage* const wpi) {
+  WebPMuxImage* next;
+  if (wpi == NULL) return NULL;
+  ChunkDelete(wpi->header_);
+  ChunkDelete(wpi->alpha_);
+  ChunkDelete(wpi->img_);
+  ChunkListDelete(&wpi->unknown_);
+
+  next = wpi->next_;
+  MuxImageInit(wpi);
+  return next;
+}
+
+//------------------------------------------------------------------------------
+// MuxImage search methods.
+
+// Get a reference to appropriate chunk list within an image given chunk tag.
+static WebPChunk** GetChunkListFromId(const WebPMuxImage* const wpi,
+                                      WebPChunkId id) {
+  assert(wpi != NULL);
+  switch (id) {
+    case WEBP_CHUNK_ANMF:  return (WebPChunk**)&wpi->header_;
+    case WEBP_CHUNK_ALPHA: return (WebPChunk**)&wpi->alpha_;
+    case WEBP_CHUNK_IMAGE: return (WebPChunk**)&wpi->img_;
+    default: return NULL;
+  }
+}
+
+int MuxImageCount(const WebPMuxImage* wpi_list, WebPChunkId id) {
+  int count = 0;
+  const WebPMuxImage* current;
+  for (current = wpi_list; current != NULL; current = current->next_) {
+    if (id == WEBP_CHUNK_NIL) {
+      ++count;  // Special case: count all images.
+    } else {
+      const WebPChunk* const wpi_chunk = *GetChunkListFromId(current, id);
+      if (wpi_chunk != NULL) {
+        const WebPChunkId wpi_chunk_id = ChunkGetIdFromTag(wpi_chunk->tag_);
+        if (wpi_chunk_id == id) ++count;  // Count images with a matching 'id'.
+      }
+    }
+  }
+  return count;
+}
+
+// Outputs a pointer to 'prev_wpi->next_',
+//   where 'prev_wpi' is the pointer to the image at position (nth - 1).
+// Returns true if nth image was found.
+static int SearchImageToGetOrDelete(WebPMuxImage** wpi_list, uint32_t nth,
+                                    WebPMuxImage*** const location) {
+  uint32_t count = 0;
+  assert(wpi_list);
+  *location = wpi_list;
+
+  if (nth == 0) {
+    nth = MuxImageCount(*wpi_list, WEBP_CHUNK_NIL);
+    if (nth == 0) return 0;  // Not found.
+  }
+
+  while (*wpi_list != NULL) {
+    WebPMuxImage* const cur_wpi = *wpi_list;
+    ++count;
+    if (count == nth) return 1;  // Found.
+    wpi_list = &cur_wpi->next_;
+    *location = wpi_list;
+  }
+  return 0;  // Not found.
+}
+
+//------------------------------------------------------------------------------
+// MuxImage writer methods.
+
+WebPMuxError MuxImagePush(const WebPMuxImage* wpi, WebPMuxImage** wpi_list) {
+  WebPMuxImage* new_wpi;
+
+  while (*wpi_list != NULL) {
+    WebPMuxImage* const cur_wpi = *wpi_list;
+    if (cur_wpi->next_ == NULL) break;
+    wpi_list = &cur_wpi->next_;
+  }
+
+  new_wpi = (WebPMuxImage*)WebPSafeMalloc(1ULL, sizeof(*new_wpi));
+  if (new_wpi == NULL) return WEBP_MUX_MEMORY_ERROR;
+  *new_wpi = *wpi;
+  new_wpi->next_ = NULL;
+
+  if (*wpi_list != NULL) {
+    (*wpi_list)->next_ = new_wpi;
+  } else {
+    *wpi_list = new_wpi;
+  }
+  return WEBP_MUX_OK;
+}
+
+//------------------------------------------------------------------------------
+// MuxImage deletion methods.
+
+WebPMuxImage* MuxImageDelete(WebPMuxImage* const wpi) {
+  // Delete the components of wpi. If wpi is NULL this is a noop.
+  WebPMuxImage* const next = MuxImageRelease(wpi);
+  WebPSafeFree(wpi);
+  return next;
+}
+
+WebPMuxError MuxImageDeleteNth(WebPMuxImage** wpi_list, uint32_t nth) {
+  assert(wpi_list);
+  if (!SearchImageToGetOrDelete(wpi_list, nth, &wpi_list)) {
+    return WEBP_MUX_NOT_FOUND;
+  }
+  *wpi_list = MuxImageDelete(*wpi_list);
+  return WEBP_MUX_OK;
+}
+
+//------------------------------------------------------------------------------
+// MuxImage reader methods.
+
+WebPMuxError MuxImageGetNth(const WebPMuxImage** wpi_list, uint32_t nth,
+                            WebPMuxImage** wpi) {
+  assert(wpi_list);
+  assert(wpi);
+  if (!SearchImageToGetOrDelete((WebPMuxImage**)wpi_list, nth,
+                                (WebPMuxImage***)&wpi_list)) {
+    return WEBP_MUX_NOT_FOUND;
+  }
+  *wpi = (WebPMuxImage*)*wpi_list;
+  return WEBP_MUX_OK;
+}
+
+//------------------------------------------------------------------------------
+// MuxImage serialization methods.
+
+// Size of an image.
+size_t MuxImageDiskSize(const WebPMuxImage* const wpi) {
+  size_t size = 0;
+  if (wpi->header_ != NULL) size += ChunkDiskSize(wpi->header_);
+  if (wpi->alpha_ != NULL) size += ChunkDiskSize(wpi->alpha_);
+  if (wpi->img_ != NULL) size += ChunkDiskSize(wpi->img_);
+  if (wpi->unknown_ != NULL) size += ChunkListDiskSize(wpi->unknown_);
+  return size;
+}
+
+// Special case as ANMF chunk encapsulates other image chunks.
+static uint8_t* ChunkEmitSpecial(const WebPChunk* const header,
+                                 size_t total_size, uint8_t* dst) {
+  const size_t header_size = header->data_.size;
+  const size_t offset_to_next = total_size - CHUNK_HEADER_SIZE;
+  assert(header->tag_ == kChunks[IDX_ANMF].tag);
+  PutLE32(dst + 0, header->tag_);
+  PutLE32(dst + TAG_SIZE, (uint32_t)offset_to_next);
+  assert(header_size == (uint32_t)header_size);
+  memcpy(dst + CHUNK_HEADER_SIZE, header->data_.bytes, header_size);
+  if (header_size & 1) {
+    dst[CHUNK_HEADER_SIZE + header_size] = 0;  // Add padding.
+  }
+  return dst + ChunkDiskSize(header);
+}
+
+uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst) {
+  // Ordering of chunks to be emitted is strictly as follows:
+  // 1. ANMF chunk (if present).
+  // 2. ALPH chunk (if present).
+  // 3. VP8/VP8L chunk.
+  assert(wpi);
+  if (wpi->header_ != NULL) {
+    dst = ChunkEmitSpecial(wpi->header_, MuxImageDiskSize(wpi), dst);
+  }
+  if (wpi->alpha_ != NULL) dst = ChunkEmit(wpi->alpha_, dst);
+  if (wpi->img_ != NULL) dst = ChunkEmit(wpi->img_, dst);
+  if (wpi->unknown_ != NULL) dst = ChunkListEmit(wpi->unknown_, dst);
+  return dst;
+}
+
+//------------------------------------------------------------------------------
+// Helper methods for mux.
+
+int MuxHasAlpha(const WebPMuxImage* images) {
+  while (images != NULL) {
+    if (images->has_alpha_) return 1;
+    images = images->next_;
+  }
+  return 0;
+}
+
+uint8_t* MuxEmitRiffHeader(uint8_t* const data, size_t size) {
+  PutLE32(data + 0, MKFOURCC('R', 'I', 'F', 'F'));
+  PutLE32(data + TAG_SIZE, (uint32_t)size - CHUNK_HEADER_SIZE);
+  assert(size == (uint32_t)size);
+  PutLE32(data + TAG_SIZE + CHUNK_SIZE_BYTES, MKFOURCC('W', 'E', 'B', 'P'));
+  return data + RIFF_HEADER_SIZE;
+}
+
+WebPChunk** MuxGetChunkListFromId(const WebPMux* mux, WebPChunkId id) {
+  assert(mux != NULL);
+  switch (id) {
+    case WEBP_CHUNK_VP8X:    return (WebPChunk**)&mux->vp8x_;
+    case WEBP_CHUNK_ICCP:    return (WebPChunk**)&mux->iccp_;
+    case WEBP_CHUNK_ANIM:    return (WebPChunk**)&mux->anim_;
+    case WEBP_CHUNK_EXIF:    return (WebPChunk**)&mux->exif_;
+    case WEBP_CHUNK_XMP:     return (WebPChunk**)&mux->xmp_;
+    default:                 return (WebPChunk**)&mux->unknown_;
+  }
+}
+
+static int IsNotCompatible(int feature, int num_items) {
+  return (feature != 0) != (num_items > 0);
+}
+
+#define NO_FLAG ((WebPFeatureFlags)0)
+
+// Test basic constraints:
+// retrieval, maximum number of chunks by index (use -1 to skip)
+// and feature incompatibility (use NO_FLAG to skip).
+// On success returns WEBP_MUX_OK and stores the chunk count in *num.
+static WebPMuxError ValidateChunk(const WebPMux* const mux, CHUNK_INDEX idx,
+                                  WebPFeatureFlags feature,
+                                  uint32_t vp8x_flags,
+                                  int max, int* num) {
+  const WebPMuxError err =
+      WebPMuxNumChunks(mux, kChunks[idx].id, num);
+  if (err != WEBP_MUX_OK) return err;
+  if (max > -1 && *num > max) return WEBP_MUX_INVALID_ARGUMENT;
+  if (feature != NO_FLAG && IsNotCompatible(vp8x_flags & feature, *num)) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+  return WEBP_MUX_OK;
+}
+
+WebPMuxError MuxValidate(const WebPMux* const mux) {
+  int num_iccp;
+  int num_exif;
+  int num_xmp;
+  int num_anim;
+  int num_frames;
+  int num_vp8x;
+  int num_images;
+  int num_alpha;
+  uint32_t flags;
+  WebPMuxError err;
+
+  // Verify mux is not NULL.
+  if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT;
+
+  // Verify mux has at least one image.
+  if (mux->images_ == NULL) return WEBP_MUX_INVALID_ARGUMENT;
+
+  err = WebPMuxGetFeatures(mux, &flags);
+  if (err != WEBP_MUX_OK) return err;
+
+  // At most one color profile chunk.
+  err = ValidateChunk(mux, IDX_ICCP, ICCP_FLAG, flags, 1, &num_iccp);
+  if (err != WEBP_MUX_OK) return err;
+
+  // At most one EXIF metadata.
+  err = ValidateChunk(mux, IDX_EXIF, EXIF_FLAG, flags, 1, &num_exif);
+  if (err != WEBP_MUX_OK) return err;
+
+  // At most one XMP metadata.
+  err = ValidateChunk(mux, IDX_XMP, XMP_FLAG, flags, 1, &num_xmp);
+  if (err != WEBP_MUX_OK) return err;
+
+  // Animation: ANIMATION_FLAG, ANIM chunk and ANMF chunk(s) are consistent.
+  // At most one ANIM chunk.
+  err = ValidateChunk(mux, IDX_ANIM, NO_FLAG, flags, 1, &num_anim);
+  if (err != WEBP_MUX_OK) return err;
+  err = ValidateChunk(mux, IDX_ANMF, NO_FLAG, flags, -1, &num_frames);
+  if (err != WEBP_MUX_OK) return err;
+
+  {
+    const int has_animation = !!(flags & ANIMATION_FLAG);
+    if (has_animation && (num_anim == 0 || num_frames == 0)) {
+      return WEBP_MUX_INVALID_ARGUMENT;
+    }
+    if (!has_animation && (num_anim == 1 || num_frames > 0)) {
+      return WEBP_MUX_INVALID_ARGUMENT;
+    }
+  }
+
+  // Verify either VP8X chunk is present OR there is only one elem in
+  // mux->images_.
+  err = ValidateChunk(mux, IDX_VP8X, NO_FLAG, flags, 1, &num_vp8x);
+  if (err != WEBP_MUX_OK) return err;
+  err = ValidateChunk(mux, IDX_VP8, NO_FLAG, flags, -1, &num_images);
+  if (err != WEBP_MUX_OK) return err;
+  if (num_vp8x == 0 && num_images != 1) return WEBP_MUX_INVALID_ARGUMENT;
+
+  // ALPHA_FLAG & alpha chunk(s) are consistent.
+  if (MuxHasAlpha(mux->images_)) {
+    if (num_vp8x > 0) {
+      // VP8X chunk is present, so it should contain ALPHA_FLAG.
+      if (!(flags & ALPHA_FLAG)) return WEBP_MUX_INVALID_ARGUMENT;
+    } else {
+      // VP8X chunk is not present, so ALPH chunks should NOT be present either.
+      err = WebPMuxNumChunks(mux, WEBP_CHUNK_ALPHA, &num_alpha);
+      if (err != WEBP_MUX_OK) return err;
+      if (num_alpha > 0) return WEBP_MUX_INVALID_ARGUMENT;
+    }
+  } else {  // Mux doesn't need alpha. So, ALPHA_FLAG should NOT be present.
+    if (flags & ALPHA_FLAG) return WEBP_MUX_INVALID_ARGUMENT;
+  }
+
+  return WEBP_MUX_OK;
+}
+
+#undef NO_FLAG
+
+//------------------------------------------------------------------------------
+
diff --git a/third_party/libwebp/mux/muxread.c b/third_party/libwebp/mux/muxread.c
new file mode 100644
index 0000000..410acd9
--- /dev/null
+++ b/third_party/libwebp/mux/muxread.c
@@ -0,0 +1,536 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Read APIs for mux.
+//
+// Authors: Urvang (urvang@google.com)
+//          Vikas (vikasa@google.com)
+
+#include <assert.h>
+#include "./muxi.h"
+#include "../utils/utils.h"
+
+//------------------------------------------------------------------------------
+// Helper method(s).
+
+// Handy MACRO.
+#define SWITCH_ID_LIST(INDEX, LIST)                                           \
+  if (idx == (INDEX)) {                                                       \
+    const WebPChunk* const chunk = ChunkSearchList((LIST), nth,               \
+                                                   kChunks[(INDEX)].tag);     \
+    if (chunk) {                                                              \
+      *data = chunk->data_;                                                   \
+      return WEBP_MUX_OK;                                                     \
+    } else {                                                                  \
+      return WEBP_MUX_NOT_FOUND;                                              \
+    }                                                                         \
+  }
+
+static WebPMuxError MuxGet(const WebPMux* const mux, CHUNK_INDEX idx,
+                           uint32_t nth, WebPData* const data) {
+  assert(mux != NULL);
+  assert(!IsWPI(kChunks[idx].id));
+  WebPDataInit(data);
+
+  SWITCH_ID_LIST(IDX_VP8X, mux->vp8x_);
+  SWITCH_ID_LIST(IDX_ICCP, mux->iccp_);
+  SWITCH_ID_LIST(IDX_ANIM, mux->anim_);
+  SWITCH_ID_LIST(IDX_EXIF, mux->exif_);
+  SWITCH_ID_LIST(IDX_XMP, mux->xmp_);
+  SWITCH_ID_LIST(IDX_UNKNOWN, mux->unknown_);
+  return WEBP_MUX_NOT_FOUND;
+}
+#undef SWITCH_ID_LIST
+
+// Fill the chunk with the given data (includes chunk header bytes), after some
+// verifications.
+static WebPMuxError ChunkVerifyAndAssign(WebPChunk* chunk,
+                                         const uint8_t* data, size_t data_size,
+                                         size_t riff_size, int copy_data) {
+  uint32_t chunk_size;
+  WebPData chunk_data;
+
+  // Sanity checks.
+  if (data_size < CHUNK_HEADER_SIZE) return WEBP_MUX_NOT_ENOUGH_DATA;
+  chunk_size = GetLE32(data + TAG_SIZE);
+
+  {
+    const size_t chunk_disk_size = SizeWithPadding(chunk_size);
+    if (chunk_disk_size > riff_size) return WEBP_MUX_BAD_DATA;
+    if (chunk_disk_size > data_size) return WEBP_MUX_NOT_ENOUGH_DATA;
+  }
+
+  // Data assignment.
+  chunk_data.bytes = data + CHUNK_HEADER_SIZE;
+  chunk_data.size = chunk_size;
+  return ChunkAssignData(chunk, &chunk_data, copy_data, GetLE32(data + 0));
+}
+
+int MuxImageFinalize(WebPMuxImage* const wpi) {
+  const WebPChunk* const img = wpi->img_;
+  const WebPData* const image = &img->data_;
+  const int is_lossless = (img->tag_ == kChunks[IDX_VP8L].tag);
+  int w, h;
+  int vp8l_has_alpha = 0;
+  const int ok = is_lossless ?
+      VP8LGetInfo(image->bytes, image->size, &w, &h, &vp8l_has_alpha) :
+      VP8GetInfo(image->bytes, image->size, image->size, &w, &h);
+  assert(img != NULL);
+  if (ok) {
+    // Ignore ALPH chunk accompanying VP8L.
+    if (is_lossless && (wpi->alpha_ != NULL)) {
+      ChunkDelete(wpi->alpha_);
+      wpi->alpha_ = NULL;
+    }
+    wpi->width_ = w;
+    wpi->height_ = h;
+    wpi->has_alpha_ = vp8l_has_alpha || (wpi->alpha_ != NULL);
+  }
+  return ok;
+}
+
+static int MuxImageParse(const WebPChunk* const chunk, int copy_data,
+                         WebPMuxImage* const wpi) {
+  const uint8_t* bytes = chunk->data_.bytes;
+  size_t size = chunk->data_.size;
+  const uint8_t* const last = bytes + size;
+  WebPChunk subchunk;
+  size_t subchunk_size;
+  ChunkInit(&subchunk);
+
+  assert(chunk->tag_ == kChunks[IDX_ANMF].tag);
+  assert(!wpi->is_partial_);
+
+  // ANMF.
+  {
+    const size_t hdr_size = ANMF_CHUNK_SIZE;
+    const WebPData temp = { bytes, hdr_size };
+    // Each of ANMF chunk contain a header at the beginning. So, its size should
+    // be at least 'hdr_size'.
+    if (size < hdr_size) goto Fail;
+    ChunkAssignData(&subchunk, &temp, copy_data, chunk->tag_);
+  }
+  ChunkSetNth(&subchunk, &wpi->header_, 1);
+  wpi->is_partial_ = 1;  // Waiting for ALPH and/or VP8/VP8L chunks.
+
+  // Rest of the chunks.
+  subchunk_size = ChunkDiskSize(&subchunk) - CHUNK_HEADER_SIZE;
+  bytes += subchunk_size;
+  size -= subchunk_size;
+
+  while (bytes != last) {
+    ChunkInit(&subchunk);
+    if (ChunkVerifyAndAssign(&subchunk, bytes, size, size,
+                             copy_data) != WEBP_MUX_OK) {
+      goto Fail;
+    }
+    switch (ChunkGetIdFromTag(subchunk.tag_)) {
+      case WEBP_CHUNK_ALPHA:
+        if (wpi->alpha_ != NULL) goto Fail;  // Consecutive ALPH chunks.
+        if (ChunkSetNth(&subchunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Fail;
+        wpi->is_partial_ = 1;  // Waiting for a VP8 chunk.
+        break;
+      case WEBP_CHUNK_IMAGE:
+        if (ChunkSetNth(&subchunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Fail;
+        if (!MuxImageFinalize(wpi)) goto Fail;
+        wpi->is_partial_ = 0;  // wpi is completely filled.
+        break;
+      case WEBP_CHUNK_UNKNOWN:
+        if (wpi->is_partial_) goto Fail;  // Encountered an unknown chunk
+                                          // before some image chunks.
+        if (ChunkSetNth(&subchunk, &wpi->unknown_, 0) != WEBP_MUX_OK) goto Fail;
+        break;
+      default:
+        goto Fail;
+        break;
+    }
+    subchunk_size = ChunkDiskSize(&subchunk);
+    bytes += subchunk_size;
+    size -= subchunk_size;
+  }
+  if (wpi->is_partial_) goto Fail;
+  return 1;
+
+ Fail:
+  ChunkRelease(&subchunk);
+  return 0;
+}
+
+//------------------------------------------------------------------------------
+// Create a mux object from WebP-RIFF data.
+
+WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data,
+                               int version) {
+  size_t riff_size;
+  uint32_t tag;
+  const uint8_t* end;
+  WebPMux* mux = NULL;
+  WebPMuxImage* wpi = NULL;
+  const uint8_t* data;
+  size_t size;
+  WebPChunk chunk;
+  ChunkInit(&chunk);
+
+  // Sanity checks.
+  if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_MUX_ABI_VERSION)) {
+    return NULL;  // version mismatch
+  }
+  if (bitstream == NULL) return NULL;
+
+  data = bitstream->bytes;
+  size = bitstream->size;
+
+  if (data == NULL) return NULL;
+  if (size < RIFF_HEADER_SIZE) return NULL;
+  if (GetLE32(data + 0) != MKFOURCC('R', 'I', 'F', 'F') ||
+      GetLE32(data + CHUNK_HEADER_SIZE) != MKFOURCC('W', 'E', 'B', 'P')) {
+    return NULL;
+  }
+
+  mux = WebPMuxNew();
+  if (mux == NULL) return NULL;
+
+  if (size < RIFF_HEADER_SIZE + TAG_SIZE) goto Err;
+
+  tag = GetLE32(data + RIFF_HEADER_SIZE);
+  if (tag != kChunks[IDX_VP8].tag &&
+      tag != kChunks[IDX_VP8L].tag &&
+      tag != kChunks[IDX_VP8X].tag) {
+    goto Err;  // First chunk should be VP8, VP8L or VP8X.
+  }
+
+  riff_size = SizeWithPadding(GetLE32(data + TAG_SIZE));
+  if (riff_size > MAX_CHUNK_PAYLOAD || riff_size > size) {
+    goto Err;
+  } else {
+    if (riff_size < size) {  // Redundant data after last chunk.
+      size = riff_size;  // To make sure we don't read any data beyond mux_size.
+    }
+  }
+
+  end = data + size;
+  data += RIFF_HEADER_SIZE;
+  size -= RIFF_HEADER_SIZE;
+
+  wpi = (WebPMuxImage*)WebPSafeMalloc(1ULL, sizeof(*wpi));
+  if (wpi == NULL) goto Err;
+  MuxImageInit(wpi);
+
+  // Loop over chunks.
+  while (data != end) {
+    size_t data_size;
+    WebPChunkId id;
+    WebPChunk** chunk_list;
+    if (ChunkVerifyAndAssign(&chunk, data, size, riff_size,
+                             copy_data) != WEBP_MUX_OK) {
+      goto Err;
+    }
+    data_size = ChunkDiskSize(&chunk);
+    id = ChunkGetIdFromTag(chunk.tag_);
+    switch (id) {
+      case WEBP_CHUNK_ALPHA:
+        if (wpi->alpha_ != NULL) goto Err;  // Consecutive ALPH chunks.
+        if (ChunkSetNth(&chunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Err;
+        wpi->is_partial_ = 1;  // Waiting for a VP8 chunk.
+        break;
+      case WEBP_CHUNK_IMAGE:
+        if (ChunkSetNth(&chunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Err;
+        if (!MuxImageFinalize(wpi)) goto Err;
+        wpi->is_partial_ = 0;  // wpi is completely filled.
+ PushImage:
+        // Add this to mux->images_ list.
+        if (MuxImagePush(wpi, &mux->images_) != WEBP_MUX_OK) goto Err;
+        MuxImageInit(wpi);  // Reset for reading next image.
+        break;
+      case WEBP_CHUNK_ANMF:
+        if (wpi->is_partial_) goto Err;  // Previous wpi is still incomplete.
+        if (!MuxImageParse(&chunk, copy_data, wpi)) goto Err;
+        ChunkRelease(&chunk);
+        goto PushImage;
+        break;
+      default:  // A non-image chunk.
+        if (wpi->is_partial_) goto Err;  // Encountered a non-image chunk before
+                                         // getting all chunks of an image.
+        chunk_list = MuxGetChunkListFromId(mux, id);  // List to add this chunk.
+        if (ChunkSetNth(&chunk, chunk_list, 0) != WEBP_MUX_OK) goto Err;
+        if (id == WEBP_CHUNK_VP8X) {  // grab global specs
+          mux->canvas_width_ = GetLE24(data + 12) + 1;
+          mux->canvas_height_ = GetLE24(data + 15) + 1;
+        }
+        break;
+    }
+    data += data_size;
+    size -= data_size;
+    ChunkInit(&chunk);
+  }
+
+  // Validate mux if complete.
+  if (MuxValidate(mux) != WEBP_MUX_OK) goto Err;
+
+  MuxImageDelete(wpi);
+  return mux;  // All OK;
+
+ Err:  // Something bad happened.
+  ChunkRelease(&chunk);
+  MuxImageDelete(wpi);
+  WebPMuxDelete(mux);
+  return NULL;
+}
+
+//------------------------------------------------------------------------------
+// Get API(s).
+
+// Validates that the given mux has a single image.
+static WebPMuxError ValidateForSingleImage(const WebPMux* const mux) {
+  const int num_images = MuxImageCount(mux->images_, WEBP_CHUNK_IMAGE);
+  const int num_frames = MuxImageCount(mux->images_, WEBP_CHUNK_ANMF);
+
+  if (num_images == 0) {
+    // No images in mux.
+    return WEBP_MUX_NOT_FOUND;
+  } else if (num_images == 1 && num_frames == 0) {
+    // Valid case (single image).
+    return WEBP_MUX_OK;
+  } else {
+    // Frame case OR an invalid mux.
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+}
+
+// Get the canvas width, height and flags after validating that VP8X/VP8/VP8L
+// chunk and canvas size are valid.
+static WebPMuxError MuxGetCanvasInfo(const WebPMux* const mux,
+                                     int* width, int* height, uint32_t* flags) {
+  int w, h;
+  uint32_t f = 0;
+  WebPData data;
+  assert(mux != NULL);
+
+  // Check if VP8X chunk is present.
+  if (MuxGet(mux, IDX_VP8X, 1, &data) == WEBP_MUX_OK) {
+    if (data.size < VP8X_CHUNK_SIZE) return WEBP_MUX_BAD_DATA;
+    f = GetLE32(data.bytes + 0);
+    w = GetLE24(data.bytes + 4) + 1;
+    h = GetLE24(data.bytes + 7) + 1;
+  } else {
+    const WebPMuxImage* const wpi = mux->images_;
+    // Grab user-forced canvas size as default.
+    w = mux->canvas_width_;
+    h = mux->canvas_height_;
+    if (w == 0 && h == 0 && ValidateForSingleImage(mux) == WEBP_MUX_OK) {
+      // single image and not forced canvas size => use dimension of first frame
+      assert(wpi != NULL);
+      w = wpi->width_;
+      h = wpi->height_;
+    }
+    if (wpi != NULL) {
+      if (wpi->has_alpha_) f |= ALPHA_FLAG;
+    }
+  }
+  if (w * (uint64_t)h >= MAX_IMAGE_AREA) return WEBP_MUX_BAD_DATA;
+
+  if (width != NULL) *width = w;
+  if (height != NULL) *height = h;
+  if (flags != NULL) *flags = f;
+  return WEBP_MUX_OK;
+}
+
+WebPMuxError WebPMuxGetCanvasSize(const WebPMux* mux, int* width, int* height) {
+  if (mux == NULL || width == NULL || height == NULL) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+  return MuxGetCanvasInfo(mux, width, height, NULL);
+}
+
+WebPMuxError WebPMuxGetFeatures(const WebPMux* mux, uint32_t* flags) {
+  if (mux == NULL || flags == NULL) return WEBP_MUX_INVALID_ARGUMENT;
+  return MuxGetCanvasInfo(mux, NULL, NULL, flags);
+}
+
+static uint8_t* EmitVP8XChunk(uint8_t* const dst, int width,
+                              int height, uint32_t flags) {
+  const size_t vp8x_size = CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE;
+  assert(width >= 1 && height >= 1);
+  assert(width <= MAX_CANVAS_SIZE && height <= MAX_CANVAS_SIZE);
+  assert(width * (uint64_t)height < MAX_IMAGE_AREA);
+  PutLE32(dst, MKFOURCC('V', 'P', '8', 'X'));
+  PutLE32(dst + TAG_SIZE, VP8X_CHUNK_SIZE);
+  PutLE32(dst + CHUNK_HEADER_SIZE, flags);
+  PutLE24(dst + CHUNK_HEADER_SIZE + 4, width - 1);
+  PutLE24(dst + CHUNK_HEADER_SIZE + 7, height - 1);
+  return dst + vp8x_size;
+}
+
+// Assemble a single image WebP bitstream from 'wpi'.
+static WebPMuxError SynthesizeBitstream(const WebPMuxImage* const wpi,
+                                        WebPData* const bitstream) {
+  uint8_t* dst;
+
+  // Allocate data.
+  const int need_vp8x = (wpi->alpha_ != NULL);
+  const size_t vp8x_size = need_vp8x ? CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE : 0;
+  const size_t alpha_size = need_vp8x ? ChunkDiskSize(wpi->alpha_) : 0;
+  // Note: No need to output ANMF chunk for a single image.
+  const size_t size = RIFF_HEADER_SIZE + vp8x_size + alpha_size +
+                      ChunkDiskSize(wpi->img_);
+  uint8_t* const data = (uint8_t*)WebPSafeMalloc(1ULL, size);
+  if (data == NULL) return WEBP_MUX_MEMORY_ERROR;
+
+  // Main RIFF header.
+  dst = MuxEmitRiffHeader(data, size);
+
+  if (need_vp8x) {
+    dst = EmitVP8XChunk(dst, wpi->width_, wpi->height_, ALPHA_FLAG);  // VP8X.
+    dst = ChunkListEmit(wpi->alpha_, dst);       // ALPH.
+  }
+
+  // Bitstream.
+  dst = ChunkListEmit(wpi->img_, dst);
+  assert(dst == data + size);
+
+  // Output.
+  bitstream->bytes = data;
+  bitstream->size = size;
+  return WEBP_MUX_OK;
+}
+
+WebPMuxError WebPMuxGetChunk(const WebPMux* mux, const char fourcc[4],
+                             WebPData* chunk_data) {
+  CHUNK_INDEX idx;
+  if (mux == NULL || fourcc == NULL || chunk_data == NULL) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+  idx = ChunkGetIndexFromFourCC(fourcc);
+  if (IsWPI(kChunks[idx].id)) {     // An image chunk.
+    return WEBP_MUX_INVALID_ARGUMENT;
+  } else if (idx != IDX_UNKNOWN) {  // A known chunk type.
+    return MuxGet(mux, idx, 1, chunk_data);
+  } else {                          // An unknown chunk type.
+    const WebPChunk* const chunk =
+        ChunkSearchList(mux->unknown_, 1, ChunkGetTagFromFourCC(fourcc));
+    if (chunk == NULL) return WEBP_MUX_NOT_FOUND;
+    *chunk_data = chunk->data_;
+    return WEBP_MUX_OK;
+  }
+}
+
+static WebPMuxError MuxGetImageInternal(const WebPMuxImage* const wpi,
+                                        WebPMuxFrameInfo* const info) {
+  // Set some defaults for unrelated fields.
+  info->x_offset = 0;
+  info->y_offset = 0;
+  info->duration = 1;
+  info->dispose_method = WEBP_MUX_DISPOSE_NONE;
+  info->blend_method = WEBP_MUX_BLEND;
+  // Extract data for related fields.
+  info->id = ChunkGetIdFromTag(wpi->img_->tag_);
+  return SynthesizeBitstream(wpi, &info->bitstream);
+}
+
+static WebPMuxError MuxGetFrameInternal(const WebPMuxImage* const wpi,
+                                        WebPMuxFrameInfo* const frame) {
+  const int is_frame = (wpi->header_->tag_ == kChunks[IDX_ANMF].tag);
+  const WebPData* frame_data;
+  if (!is_frame) return WEBP_MUX_INVALID_ARGUMENT;
+  assert(wpi->header_ != NULL);  // Already checked by WebPMuxGetFrame().
+  // Get frame chunk.
+  frame_data = &wpi->header_->data_;
+  if (frame_data->size < kChunks[IDX_ANMF].size) return WEBP_MUX_BAD_DATA;
+  // Extract info.
+  frame->x_offset = 2 * GetLE24(frame_data->bytes + 0);
+  frame->y_offset = 2 * GetLE24(frame_data->bytes + 3);
+  {
+    const uint8_t bits = frame_data->bytes[15];
+    frame->duration = GetLE24(frame_data->bytes + 12);
+    frame->dispose_method =
+        (bits & 1) ? WEBP_MUX_DISPOSE_BACKGROUND : WEBP_MUX_DISPOSE_NONE;
+    frame->blend_method = (bits & 2) ? WEBP_MUX_NO_BLEND : WEBP_MUX_BLEND;
+  }
+  frame->id = ChunkGetIdFromTag(wpi->header_->tag_);
+  return SynthesizeBitstream(wpi, &frame->bitstream);
+}
+
+WebPMuxError WebPMuxGetFrame(
+    const WebPMux* mux, uint32_t nth, WebPMuxFrameInfo* frame) {
+  WebPMuxError err;
+  WebPMuxImage* wpi;
+
+  // Sanity checks.
+  if (mux == NULL || frame == NULL) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+
+  // Get the nth WebPMuxImage.
+  err = MuxImageGetNth((const WebPMuxImage**)&mux->images_, nth, &wpi);
+  if (err != WEBP_MUX_OK) return err;
+
+  // Get frame info.
+  if (wpi->header_ == NULL) {
+    return MuxGetImageInternal(wpi, frame);
+  } else {
+    return MuxGetFrameInternal(wpi, frame);
+  }
+}
+
+WebPMuxError WebPMuxGetAnimationParams(const WebPMux* mux,
+                                       WebPMuxAnimParams* params) {
+  WebPData anim;
+  WebPMuxError err;
+
+  if (mux == NULL || params == NULL) return WEBP_MUX_INVALID_ARGUMENT;
+
+  err = MuxGet(mux, IDX_ANIM, 1, &anim);
+  if (err != WEBP_MUX_OK) return err;
+  if (anim.size < kChunks[WEBP_CHUNK_ANIM].size) return WEBP_MUX_BAD_DATA;
+  params->bgcolor = GetLE32(anim.bytes);
+  params->loop_count = GetLE16(anim.bytes + 4);
+
+  return WEBP_MUX_OK;
+}
+
+// Get chunk index from chunk id. Returns IDX_NIL if not found.
+static CHUNK_INDEX ChunkGetIndexFromId(WebPChunkId id) {
+  int i;
+  for (i = 0; kChunks[i].id != WEBP_CHUNK_NIL; ++i) {
+    if (id == kChunks[i].id) return (CHUNK_INDEX)i;
+  }
+  return IDX_NIL;
+}
+
+// Count number of chunks matching 'tag' in the 'chunk_list'.
+// If tag == NIL_TAG, any tag will be matched.
+static int CountChunks(const WebPChunk* const chunk_list, uint32_t tag) {
+  int count = 0;
+  const WebPChunk* current;
+  for (current = chunk_list; current != NULL; current = current->next_) {
+    if (tag == NIL_TAG || current->tag_ == tag) {
+      count++;  // Count chunks whose tags match.
+    }
+  }
+  return count;
+}
+
+WebPMuxError WebPMuxNumChunks(const WebPMux* mux,
+                              WebPChunkId id, int* num_elements) {
+  if (mux == NULL || num_elements == NULL) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+
+  if (IsWPI(id)) {
+    *num_elements = MuxImageCount(mux->images_, id);
+  } else {
+    WebPChunk* const* chunk_list = MuxGetChunkListFromId(mux, id);
+    const CHUNK_INDEX idx = ChunkGetIndexFromId(id);
+    *num_elements = CountChunks(*chunk_list, kChunks[idx].tag);
+  }
+
+  return WEBP_MUX_OK;
+}
+
+//------------------------------------------------------------------------------
diff --git a/third_party/libxml/BUILD.gn b/third_party/libxml/BUILD.gn
index 28ac4d0..430a46e3 100644
--- a/third_party/libxml/BUILD.gn
+++ b/third_party/libxml/BUILD.gn
@@ -4,7 +4,7 @@
 
 # Define an "os_include" variable that points at the OS-specific generated
 # headers.  These were generated by running the configure script offline.
-if (is_linux || is_android || is_nacl) {
+if (is_linux || is_android || is_nacl || is_fuchsia) {
   os_include = "linux"
 } else if (is_mac || is_ios) {
   os_include = "mac"
@@ -71,6 +71,7 @@
 
 static_library("libxml") {
   output_name = "libxml2"
+
   # Commented out sources are libxml2 files we do not want to include. They are
   # here to make it easy to identify files which are new.
   sources = [
@@ -80,16 +81,20 @@
     "linux/include/libxml/xmlversion.h",
     "mac/config.h",
     "mac/include/libxml/xmlversion.h",
+
     #"src/DOCBparser.c",
     "src/HTMLparser.c",
     "src/HTMLtree.c",
+
     #"src/SAX.c",
     "src/SAX2.c",
     "src/buf.c",
     "src/buf.h",
+
     #"src/c14n.c",
     #"src/catalog.c",
     "src/chvalid.c",
+
     #"src/debugXML.c",
     "src/dict.c",
     "src/elfgcchack.h",
@@ -147,20 +152,24 @@
     "src/include/libxml/xpointer.h",
     "src/include/win32config.h",
     "src/include/wsockcompat.h",
+
     #"src/legacy.c",
     "src/libxml.h",
     "src/list.c",
     "src/parser.c",
     "src/parserInternals.c",
     "src/pattern.c",
+
     #"src/relaxng.c",
     "src/save.h",
+
     #"src/schematron.c",
     "src/threads.c",
     "src/timsort.h",
     "src/tree.c",
     "src/triodef.h",
     "src/trionan.h",
+
     #"src/trio.c",
     #"src/trio.h",
     #"src/triodef.h",
@@ -171,20 +180,25 @@
     #"src/triostr.h",
     "src/uri.c",
     "src/valid.c",
+
     #"src/xinclude.c",
     #"src/xlink.c",
     "src/xmlIO.c",
     "src/xmlmemory.c",
+
     #"src/xmlmodule.c",
     "src/xmlreader.c",
+
     #"src/xmlregexp.c",
     "src/xmlsave.c",
+
     #"src/xmlschemas.c",
     #"src/xmlschemastypes.c",
     "src/xmlstring.c",
     "src/xmlunicode.c",
     "src/xmlwriter.c",
     "src/xpath.c",
+
     #"src/xpointer.c",
     #"src/xzlib.c",
     "src/xzlib.h",
diff --git a/tools/perf/core/perf_data_generator.py b/tools/perf/core/perf_data_generator.py
index f5ec294..48f51c6 100755
--- a/tools/perf/core/perf_data_generator.py
+++ b/tools/perf/core/perf_data_generator.py
@@ -263,7 +263,7 @@
        'perf_tests': [
          ('tracing_perftests', 'build73-b1--device2'),
          ('gpu_perftests', 'build73-b1--device2'),
-         ('cc_perftests', 'build73-b1--device2'),
+         #  ('cc_perftests', 'build73-b1--device2'),  # crbug.com/721757
          ]
       }
     ])
diff --git a/ui/app_list/presenter/BUILD.gn b/ui/app_list/presenter/BUILD.gn
index 3af3c3fa..13b211b 100644
--- a/ui/app_list/presenter/BUILD.gn
+++ b/ui/app_list/presenter/BUILD.gn
@@ -16,6 +16,7 @@
     "//services/ui/public/interfaces",
   ]
 
+  component_output_prefix = "app_list_presenter_mojom"
   export_class_attribute = "APP_LIST_PRESENTER_EXPORT"
   export_define = "APP_LIST_PRESENTER_IMPLEMENTATION=1"
   export_header = "ui/app_list/presenter/app_list_presenter_export.h"
diff --git a/ui/gfx/codec/BUILD.gn b/ui/gfx/codec/BUILD.gn
index 6cdc8a7c..03a5bd7 100644
--- a/ui/gfx/codec/BUILD.gn
+++ b/ui/gfx/codec/BUILD.gn
@@ -11,8 +11,6 @@
     "jpeg_codec.h",
     "png_codec.cc",
     "png_codec.h",
-    "skia_image_encoder_adapter.cc",
-    "skia_image_encoder_adapter.h",
   ]
 
   deps = [
diff --git a/ui/gfx/codec/skia_image_encoder_adapter.cc b/ui/gfx/codec/skia_image_encoder_adapter.cc
deleted file mode 100644
index 6dbb457a..0000000
--- a/ui/gfx/codec/skia_image_encoder_adapter.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (c) 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 "ui/gfx/codec/skia_image_encoder_adapter.h"
-
-#include "third_party/skia/include/core/SkBitmap.h"
-#include "third_party/skia/include/core/SkStream.h"
-#include "ui/gfx/codec/jpeg_codec.h"
-#include "ui/gfx/codec/png_codec.h"
-#include "ui/gfx/geometry/size.h"
-
-bool gfx::EncodeSkiaImage(SkWStream* dst,
-                          const SkPixmap& pixmap,
-                          SkEncodedImageFormat format,
-                          int quality) {
-  if (kN32_SkColorType != pixmap.colorType() ||
-      (kPremul_SkAlphaType != pixmap.alphaType() &&
-       kOpaque_SkAlphaType != pixmap.alphaType())) {
-    return false;
-  }
-  std::vector<unsigned char> buffer;
-  switch (format) {
-    case SkEncodedImageFormat::kPNG:
-      return gfx::PNGCodec::Encode(
-                 reinterpret_cast<const unsigned char*>(pixmap.addr()),
-                 gfx::PNGCodec::FORMAT_SkBitmap,
-                 gfx::Size(pixmap.width(), pixmap.height()),
-                 static_cast<int>(pixmap.rowBytes()), false,
-                 std::vector<gfx::PNGCodec::Comment>(), &buffer) &&
-             dst->write(buffer.data(), buffer.size());
-    case SkEncodedImageFormat::kJPEG:
-      return gfx::JPEGCodec::Encode(
-                 reinterpret_cast<const unsigned char*>(pixmap.addr()),
-                 gfx::JPEGCodec::FORMAT_SkBitmap, pixmap.width(),
-                 pixmap.height(), static_cast<int>(pixmap.rowBytes()), quality,
-                 &buffer) &&
-             dst->write(buffer.data(), buffer.size());
-    default:
-      return false;
-  }
-}
diff --git a/ui/gfx/codec/skia_image_encoder_adapter.h b/ui/gfx/codec/skia_image_encoder_adapter.h
deleted file mode 100644
index f231a494..0000000
--- a/ui/gfx/codec/skia_image_encoder_adapter.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 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 UI_GFX_CODEC_SKIA_IMAGE_ENCODER_ADAPTER_H
-#define UI_GFX_CODEC_SKIA_IMAGE_ENCODER_ADAPTER_H
-
-#include "third_party/skia/include/core/SkEncodedImageFormat.h"
-#include "ui/gfx/codec/codec_export.h"
-
-class SkWStream;
-class SkPixmap;
-
-namespace gfx {
-
-// Matches signature of Skia's SkEncodeImage, but makes use of Chromium's
-// encoders.
-CODEC_EXPORT bool EncodeSkiaImage(SkWStream* dst,
-                                  const SkPixmap& pixmap,
-                                  SkEncodedImageFormat format,
-                                  int quality);
-
-}  // namespace gfx
-
-#endif  // UI_GFX_CODEC_SKIA_IMAGE_ENCODER_ADAPTER_H
diff --git a/ui/gfx/ipc/BUILD.gn b/ui/gfx/ipc/BUILD.gn
index 2466679..170b9fc 100644
--- a/ui/gfx/ipc/BUILD.gn
+++ b/ui/gfx/ipc/BUILD.gn
@@ -1,3 +1,5 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
 component("ipc") {
diff --git a/ui/views/test/widget_test.cc b/ui/views/test/widget_test.cc
index 457a63d..a96a9db 100644
--- a/ui/views/test/widget_test.cc
+++ b/ui/views/test/widget_test.cc
@@ -68,7 +68,7 @@
 }
 
 Widget* WidgetTest::CreateChildNativeWidget() {
-  return CreateChildNativeWidgetWithParent(NULL);
+  return CreateChildNativeWidgetWithParent(nullptr);
 }
 
 Widget* WidgetTest::CreateNativeDesktopWidget() {
@@ -177,5 +177,20 @@
     run_loop_.Quit();
 }
 
+WidgetClosingObserver::WidgetClosingObserver(Widget* widget) : widget_(widget) {
+  widget_->AddObserver(this);
+}
+
+WidgetClosingObserver::~WidgetClosingObserver() {
+  if (widget_)
+    widget_->RemoveObserver(this);
+}
+
+void WidgetClosingObserver::OnWidgetClosing(Widget* widget) {
+  DCHECK_EQ(widget_, widget);
+  widget_->RemoveObserver(this);
+  widget_ = nullptr;
+}
+
 }  // namespace test
 }  // namespace views
diff --git a/ui/views/test/widget_test.h b/ui/views/test/widget_test.h
index 62be1b7e..a796a26f3 100644
--- a/ui/views/test/widget_test.h
+++ b/ui/views/test/widget_test.h
@@ -177,6 +177,24 @@
   DISALLOW_COPY_AND_ASSIGN(WidgetActivationWaiter);
 };
 
+// Use in tests to provide functionality to observe the widget passed in the
+// constructor for the widget closing event.
+class WidgetClosingObserver : public WidgetObserver {
+ public:
+  explicit WidgetClosingObserver(Widget* widget);
+  ~WidgetClosingObserver() override;
+
+  bool widget_closed() const { return !widget_; }
+
+ private:
+  // views::WidgetObserver override:
+  void OnWidgetClosing(Widget* widget) override;
+
+  Widget* widget_;
+
+  DISALLOW_COPY_AND_ASSIGN(WidgetClosingObserver);
+};
+
 }  // namespace test
 }  // namespace views