diff --git a/AUTHORS b/AUTHORS
index d0e12c4..8ed2917 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -915,6 +915,7 @@
 Collabora Limited <*@collabora.com>
 Comodo CA Limited
 Endless Mobile, Inc. <*@endlessm.com>
+Estimote, Inc. <*@estimote.com>
 Facebook, Inc. <*@fb.com>
 Facebook, Inc. <*@oculus.com>
 Google Inc. <*@google.com>
diff --git a/DEPS b/DEPS
index 7f82f3b5..42cf6e7 100644
--- a/DEPS
+++ b/DEPS
@@ -86,7 +86,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '5b1180df6835e403e5a9636fe9f7c987e1e48e2c',
+  'angle_revision': 'f2afccca7b27bf2a2415cbf066771295106500c5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling build tools
   # and whatever else without interference from each other.
@@ -98,7 +98,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': 'd01f2429bc561eed07293b46e08a07c54e471521',
+  'pdfium_revision': 'd84b42c71a8122b7cd144474fd6d6aaa787f54e1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling openmax_dl
   # and whatever else without interference from each other.
@@ -323,7 +323,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'cfb9a236fb19cacf6cab23aa317f2628e3d0fdfb',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '9fce213bdbb8512c571c6e14b0302dbb5ecd653e',
 
   # DevTools node modules. Used on Linux buildbots only.
   'src/third_party/devtools-node-modules': {
@@ -639,7 +639,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '1a8ed15c9c25f2b1ccd97149c94ea245ab982252',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '28a06b16cc4daa9f380ad45af8acfd11b6057283', # commit position 20628
+    Var('webrtc_git') + '/src.git' + '@' + '67c20ae57100e5878e9734b1c8c53ebd5b5d7bbf', # commit position 20628
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
diff --git a/android_webview/common/crash_reporter/crash_keys.cc b/android_webview/common/crash_reporter/crash_keys.cc
index e4887a86..28f9232 100644
--- a/android_webview/common/crash_reporter/crash_keys.cc
+++ b/android_webview/common/crash_reporter/crash_keys.cc
@@ -29,8 +29,6 @@
 
 size_t RegisterWebViewCrashKeys() {
   base::debug::CrashKey fixed_keys[] = {
-      {kClientId, kSmallSize},
-      {kChannel, kSmallSize},
       {kActiveURL, kLargeSize},
       {kNumVariations, kSmallSize},
       {kVariations, kHugeSize},
@@ -51,8 +49,6 @@
   std::vector<base::debug::CrashKey> keys(fixed_keys,
                                           fixed_keys + arraysize(fixed_keys));
 
-  GetCrashKeysForCommandLineSwitches(&keys);
-
   return base::debug::InitCrashKeys(&keys.at(0), keys.size(), kChunkMaxLength);
 }
 
diff --git a/base/BUILD.gn b/base/BUILD.gn
index f5cbb79..5ebbf04 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -1386,6 +1386,8 @@
     ]
 
     if (use_partition_alloc) {
+      defines += [ "USE_PARTITION_ALLOC" ]
+
       # Add stuff that doesn't work in NaCl.
       sources += [
         # PartitionAlloc uses SpinLock, which doesn't work in NaCl (see below).
diff --git a/base/metrics/histogram_snapshot_manager.h b/base/metrics/histogram_snapshot_manager.h
index 51bf92c..e2a404f 100644
--- a/base/metrics/histogram_snapshot_manager.h
+++ b/base/metrics/histogram_snapshot_manager.h
@@ -28,10 +28,10 @@
 // corruption, this class also validates as much redundancy as it can before
 // calling for the marginal change (a.k.a., delta) in a histogram to be
 // recorded.
-class BASE_EXPORT HistogramSnapshotManager {
+class BASE_EXPORT HistogramSnapshotManager final {
  public:
   explicit HistogramSnapshotManager(HistogramFlattener* histogram_flattener);
-  virtual ~HistogramSnapshotManager();
+  ~HistogramSnapshotManager();
 
   // Snapshot all histograms, and ask |histogram_flattener_| to record the
   // delta. |flags_to_set| is used to set flags for each histogram.
@@ -39,15 +39,13 @@
   // Only histograms that have all the flags specified by the argument will be
   // chosen. If all histograms should be recorded, set it to
   // |Histogram::kNoFlags|.
-  template <class ForwardHistogramIterator>
-  void PrepareDeltas(ForwardHistogramIterator begin,
-                     ForwardHistogramIterator end,
+  void PrepareDeltas(const std::vector<HistogramBase*>& histograms,
                      HistogramBase::Flags flags_to_set,
                      HistogramBase::Flags required_flags) {
-    for (ForwardHistogramIterator it = begin; it != end; ++it) {
-      (*it)->SetFlags(flags_to_set);
-      if (((*it)->flags() & required_flags) == required_flags)
-        PrepareDelta(*it);
+    for (HistogramBase* const histogram : histograms) {
+      histogram->SetFlags(flags_to_set);
+      if ((histogram->flags() & required_flags) == required_flags)
+        PrepareDelta(histogram);
     }
   }
 
diff --git a/base/metrics/statistics_recorder.cc b/base/metrics/statistics_recorder.cc
index a21adc0..82d7a93 100644
--- a/base/metrics/statistics_recorder.cc
+++ b/base/metrics/statistics_recorder.cc
@@ -28,13 +28,24 @@
 
 bool HistogramNameLesser(const base::HistogramBase* a,
                          const base::HistogramBase* b) {
-  return a->histogram_name() < b->histogram_name();
+  return strcmp(a->histogram_name(), b->histogram_name()) < 0;
 }
 
 }  // namespace
 
 namespace base {
 
+size_t StatisticsRecorder::BucketRangesHash::operator()(
+    const BucketRanges* const a) const {
+  return a->checksum();
+}
+
+bool StatisticsRecorder::BucketRangesEqual::operator()(
+    const BucketRanges* const a,
+    const BucketRanges* const b) const {
+  return a->Equals(b);
+}
+
 StatisticsRecorder::~StatisticsRecorder() {
   DCHECK(histograms_);
   DCHECK(ranges_);
@@ -81,92 +92,72 @@
 // static
 HistogramBase* StatisticsRecorder::RegisterOrDeleteDuplicate(
     HistogramBase* histogram) {
-  HistogramBase* histogram_to_delete = nullptr;
-  HistogramBase* histogram_to_return = nullptr;
-  {
-    base::AutoLock auto_lock(lock_.Get());
-    if (!histograms_) {
-      histogram_to_return = histogram;
+  // Declared before auto_lock to ensure correct destruction order.
+  std::unique_ptr<HistogramBase> histogram_deleter;
+  base::AutoLock auto_lock(lock_.Get());
 
-      // As per crbug.com/79322 the histograms are intentionally leaked, so we
-      // need to annotate them. Because ANNOTATE_LEAKING_OBJECT_PTR may be used
-      // only once for an object, the duplicates should not be annotated.
-      // Callers are responsible for not calling RegisterOrDeleteDuplicate(ptr)
-      // twice |if (!histograms_)|.
-      ANNOTATE_LEAKING_OBJECT_PTR(histogram);  // see crbug.com/79322
-    } else {
-      const char* name = histogram->histogram_name();
-      StringPiece name_piece(name);
-      HistogramMap::iterator it = histograms_->find(name_piece);
-      if (histograms_->end() == it) {
-        // |name_piece| is guaranteed to never change or be deallocated so long
-        // as the histogram is alive (which is forever).
-        (*histograms_)[name_piece] = histogram;
-        ANNOTATE_LEAKING_OBJECT_PTR(histogram);  // see crbug.com/79322
-        // If there are callbacks for this histogram, we set the kCallbackExists
-        // flag.
-        auto callback_iterator = callbacks_->find(name);
-        if (callback_iterator != callbacks_->end()) {
-          if (!callback_iterator->second.is_null())
-            histogram->SetFlags(HistogramBase::kCallbackExists);
-          else
-            histogram->ClearFlags(HistogramBase::kCallbackExists);
-        }
-        histogram_to_return = histogram;
-      } else if (histogram == it->second) {
-        // The histogram was registered before.
-        histogram_to_return = histogram;
-      } else {
-        // We already have one histogram with this name.
-        DCHECK_EQ(StringPiece(histogram->histogram_name()),
-                  StringPiece(it->second->histogram_name()))
-            << "hash collision";
-        histogram_to_return = it->second;
-        histogram_to_delete = histogram;
-      }
-    }
+  if (!histograms_) {
+    // As per crbug.com/79322 the histograms are intentionally leaked, so we
+    // need to annotate them. Because ANNOTATE_LEAKING_OBJECT_PTR may be used
+    // only once for an object, the duplicates should not be annotated.
+    // Callers are responsible for not calling RegisterOrDeleteDuplicate(ptr)
+    // twice |if (!histograms_)|.
+    ANNOTATE_LEAKING_OBJECT_PTR(histogram);  // see crbug.com/79322
+    return histogram;
   }
-  delete histogram_to_delete;
-  return histogram_to_return;
+
+  const char* const name = histogram->histogram_name();
+  HistogramBase*& registered = (*histograms_)[name];
+
+  if (!registered) {
+    // |name| is guaranteed to never change or be deallocated so long
+    // as the histogram is alive (which is forever).
+    registered = histogram;
+    ANNOTATE_LEAKING_OBJECT_PTR(histogram);  // see crbug.com/79322
+    // If there are callbacks for this histogram, we set the kCallbackExists
+    // flag.
+    const auto callback_iterator = callbacks_->find(name);
+    if (callback_iterator != callbacks_->end()) {
+      if (!callback_iterator->second.is_null())
+        histogram->SetFlags(HistogramBase::kCallbackExists);
+      else
+        histogram->ClearFlags(HistogramBase::kCallbackExists);
+    }
+    return histogram;
+  }
+
+  if (histogram == registered) {
+    // The histogram was registered before.
+    return histogram;
+  }
+
+  // We already have one histogram with this name.
+  histogram_deleter.reset(histogram);
+  return registered;
 }
 
 // static
 const BucketRanges* StatisticsRecorder::RegisterOrDeleteDuplicateRanges(
     const BucketRanges* ranges) {
   DCHECK(ranges->HasValidChecksum());
-  std::unique_ptr<const BucketRanges> ranges_deleter;
 
+  // Declared before auto_lock to ensure correct destruction order.
+  std::unique_ptr<const BucketRanges> ranges_deleter;
   base::AutoLock auto_lock(lock_.Get());
+
   if (!ranges_) {
     ANNOTATE_LEAKING_OBJECT_PTR(ranges);
     return ranges;
   }
 
-  std::list<const BucketRanges*>* checksum_matching_list;
-  RangesMap::iterator ranges_it = ranges_->find(ranges->checksum());
-  if (ranges_->end() == ranges_it) {
-    // Add a new matching list to map.
-    checksum_matching_list = new std::list<const BucketRanges*>();
-    ANNOTATE_LEAKING_OBJECT_PTR(checksum_matching_list);
-    (*ranges_)[ranges->checksum()] = checksum_matching_list;
+  const BucketRanges* const registered = *ranges_->insert(ranges).first;
+  if (registered == ranges) {
+    ANNOTATE_LEAKING_OBJECT_PTR(ranges);
   } else {
-    checksum_matching_list = ranges_it->second;
+    ranges_deleter.reset(ranges);
   }
 
-  for (const BucketRanges* existing_ranges : *checksum_matching_list) {
-    if (existing_ranges->Equals(ranges)) {
-      if (existing_ranges == ranges) {
-        return ranges;
-      } else {
-        ranges_deleter.reset(ranges);
-        return existing_ranges;
-      }
-    }
-  }
-  // We haven't found a BucketRanges which has the same ranges. Register the
-  // new BucketRanges.
-  checksum_matching_list->push_front(ranges);
-  return ranges;
+  return registered;
 }
 
 // static
@@ -180,7 +171,7 @@
   std::sort(snapshot.begin(), snapshot.end(), &HistogramNameLesser);
   for (const HistogramBase* histogram : snapshot) {
     histogram->WriteHTMLGraph(output);
-    output->append("<br><hr><br>");
+    *output += "<br><hr><br>";
   }
 }
 
@@ -208,16 +199,14 @@
   if (!IsActive())
     return std::string();
 
-  std::string output("{");
   Histograms snapshot;
   GetSnapshot(std::string(), &snapshot);
-  output += "\"histograms\":[";
-  bool first_histogram = true;
-  for (const HistogramBase* histogram : snapshot) {
-    if (first_histogram)
-      first_histogram = false;
-    else
-      output += ",";
+
+  std::string output = "{\"histograms\":[";
+  const char* sep = "";
+  for (const HistogramBase* const histogram : snapshot) {
+    output += sep;
+    sep = ",";
     std::string json;
     histogram->WriteJSON(&json, verbosity_level);
     output += json;
@@ -244,10 +233,8 @@
   if (!ranges_)
     return;
 
-  for (const auto& entry : *ranges_) {
-    for (auto* range_entry : *entry.second) {
-      output->push_back(range_entry);
-    }
+  for (const BucketRanges* const p : *ranges_) {
+    output->push_back(p);
   }
 }
 
@@ -262,10 +249,8 @@
   if (!histograms_)
     return nullptr;
 
-  HistogramMap::iterator it = histograms_->find(name);
-  if (histograms_->end() == it)
-    return nullptr;
-  return it->second;
+  const HistogramMap::const_iterator it = histograms_->find(name);
+  return it != histograms_->end() ? it->second : nullptr;
 }
 
 // static
@@ -291,9 +276,9 @@
   if (include_persistent)
     ImportGlobalPersistentHistograms();
 
-  auto known = GetKnownHistograms(include_persistent);
-  snapshot_manager->PrepareDeltas(known.begin(), known.end(), flags_to_set,
-                                  required_flags);
+  Histograms known = GetKnownHistograms(include_persistent);
+  std::sort(known.begin(), known.end(), &HistogramNameLesser);
+  snapshot_manager->PrepareDeltas(known, flags_to_set, required_flags);
 }
 
 // static
@@ -335,11 +320,10 @@
   if (!histograms_)
     return false;
 
-  if (ContainsKey(*callbacks_, name))
+  if (!callbacks_->insert({name, cb}).second)
     return false;
-  callbacks_->insert(std::make_pair(name, cb));
 
-  auto it = histograms_->find(name);
+  const HistogramMap::const_iterator it = histograms_->find(name);
   if (it != histograms_->end())
     it->second->SetFlags(HistogramBase::kCallbackExists);
 
@@ -355,7 +339,7 @@
   callbacks_->erase(name);
 
   // We also clear the flag from the histogram (if it exists).
-  auto it = histograms_->find(name);
+  const HistogramMap::const_iterator it = histograms_->find(name);
   if (it != histograms_->end())
     it->second->ClearFlags(HistogramBase::kCallbackExists);
 }
@@ -367,17 +351,14 @@
   if (!histograms_)
     return OnSampleCallback();
 
-  auto callback_iterator = callbacks_->find(name);
-  return callback_iterator != callbacks_->end() ? callback_iterator->second
-                                                : OnSampleCallback();
+  const auto it = callbacks_->find(name);
+  return it != callbacks_->end() ? it->second : OnSampleCallback();
 }
 
 // static
 size_t StatisticsRecorder::GetHistogramCount() {
   base::AutoLock auto_lock(lock_.Get());
-  if (!histograms_)
-    return 0;
-  return histograms_->size();
+  return histograms_ ? histograms_->size() : 0;
 }
 
 // static
@@ -385,18 +366,17 @@
   if (!histograms_)
     return;
 
-  HistogramMap::iterator found = histograms_->find(name);
+  const HistogramMap::iterator found = histograms_->find(name);
   if (found == histograms_->end())
     return;
 
-  HistogramBase* base = found->second;
+  HistogramBase* const base = found->second;
   if (base->GetHistogramType() != SPARSE_HISTOGRAM) {
     // When forgetting a histogram, it's likely that other information is
     // also becoming invalid. Clear the persistent reference that may no
     // longer be valid. There's no danger in this as, at worst, duplicates
     // will be created in persistent memory.
-    Histogram* histogram = static_cast<Histogram*>(base);
-    histogram->bucket_ranges()->set_persistent_reference(0);
+    static_cast<Histogram*>(base)->bucket_ranges()->set_persistent_reference(0);
   }
 
   histograms_->erase(found);
@@ -437,20 +417,18 @@
 }
 
 // static
-std::vector<HistogramBase*> StatisticsRecorder::GetKnownHistograms(
+StatisticsRecorder::Histograms StatisticsRecorder::GetKnownHistograms(
     bool include_persistent) {
-  std::vector<HistogramBase*> known;
+  Histograms known;
   base::AutoLock auto_lock(lock_.Get());
   if (!histograms_ || histograms_->empty())
     return known;
 
   known.reserve(histograms_->size());
   for (const auto& h : *histograms_) {
-    if (!include_persistent &&
-        (h.second->flags() & HistogramBase::kIsPersistent)) {
-      continue;
-    }
-    known.push_back(h.second);
+    if (include_persistent ||
+        (h.second->flags() & HistogramBase::kIsPersistent) == 0)
+      known.push_back(h.second);
   }
 
   return known;
@@ -461,18 +439,17 @@
   if (!histograms_)
     return;
 
-  // Import histograms from known persistent storage. Histograms could have
-  // been added by other processes and they must be fetched and recognized
-  // locally. If the persistent memory segment is not shared between processes,
-  // this call does nothing.
-  GlobalHistogramAllocator* allocator = GlobalHistogramAllocator::Get();
-  if (allocator)
+  // Import histograms from known persistent storage. Histograms could have been
+  // added by other processes and they must be fetched and recognized locally.
+  // If the persistent memory segment is not shared between processes, this call
+  // does nothing.
+  if (GlobalHistogramAllocator* allocator = GlobalHistogramAllocator::Get())
     allocator->ImportHistogramsToStatisticsRecorder();
 }
 
 // This singleton instance should be started during the single threaded portion
-// of main(), and hence it is not thread safe.  It initializes globals to
-// provide support for all future calls.
+// of main(), and hence it is not thread safe. It initializes globals to provide
+// support for all future calls.
 StatisticsRecorder::StatisticsRecorder() {
   base::AutoLock auto_lock(lock_.Get());
 
@@ -500,24 +477,24 @@
 
 // static
 void StatisticsRecorder::Reset() {
+  // Declared before auto_lock to ensure correct destruction order.
   std::unique_ptr<HistogramMap> histograms_deleter;
   std::unique_ptr<CallbackMap> callbacks_deleter;
   std::unique_ptr<RangesMap> ranges_deleter;
   std::unique_ptr<HistogramProviders> providers_deleter;
   std::unique_ptr<RecordHistogramChecker> record_checker_deleter;
-  {
-    base::AutoLock auto_lock(lock_.Get());
-    histograms_deleter.reset(histograms_);
-    callbacks_deleter.reset(callbacks_);
-    ranges_deleter.reset(ranges_);
-    providers_deleter.reset(providers_);
-    record_checker_deleter.reset(record_checker_);
-    histograms_ = nullptr;
-    callbacks_ = nullptr;
-    ranges_ = nullptr;
-    providers_ = nullptr;
-    record_checker_ = nullptr;
-  }
+  base::AutoLock auto_lock(lock_.Get());
+  histograms_deleter.reset(histograms_);
+  callbacks_deleter.reset(callbacks_);
+  ranges_deleter.reset(ranges_);
+  providers_deleter.reset(providers_);
+  record_checker_deleter.reset(record_checker_);
+  histograms_ = nullptr;
+  callbacks_ = nullptr;
+  ranges_ = nullptr;
+  providers_ = nullptr;
+  record_checker_ = nullptr;
+
   // We are going to leak the histograms and the ranges.
 }
 
@@ -528,7 +505,6 @@
   VLOG(1) << output;
 }
 
-
 // static
 StatisticsRecorder::HistogramMap* StatisticsRecorder::histograms_ = nullptr;
 // static
@@ -536,7 +512,8 @@
 // static
 StatisticsRecorder::RangesMap* StatisticsRecorder::ranges_ = nullptr;
 // static
-StatisticsRecorder::HistogramProviders* StatisticsRecorder::providers_;
+StatisticsRecorder::HistogramProviders* StatisticsRecorder::providers_ =
+    nullptr;
 // static
 RecordHistogramChecker* StatisticsRecorder::record_checker_ = nullptr;
 // static
diff --git a/base/metrics/statistics_recorder.h b/base/metrics/statistics_recorder.h
index 49ccaf5..25427fd 100644
--- a/base/metrics/statistics_recorder.h
+++ b/base/metrics/statistics_recorder.h
@@ -12,10 +12,10 @@
 
 #include <stdint.h>
 
-#include <list>
-#include <map>
 #include <memory>
 #include <string>
+#include <unordered_map>
+#include <unordered_set>
 #include <vector>
 
 #include "base/base_export.h"
@@ -36,35 +36,6 @@
 
 class BASE_EXPORT StatisticsRecorder {
  public:
-  // A class used as a key for the histogram map below. It always references
-  // a string owned outside of this class, likely in the value of the map.
-  class StringKey : public StringPiece {
-   public:
-    // Constructs the StringKey using various sources. The source must live
-    // at least as long as the created object.
-    StringKey(const std::string& str) : StringPiece(str) {}
-    StringKey(StringPiece str) : StringPiece(str) {}
-
-    // Though StringPiece is better passed by value than by reference, in
-    // this case it's being passed many times and likely already been stored
-    // in memory (not just registers) so the benefit of pass-by-value is
-    // negated.
-    bool operator<(const StringKey& rhs) const {
-      // Since order is unimportant in the map and string comparisons can be
-      // slow, use the length as the primary sort value.
-      if (length() < rhs.length())
-        return true;
-      if (length() > rhs.length())
-        return false;
-
-      // Fall back to an actual string comparison. The lengths are the same
-      // so a simple memory-compare is sufficient. This is slightly more
-      // efficient than calling operator<() for StringPiece which would
-      // again have to check lengths before calling wordmemcmp().
-      return wordmemcmp(data(), rhs.data(), length()) < 0;
-    }
-  };
-
   // An interface class that allows the StatisticsRecorder to forcibly merge
   // histograms from providers when necessary.
   class HistogramProvider {
@@ -73,9 +44,7 @@
     virtual void MergeHistogramDeltas() = 0;
   };
 
-  typedef std::map<StringKey, HistogramBase*> HistogramMap;
   typedef std::vector<HistogramBase*> Histograms;
-  typedef std::vector<WeakPtr<HistogramProvider>> HistogramProviders;
 
   ~StatisticsRecorder();
 
@@ -115,13 +84,17 @@
   static std::string ToJSON(JSONVerbosityLevel verbosity_level);
 
   // Method for extracting histograms which were marked for use by UMA.
+  //
+  // This method is thread safe.
   static void GetHistograms(Histograms* output);
 
   // Method for extracting BucketRanges used by all histograms registered.
   static void GetBucketRanges(std::vector<const BucketRanges*>* output);
 
-  // Find a histogram by name. It matches the exact name. This method is thread
-  // safe.  It returns NULL if a matching histogram is not found.
+  // Find a histogram by name. It matches the exact name.
+  // It returns NULL if a matching histogram is not found.
+  //
+  // This method is thread safe.
   static HistogramBase* FindHistogram(base::StringPiece name);
 
   // Imports histograms from providers. This must be called on the UI thread.
@@ -141,6 +114,8 @@
   // caller supplied vector (Histograms). Only histograms which have |query| as
   // a substring are copied (an empty string will process all registered
   // histograms).
+  //
+  // This method is thread safe.
   static void GetSnapshot(const std::string& query, Histograms* snapshot);
 
   typedef base::Callback<void(HistogramBase::Sample)> OnSampleCallback;
@@ -203,14 +178,26 @@
   static bool ShouldRecordHistogram(uint64_t histogram_hash);
 
  private:
+  typedef std::vector<WeakPtr<HistogramProvider>> HistogramProviders;
+
+  typedef std::unordered_map<StringPiece, HistogramBase*, StringPieceHash>
+      HistogramMap;
+
   // We keep a map of callbacks to histograms, so that as histograms are
   // created, we can set the callback properly.
-  typedef std::map<std::string, OnSampleCallback> CallbackMap;
+  typedef std::unordered_map<std::string, OnSampleCallback> CallbackMap;
 
-  // We keep all |bucket_ranges_| in a map, from checksum to a list of
-  // |bucket_ranges_|.  Checksum is calculated from the |ranges_| in
-  // |bucket_ranges_|.
-  typedef std::map<uint32_t, std::list<const BucketRanges*>*> RangesMap;
+  struct BucketRangesHash {
+    size_t operator()(const BucketRanges* a) const;
+  };
+
+  struct BucketRangesEqual {
+    bool operator()(const BucketRanges* a, const BucketRanges* b) const;
+  };
+
+  typedef std::
+      unordered_set<const BucketRanges*, BucketRangesHash, BucketRangesEqual>
+          RangesMap;
 
   friend struct LazyInstanceTraitsBase<StatisticsRecorder>;
   friend class StatisticsRecorderTest;
@@ -218,8 +205,7 @@
 
   // Fetch set of existing histograms. Ownership of the individual histograms
   // remains with the StatisticsRecorder.
-  static std::vector<HistogramBase*> GetKnownHistograms(
-      bool include_persistent);
+  static Histograms GetKnownHistograms(bool include_persistent);
 
   // Imports histograms from global persistent memory. The global lock must
   // not be held during this call.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelMetrics.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelMetrics.java
index 9858416..2ebfc0e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelMetrics.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelMetrics.java
@@ -120,11 +120,21 @@
             if (mWasContextualCardsDataShown) {
                 ContextualSearchUma.logContextualCardsResultsSeen(mWasSearchContentViewSeen);
             }
+            if (mRankerLogger != null) {
+                mRankerLogger.logOutcome(
+                        ContextualSearchRankerLogger.Feature.OUTCOME_WAS_CARDS_DATA_SHOWN,
+                        mWasContextualCardsDataShown);
+            }
             if (mWasQuickActionShown) {
                 ContextualSearchUma.logQuickActionResultsSeen(mWasSearchContentViewSeen,
                         mQuickActionCategory);
                 ContextualSearchUma.logQuickActionClicked(mWasQuickActionClicked,
                         mQuickActionCategory);
+                if (mRankerLogger != null) {
+                    mRankerLogger.logOutcome(
+                            ContextualSearchRankerLogger.Feature.OUTCOME_WAS_QUICK_ACTION_CLICKED,
+                            mWasQuickActionClicked);
+                }
             }
 
             if (mResultsSeenExperiments != null) {
@@ -132,7 +142,10 @@
                         mWasSearchContentViewSeen, mWasActivatedByTap);
                 mResultsSeenExperiments.logPanelViewedDurations(
                         panelViewDurationMs, mPanelOpenedBeyondPeekDurationMs);
-                if (!isChained) mResultsSeenExperiments = null;
+                if (mRankerLogger != null) {
+                    mResultsSeenExperiments.logRankerTapSuppressionOutcome(mRankerLogger);
+                }
+                mResultsSeenExperiments = null;
             }
             mPanelOpenedBeyondPeekDurationMs = 0;
 
@@ -140,10 +153,24 @@
                 boolean wasAnySuppressionHeuristicSatisfied = mWasAnyHeuristicSatisfiedOnPanelShow;
                 ContextualSearchUma.logAnyTapSuppressionHeuristicSatisfied(
                         mWasSearchContentViewSeen, wasAnySuppressionHeuristicSatisfied);
+                // Update The Ranker logger.
+                if (mRankerLogger != null) {
+                    // Tell Ranker about the primary outcome.
+                    mRankerLogger.logOutcome(
+                            ContextualSearchRankerLogger.Feature.OUTCOME_WAS_PANEL_OPENED,
+                            mWasSearchContentViewSeen);
+                    ContextualSearchUma.logRankerInference(mWasSearchContentViewSeen,
+                            mRankerLogger.getPredictionForTapSuppression());
+                }
                 ContextualSearchUma.logSelectionLengthResultsSeen(
                         mWasSearchContentViewSeen, mSelectionLength);
             }
 
+            // Reset writing to Ranker so whatever interactions occurred are recorded as a
+            // complete record.
+            if (mRankerLogger != null) mRankerLogger.writeLogAndReset();
+            mRankerLogger = null;
+
             // Notifications to Feature Engagement.
             ContextualSearchIPH.doSearchFinishedNotifications(profile, mWasSearchContentViewSeen,
                     mWasActivatedByTap, mWasContextualCardsDataShown);
@@ -324,34 +351,6 @@
     }
 
     /**
-     * Writes all the outcome features to the Ranker Logger and resets the logger.
-     * @param rankerLogger The {@link ContextualSearchRankerLogger} currently being used to measure
-     *                     or suppress the UI by Ranker.
-     */
-    public void writeRankerLoggerOutcomesAndReset() {
-        if (mRankerLogger != null && mWasActivatedByTap) {
-            // Tell Ranker about the primary outcome.
-            mRankerLogger.logOutcome(ContextualSearchRankerLogger.Feature.OUTCOME_WAS_PANEL_OPENED,
-                    mWasSearchContentViewSeen);
-            ContextualSearchUma.logRankerInference(
-                    mWasSearchContentViewSeen, mRankerLogger.getPredictionForTapSuppression());
-            mRankerLogger.logOutcome(
-                    ContextualSearchRankerLogger.Feature.OUTCOME_WAS_CARDS_DATA_SHOWN,
-                    mWasContextualCardsDataShown);
-            if (mWasQuickActionShown) {
-                mRankerLogger.logOutcome(
-                        ContextualSearchRankerLogger.Feature.OUTCOME_WAS_QUICK_ACTION_CLICKED,
-                        mWasQuickActionClicked);
-            }
-            if (mResultsSeenExperiments != null) {
-                mResultsSeenExperiments.logRankerTapSuppressionOutcome(mRankerLogger);
-            }
-            mRankerLogger.writeLogAndReset();
-            mRankerLogger = null;
-        }
-    }
-
-    /**
      * Determine whether a new contextual search is starting.
      * @param toState The contextual search state that will be transitioned to.
      * @param reason The reason for the search state transition.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchHeuristics.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchHeuristics.java
index ab4281d..057685b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchHeuristics.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchHeuristics.java
@@ -12,7 +12,6 @@
  */
 public class ContextualSearchHeuristics {
     protected Set<ContextualSearchHeuristic> mHeuristics;
-    private QuickAnswersHeuristic mQuickAnswersHeuristic;
 
     /**
      * Manages a set of heuristics.
@@ -97,20 +96,4 @@
             heuristic.logRankerTapSuppressionOutcome(logger);
         }
     }
-
-    /**
-     * Sets the {@link QuickAnswersHeuristic} so that it can be accessed externally by
-     * {@link #getQuickAnswersHeuristic}.
-     * @param quickAnswersHeuristic The active {@link QuickAnswersHeuristic}.
-     */
-    public void setQuickAnswersHeuristic(QuickAnswersHeuristic quickAnswersHeuristic) {
-        mQuickAnswersHeuristic = quickAnswersHeuristic;
-    }
-
-    /**
-     * @return The active {@link QuickAnswersHeuristic}.
-     */
-    public QuickAnswersHeuristic getQuickAnswersHeuristic() {
-        return mQuickAnswersHeuristic;
-    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateController.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateController.java
index 6941ef2d..f864760 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateController.java
@@ -77,11 +77,6 @@
          * within the waiting period we'll advance.
          */
         WAITING_FOR_POSSIBLE_TAP_ON_TAP_SELECTION,
-        /** The first state in the Tap-gesture processing pipeline where we know we're processing
-         * a Tap-gesture that won't be converted into a long-press (from tap on tap-selection).  It
-         * may later be suppressed or ignored due to being on an invalid character.
-         */
-        TAP_GESTURE_COMMIT,
         /** Gathers text surrounding the selection. */
         GATHERING_SURROUNDINGS,
         /** Decides if the gesture should trigger the UX or be suppressed. */
@@ -226,13 +221,14 @@
      *        or known.  Only needed when we enter the IDLE state.
      */
     private void startWorkingOn(InternalState state, @Nullable StateChangeReason reason) {
-        // All transitional states should be listed here, but not start states.
         switch (state) {
             case IDLE:
                 assert reason != null;
                 mStateHandler.hideContextualSearchUi(reason);
                 break;
 
+            case LONG_PRESS_RECOGNIZED:
+                break;
             case SHOWING_LONGPRESS_SEARCH:
                 mStateHandler.showContextualSearchLongpressUi();
                 break;
@@ -240,12 +236,11 @@
             case WAITING_FOR_POSSIBLE_TAP_NEAR_PREVIOUS:
                 mStateHandler.waitForPossibleTapNearPrevious();
                 break;
+            case TAP_RECOGNIZED:
+                break;
             case WAITING_FOR_POSSIBLE_TAP_ON_TAP_SELECTION:
                 mStateHandler.waitForPossibleTapOnTapSelection();
                 break;
-            case TAP_GESTURE_COMMIT:
-                mStateHandler.tapGestureCommit();
-                break;
             case GATHERING_SURROUNDINGS:
                 mStateHandler.gatherSurroundingText();
                 break;
@@ -309,13 +304,10 @@
                 if (mPreviousState != null && mPreviousState != InternalState.IDLE) {
                     transitionTo(InternalState.WAITING_FOR_POSSIBLE_TAP_ON_TAP_SELECTION);
                 } else {
-                    transitionTo(InternalState.TAP_GESTURE_COMMIT);
+                    transitionTo(InternalState.GATHERING_SURROUNDINGS);
                 }
                 break;
             case WAITING_FOR_POSSIBLE_TAP_ON_TAP_SELECTION:
-                transitionTo(InternalState.TAP_GESTURE_COMMIT);
-                break;
-            case TAP_GESTURE_COMMIT:
                 transitionTo(InternalState.GATHERING_SURROUNDINGS);
                 break;
             case GATHERING_SURROUNDINGS:
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateHandler.java
index 3b721c3..9e1f23b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateHandler.java
@@ -30,13 +30,6 @@
     void showContextualSearchLongpressUi();
 
     /**
-     * The first state in the Tap-gesture processing pipeline where we know we're processing
-     * a Tap-gesture that won't be converted into something else.  Starts the processing pipeline.
-     * @see ContextualSearchInternalStateController.InternalState#TAP_GESTURE_COMMIT
-     */
-    void tapGestureCommit();
-
-    /**
      * Gathers text surrounding the current selection, which may have been created by either a Tap
      * or a Long-press gesture.
      * @see ContextualSearchInternalStateController.InternalState#GATHERING_SURROUNDINGS
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
index cf3d9ee..bde03300 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
@@ -179,6 +179,7 @@
     private boolean mIsAccessibilityModeEnabled;
 
     /** Tap Experiments and other variable behavior. */
+    private ContextualSearchHeuristics mHeuristics;
     private QuickAnswersHeuristic mQuickAnswersHeuristic;
 
     // Counter for how many times we've called SelectWordAroundCaret without an ACK returned.
@@ -1389,9 +1390,16 @@
 
     @Override
     public void handleMetricsForWouldSuppressTap(ContextualSearchHeuristics tapHeuristics) {
-        mQuickAnswersHeuristic = tapHeuristics.getQuickAnswersHeuristic();
+        mHeuristics = tapHeuristics;
+
+        // TODO(donnd): QuickAnswersHeuristic is getting added to TapSuppressionHeuristics and
+        // and getting considered in TapSuppressionHeuristics#shouldSuppressTap(). It should
+        // be a part of ContextualSearchHeuristics for logging purposes but not for suppression.
+        mQuickAnswersHeuristic = new QuickAnswersHeuristic();
+        mHeuristics.add(mQuickAnswersHeuristic);
+
         if (mSearchPanel != null) {
-            mSearchPanel.getPanelMetrics().setResultsSeenExperiments(tapHeuristics);
+            mSearchPanel.getPanelMetrics().setResultsSeenExperiments(mHeuristics);
             mSearchPanel.getPanelMetrics().setRankerLogger(mTapSuppressionRankerLogger);
         }
     }
@@ -1486,15 +1494,15 @@
                 // Called when the IDLE state has been entered.
                 if (mContext != null) mContext.destroy();
                 mContext = null;
+                // Make sure we write to ranker and reset at the end of every search, even if it
+                // was a suppressed tap or longpress.
+                // TODO(donnd): Find a better place to just make a single call to this (now two).
+                mTapSuppressionRankerLogger.writeLogAndReset();
                 if (mSearchPanel == null) return;
 
-                // Make sure we write to Ranker and reset at the end of every search, even if the
-                // panel was not showing because it was a suppressed tap.
                 if (isSearchPanelShowing()) {
-                    mSearchPanel.getPanelMetrics().writeRankerLoggerOutcomesAndReset();
                     mSearchPanel.closePanel(reason, false);
                 } else {
-                    mTapSuppressionRankerLogger.writeLogAndReset();
                     if (mSelectionController.getSelectionType() == SelectionType.TAP) {
                         mSelectionController.clearSelection();
                     }
@@ -1540,25 +1548,16 @@
                 }
             }
 
-            /** First step where we're committed to processing the current Tap gesture. */
-            @Override
-            public void tapGestureCommit() {
-                mInternalStateController.notifyStartingWorkOn(InternalState.TAP_GESTURE_COMMIT);
-                // We may be processing a chained search (aka a retap -- a tap near a previous tap).
-                // If it's chained we need to log the outcomes and reset, because we won't be hiding
-                // the panel at the end of the previous search (we'll update it to the new Search).
-                if (isSearchPanelShowing()) {
-                    mSearchPanel.getPanelMetrics().writeRankerLoggerOutcomesAndReset();
-                }
-                // Set up the next batch of Ranker logging.
-                mTapSuppressionRankerLogger.setupLoggingForPage(getBaseWebContents());
-                mInternalStateController.notifyFinishedWorkOn(InternalState.TAP_GESTURE_COMMIT);
-            }
-
             /** Starts the process of deciding if we'll suppress the current Tap gesture or not. */
             @Override
             public void decideSuppression() {
                 mInternalStateController.notifyStartingWorkOn(InternalState.DECIDING_SUPPRESSION);
+
+                // Ranker will handle the suppression, but our legacy implementation uses
+                // TapSuppressionHeuristics (run from the ContextualSearchSelectionController).
+                // Usage includes tap-far-from-previous suppression.
+                mTapSuppressionRankerLogger.setupLoggingForPage(getBaseWebContents());
+
                 // TODO(donnd): Move handleShouldSuppressTap out of the Selection Controller.
                 mSelectionController.handleShouldSuppressTap(mContext, mTapSuppressionRankerLogger);
             }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchRankerLoggerImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchRankerLoggerImpl.java
index 7bd5be5..b8a4ae94 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchRankerLoggerImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchRankerLoggerImpl.java
@@ -81,7 +81,6 @@
 
     // A for-testing copy of all the features to log setup so that it will survive a {@link #reset}.
     private Map<Feature, Object> mFeaturesLoggedForTesting;
-    private Map<Feature, Object> mOutcomesLoggedForTesting;
 
     /**
      * Constructs a Ranker Logger and associated native implementation to write Contextual Search
@@ -181,7 +180,7 @@
                 for (Map.Entry<Feature, Object> entry : mFeaturesToLog.entrySet()) {
                     logObject(entry.getKey(), entry.getValue());
                 }
-                mOutcomesLoggedForTesting = mFeaturesToLog;
+                mFeaturesLoggedForTesting = mFeaturesToLog;
             }
             nativeWriteLogAndReset(mNativePointer);
         }
@@ -244,9 +243,9 @@
     }
 
     /**
-     * Gets the current set of features that have been logged.  Should only be used for testing
-     * purposes!
-     * @return The current set of features that have been logged, or {@code null}.
+     * Gets the current set of features to log or that have been logged.  Should only be used for
+     * testing purposes!
+     * @return The current set of features to log or that have been logged, or {@code null}.
      */
     @VisibleForTesting
     @Nullable
@@ -254,17 +253,6 @@
         return mFeaturesLoggedForTesting;
     }
 
-    /**
-     * Gets the current set of outcomes that have been logged.  Should only be used for
-     * testing purposes!
-     * @return The current set of outcomes that have been logged, or {@code null}.
-     */
-    @VisibleForTesting
-    @Nullable
-    Map<Feature, Object> getOutcomesLogged() {
-        return mOutcomesLoggedForTesting;
-    }
-
     // ============================================================================================
     // Native methods.
     // ============================================================================================
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java
index 76f2b8b..abdb4970 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java
@@ -61,6 +61,7 @@
     private boolean mWasTapGestureDetected;
     // Reflects whether the last tap was valid and whether we still have a tap-based selection.
     private ContextualSearchTapState mLastTapState;
+    private boolean mIsWaitingForInvalidTapDetection;
     private boolean mShouldHandleSelectionModification;
     // Whether the selection was automatically expanded due to an adjustment (e.g. Resolve).
     private boolean mDidExpandSelection;
@@ -498,6 +499,14 @@
     // ============================================================================================
 
     /**
+     * @return whether a tap gesture has been detected, for testing.
+     */
+    @VisibleForTesting
+    boolean wasAnyTapGestureDetected() {
+        return mIsWaitingForInvalidTapDetection;
+    }
+
+    /**
      * @return whether selection is empty, for testing.
      */
     @VisibleForTesting
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/TapSuppressionHeuristics.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/TapSuppressionHeuristics.java
index b9ee3c6..5bb768f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/TapSuppressionHeuristics.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/TapSuppressionHeuristics.java
@@ -40,10 +40,6 @@
         mHeuristics.add(new BarOverlapTapSuppression(selectionController, y));
         // Second Tap ML Override.
         mHeuristics.add(new SecondTapMlOverride(selectionController, previousTapState, x, y));
-        // Quick Answer that appears in the Caption via the JS API.
-        QuickAnswersHeuristic quickAnswersHeuristic = new QuickAnswersHeuristic();
-        setQuickAnswersHeuristic(quickAnswersHeuristic);
-        mHeuristics.add(quickAnswersHeuristic);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/SurveyInfoBar.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/SurveyInfoBar.java
index 2506cf6..87d064c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/infobar/SurveyInfoBar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/SurveyInfoBar.java
@@ -7,6 +7,7 @@
 import android.text.method.LinkMovementMethod;
 import android.view.Gravity;
 import android.view.View;
+import android.view.View.OnClickListener;
 import android.widget.TextView;
 
 import org.chromium.base.ApiCompatibilityUtils;
@@ -15,6 +16,7 @@
 import org.chromium.chrome.browser.survey.SurveyController;
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.util.AccessibilityUtil;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.text.NoUnderlineClickableSpan;
 import org.chromium.ui.text.SpanApplier;
@@ -36,6 +38,9 @@
     // The delegate to handle what happens when info bar events are triggered.
     private final SurveyInfoBarDelegate mDelegate;
 
+    // Boolean to track if the infobar was clicked to prevent double triggering of the survey.
+    private boolean mClicked;
+
     /**
      * Create and show the {@link SurveyInfoBar}.
      * @param webContents The webcontents to create the {@link InfoBar} around.
@@ -92,17 +97,11 @@
 
         NoUnderlineClickableSpan clickableSpan = new NoUnderlineClickableSpan() {
             /** Prevent double clicking on the text span. */
-            private boolean mClicked;
 
             @Override
             public void onClick(View widget) {
                 if (mClicked) return;
-                mDelegate.onSurveyTriggered();
-
-                SurveyController.getInstance().showSurveyIfAvailable(
-                        tab.getActivity(), mSiteId, mShowAsBottomSheet, mDisplayLogoResId);
-                closeInfoBar();
-                mClicked = true;
+                showSurvey(tab);
             }
         };
 
@@ -114,6 +113,7 @@
         prompt.setMovementMethod(LinkMovementMethod.getInstance());
         prompt.setGravity(Gravity.CENTER_VERTICAL);
         ApiCompatibilityUtils.setTextAppearance(prompt, R.style.BlackTitle1);
+        addAccessibilityClickListener(prompt, tab);
         layout.addContent(prompt, 1f);
     }
 
@@ -139,6 +139,34 @@
                 siteId, showAsBottomSheet, displayLogoResId, surveyInfoBarDelegate);
     }
 
+    /**
+     * Allows the survey infobar to be triggered when talkback is enabled.
+     * @param view The view to attach the listener.
+     * @param tab The tab to attach the infobar.
+     */
+    private void addAccessibilityClickListener(TextView view, Tab tab) {
+        view.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mClicked || !AccessibilityUtil.isAccessibilityEnabled()) return;
+                showSurvey(tab);
+            }
+        });
+    }
+
+    /**
+     * Shows the survey and closes the infobar.
+     * @param tab The tab on which to show the survey.
+     */
+    private void showSurvey(Tab tab) {
+        mClicked = true;
+        mDelegate.onSurveyTriggered();
+
+        SurveyController.getInstance().showSurveyIfAvailable(
+                tab.getActivity(), mSiteId, mShowAsBottomSheet, mDisplayLogoResId);
+        closeInfoBar();
+    }
+
     private static native void nativeCreate(WebContents webContents, String siteId,
             boolean showAsBottomSheet, int displayLogoResId,
             SurveyInfoBarDelegate surveyInfoBarDelegate);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/survey/ChromeHomeSurveyController.java b/chrome/android/java/src/org/chromium/chrome/browser/survey/ChromeHomeSurveyController.java
index 74ad827..a004b59 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/survey/ChromeHomeSurveyController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/survey/ChromeHomeSurveyController.java
@@ -31,7 +31,6 @@
 import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
-import org.chromium.chrome.browser.util.AccessibilityUtil;
 import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.components.variations.VariationsAssociatedData;
 import org.chromium.ui.base.DeviceFormFactor;
@@ -118,7 +117,6 @@
     private boolean doesUserQualifyForSurvey() {
         if (DeviceFormFactor.isTablet()) return false;
         if (!isUMAEnabled() && !sForceUmaEnabledForTesting) return false;
-        if (AccessibilityUtil.isAccessibilityEnabled()) return false;
         if (hasInfoBarBeenDisplayed()) return false;
         if (!FeatureUtilities.isChromeHomeEnabled()) return true;
         return wasChromeHomeEnabledForMinimumOneWeek()
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFakeServer.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFakeServer.java
index 6f4851e..d775c6c9 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFakeServer.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFakeServer.java
@@ -624,9 +624,6 @@
                 QuickActionCategory.NONE));
         registerFakeTapSearch(new FakeTapSearch("german", false, 200, "Deutsche", "Deutsche",
                 "alternate-term", "", false, 0, 0, "de", "", "", "", QuickActionCategory.NONE));
-        registerFakeTapSearch(new FakeTapSearch("intelligence", false, 200, "Intelligence",
-                "Intelligence", "alternate-term", "", false, 0, 0, "", "", "", "",
-                QuickActionCategory.NONE));
 
         // Register a resolving search of "States" that expands to "United States".
         registerFakeSlowResolveSearch(new FakeSlowResolveSearch("states", false, 200, "States",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateControllerWrapper.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateControllerWrapper.java
index 9f1ab811..fb26d2c9 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateControllerWrapper.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateControllerWrapper.java
@@ -15,11 +15,11 @@
  */
 class ContextualSearchInternalStateControllerWrapper
         extends ContextualSearchInternalStateController {
-    static final List<InternalState> EXPECTED_TAP_RESOLVE_SEQUENCE = CollectionUtil.newArrayList(
-            InternalState.SELECTION_CLEARED_RECOGNIZED, InternalState.TAP_RECOGNIZED,
-            InternalState.TAP_GESTURE_COMMIT, InternalState.GATHERING_SURROUNDINGS,
-            InternalState.DECIDING_SUPPRESSION, InternalState.START_SHOWING_TAP_UI,
-            InternalState.SHOW_FULL_TAP_UI, InternalState.RESOLVING);
+    static final List<InternalState> EXPECTED_TAP_RESOLVE_SEQUENCE =
+            CollectionUtil.newArrayList(InternalState.SELECTION_CLEARED_RECOGNIZED,
+                    InternalState.TAP_RECOGNIZED, InternalState.GATHERING_SURROUNDINGS,
+                    InternalState.DECIDING_SUPPRESSION, InternalState.START_SHOWING_TAP_UI,
+                    InternalState.SHOW_FULL_TAP_UI, InternalState.RESOLVING);
     static final List<InternalState> EXPECTED_LONGPRESS_SEQUENCE =
             CollectionUtil.newArrayList(InternalState.LONG_PRESS_RECOGNIZED,
                     InternalState.GATHERING_SURROUNDINGS, InternalState.SHOWING_LONGPRESS_SEARCH);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
index e9dd28f..e6d7443 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
@@ -863,6 +863,20 @@
     }
 
     /**
+     * Waits for the manager to finish processing a gesture.
+     * Tells the manager that a gesture has started, and then waits for it to complete.
+     */
+    private void waitForGestureProcessing() {
+        CriteriaHelper.pollInstrumentationThread(
+                new Criteria("Gesture processing did not complete.") {
+                    @Override
+                    public boolean isSatisfied() {
+                        return !mSelectionController.wasAnyTapGestureDetected();
+                    }
+                }, TEST_TIMEOUT, DEFAULT_POLLING_INTERVAL);
+    }
+
+    /**
      * Shorthand for a common sequence:
      * 1) Waits for gesture processing,
      * 2) Waits for the panel to close,
@@ -870,6 +884,7 @@
      * @throws InterruptedException
      */
     private void waitForGestureToClosePanelAndAssertNoSelection() throws InterruptedException {
+        waitForGestureProcessing();
         waitForPanelToClose();
         assertPanelClosedOrUndefined();
         Assert.assertTrue(TextUtils.isEmpty(getSelectedText()));
@@ -984,8 +999,6 @@
      */
     private void tapBasePageToClosePanel() throws InterruptedException {
         // TODO(pedrosimonetti): This is not reliable. Find a better approach.
-        // This taps on the panel in an area that will be selected if the "intelligence" node has
-        // been tap-selected, and that will cause it to be long-press selected.
         // We use the far right side (x == 0.9f) to prevent simulating a tap on top of an
         // existing long-press selection (the pins are a tap target). This might not work on RTL.
         // We are using y == 0.35f because otherwise it will fail for long press cases.
@@ -1138,31 +1151,30 @@
         assertWaitForSelectActionBarVisible(true);
     }
 
-    /** Gets the Ranker Logger and asserts if we can't. **/
-    private ContextualSearchRankerLoggerImpl getRankerLogger() {
+    /** @return The value of the given logged feature, or {@code null} if not logged. */
+    private Object loggedToRanker(ContextualSearchRankerLogger.Feature feature) {
         ContextualSearchRankerLoggerImpl rankerLogger =
                 (ContextualSearchRankerLoggerImpl) mManager.getRankerLogger();
         Assert.assertNotNull(rankerLogger);
-        return rankerLogger;
+        return rankerLogger.getFeaturesLogged().get(feature);
     }
 
-    /** @return The value of the given logged feature, or {@code null} if not logged. */
-    private Object loggedToRanker(ContextualSearchRankerLogger.Feature feature) {
-        return getRankerLogger().getFeaturesLogged().get(feature);
+    /** Asserts that the given feature has been logged to Ranker. **/
+    private void assertLoggedToRanker(ContextualSearchRankerLogger.Feature feature) {
+        Assert.assertNotNull(loggedToRanker(feature));
     }
 
     /** Asserts that all the expected features have been logged to Ranker. **/
     private void assertLoggedAllExpectedFeaturesToRanker() {
         for (ContextualSearchRankerLogger.Feature feature : EXPECTED_RANKER_FEATURES) {
-            Assert.assertNotNull(loggedToRanker(feature));
+            assertLoggedToRanker(feature);
         }
     }
 
     /** Asserts that all the expected outcomes have been logged to Ranker. **/
     private void assertLoggedAllExpectedOutcomesToRanker() {
         for (ContextualSearchRankerLogger.Feature feature : EXPECTED_RANKER_OUTCOMES) {
-            Assert.assertNotNull("Expected this outcome to be logged: " + feature,
-                    getRankerLogger().getOutcomesLogged().get(feature));
+            assertLoggedToRanker(feature);
         }
     }
 
@@ -1241,17 +1253,19 @@
     @SmallTest
     @Feature({"ContextualSearch"})
     public void testTap() throws InterruptedException, TimeoutException {
-        simulateTapSearch("intelligence");
+        clickWordNode("intelligence");
+
+        Assert.assertEquals("Intelligence", mFakeServer.getSearchTermRequested());
+        fakeResponse(false, 200, "Intelligence", "display-text", "alternate-term", false);
+        assertContainsParameters("Intelligence", "alternate-term");
+        waitForPanelToPeek();
         assertLoadedLowPriorityUrl();
 
         assertLoggedAllExpectedFeaturesToRanker();
         Assert.assertEquals(
                 true, loggedToRanker(ContextualSearchRankerLogger.Feature.IS_LONG_WORD));
         // The panel must be closed for outcomes to be logged.
-        // Close the panel by clicking far away in order to make sure the outcomes get logged by
-        // the hideContextualSearchUi call to writeRankerLoggerOutcomesAndReset.
-        clickWordNode("states-far");
-        waitForPanelToClose();
+        closePanel();
         assertLoggedAllExpectedOutcomesToRanker();
     }
 
@@ -1444,6 +1458,7 @@
         waitForPanelToPeek();
         assertLoadedNoUrl();  // No load after long-press until opening panel.
         clickNode("question-mark");
+        waitForGestureProcessing();
         waitForPanelToCloseAndSelectionEmpty();
         Assert.assertTrue(TextUtils.isEmpty(getSelectedText()));
         assertLoadedNoUrl();
@@ -1476,6 +1491,7 @@
     public void testTapGestureOnSpecialCharacterDoesntSelect()
             throws InterruptedException, TimeoutException {
         clickNode("question-mark");
+        waitForGestureProcessing();
         Assert.assertNull(getSelectedText());
         assertPanelClosedOrUndefined();
         assertLoadedNoUrl();
@@ -1544,12 +1560,13 @@
         waitForPanelToClose();
         Assert.assertNull(getSelectedText());
         clickNode("states-far");
+        waitForGestureProcessing();
         waitForPanelToPeek();
         Assert.assertEquals("States", getSelectedText());
     }
 
     /**
-     * Tests a "retap" -- that sequential Tap gestures nearby keep selecting.
+     * Tests that sequential Tap gestures nearby keep selecting.
      */
     @Test
     @SmallTest
@@ -1558,7 +1575,6 @@
         clickWordNode("states");
         Assert.assertEquals("States", getSelectedText());
         waitForPanelToPeek();
-        assertLoggedAllExpectedFeaturesToRanker();
         // Avoid issues with double-tap detection by ensuring sequential taps
         // aren't treated as such. Double-tapping can also select words much as
         // longpress, in turn showing the pins and preventing contextual tap
@@ -1570,12 +1586,9 @@
         // for the selection to change.
         clickNode("states-near");
         waitForSelectionToBe("StatesNear");
-        assertLoggedAllExpectedOutcomesToRanker();
-        assertLoggedAllExpectedFeaturesToRanker();
         Thread.sleep(ViewConfiguration.getDoubleTapTimeout());
         clickNode("states");
         waitForSelectionToBe("States");
-        assertLoggedAllExpectedOutcomesToRanker();
     }
 
     /**
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateTest.java
index ea34e07..e91ab55 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateTest.java
@@ -46,11 +46,6 @@
         }
 
         @Override
-        public void tapGestureCommit() {
-            stubForWorkOnState(InternalState.TAP_GESTURE_COMMIT);
-        }
-
-        @Override
         public void gatherSurroundingText() {
             stubForWorkOnState(InternalState.GATHERING_SURROUNDINGS);
         }
diff --git a/chrome/app/chrome_crash_reporter_client_win.cc b/chrome/app/chrome_crash_reporter_client_win.cc
index c8e7b60..825220c 100644
--- a/chrome/app/chrome_crash_reporter_client_win.cc
+++ b/chrome/app/chrome_crash_reporter_client_win.cc
@@ -74,8 +74,6 @@
   // For now these need to be kept relatively up to date with those in
   // chrome/common/crash_keys.cc::RegisterChromeCrashKeys().
   static constexpr base::debug::CrashKey kFixedKeys[] = {
-      {kMetricsClientId, kSmallSize},
-      {kChannel, kSmallSize},
       {kActiveURL, kLargeSize},
       {kNumVariations, kSmallSize},
       {kVariations, kHugeSize},
@@ -97,8 +95,6 @@
   std::vector<base::debug::CrashKey> keys(std::begin(kFixedKeys),
                                           std::end(kFixedKeys));
 
-  crash_keys::GetCrashKeysForCommandLineSwitches(&keys);
-
   // Register the extension IDs.
   {
     static char formatted_keys[kExtensionIDMaxCount]
diff --git a/chrome/app/md_extensions_strings.grdp b/chrome/app/md_extensions_strings.grdp
index b54a2aa..fd8f48bc 100644
--- a/chrome/app/md_extensions_strings.grdp
+++ b/chrome/app/md_extensions_strings.grdp
@@ -16,6 +16,12 @@
   <message name="IDS_MD_EXTENSIONS_ERROR_ANONYMOUS_FUNCTION" desc="The label indicating that an error was caused within an anonymous function in the code.">
     anonymous function
   </message>
+  <message name="IDS_MD_EXTENSIONS_ERROR_CONTEXT" desc="The label for the error's context url.">
+    Context
+  </message>
+  <message name="IDS_MD_EXTENSIONS_ERROR_CONTEXT_UNKNOWN" desc="The text displayed to the user if an error's context url is unknown.">
+    Unknown
+  </message>
   <message name="IDS_MD_EXTENSIONS_ERROR_CLEAR_ALL" desc="The label for the button that clears all the errors.">
     Clear all
   </message>
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index 83edea0..103189ed 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -3489,6 +3489,12 @@
     <message name="IDS_SETTINGS_DISPLAY_NIGHT_LIGHT_LABEL" desc="In Device Settings > Displays, the label for the Night Light feature (which controls the color temperature of the screen) section.">
       Night Light
     </message>
+    <message name="IDS_SETTINGS_DISPLAY_NIGHT_LIGHT_ON_AT_SUNSET" desc="In Device Settings > Displays, the sub label for the automatic schedule which explains that Night Light will turn on automatically at sunset.">
+      Night Light will turn on automatically at sunset
+    </message>
+    <message name="IDS_SETTINGS_DISPLAY_NIGHT_LIGHT_OFF_AT_SUNRISE" desc="In Device Settings > Displays, the sub label for the automatic schedule which explains that Night Light will turn off automatically at sunrise.">
+      Night Light will turn off automatically at sunrise
+    </message>
     <message name="IDS_SETTINGS_DISPLAY_NIGHT_LIGHT_SCHEDULE_CUSTOM" desc="In Device Settings > Displays, the label of the option to set a custom schedule of the Night Light feature.">
       Custom
     </message>
diff --git a/chrome/browser/android/oom_intervention/oom_intervention_decider.cc b/chrome/browser/android/oom_intervention/oom_intervention_decider.cc
index fea0aae..ca6e3cf 100644
--- a/chrome/browser/android/oom_intervention/oom_intervention_decider.cc
+++ b/chrome/browser/android/oom_intervention/oom_intervention_decider.cc
@@ -74,7 +74,7 @@
   PrefService::PrefInitializationStatus pref_status =
       prefs_->GetInitializationStatus();
   if (pref_status == PrefService::INITIALIZATION_STATUS_WAITING) {
-    prefs_->AddPrefInitObserver(base::BindRepeating(
+    prefs_->AddPrefInitObserver(base::BindOnce(
         &OomInterventionDecider::OnPrefInitialized, base::Unretained(this)));
   } else {
     OnPrefInitialized(pref_status ==
diff --git a/chrome/browser/autofill/autofill_provider_browsertest.cc b/chrome/browser/autofill/autofill_provider_browsertest.cc
new file mode 100644
index 0000000..d905b2ae
--- /dev/null
+++ b/chrome/browser/autofill/autofill_provider_browsertest.cc
@@ -0,0 +1,187 @@
+// 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 "base/macros.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/autofill/content/browser/content_autofill_driver_factory.h"
+#include "components/autofill/core/browser/test_autofill_client.h"
+#include "components/autofill/core/browser/test_autofill_provider.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_utils.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace autofill {
+namespace {
+
+const base::FilePath::CharType kDocRoot[] =
+    FILE_PATH_LITERAL("chrome/test/data");
+
+// The workaround class for MSVC warning C4373
+// see
+// https://github.com/google/googlemock/blob/master/googlemock/docs/v1_5/FrequentlyAskedQuestions.md
+class MockableAutofillProvider : public TestAutofillProvider {
+ public:
+  ~MockableAutofillProvider() override {}
+  bool OnWillSubmitForm(AutofillHandlerProxy* handler,
+                        const FormData& form,
+                        const base::TimeTicks timestamp) override {
+    return MockableOnWillSubmitForm(handler, form);
+  }
+
+  virtual bool MockableOnWillSubmitForm(AutofillHandlerProxy* handler,
+                                        const FormData& form) = 0;
+};
+
+class MockAutofillProvider : public MockableAutofillProvider {
+ public:
+  MockAutofillProvider() {}
+  ~MockAutofillProvider() override {}
+
+  MOCK_METHOD2(MockableOnWillSubmitForm,
+               bool(AutofillHandlerProxy* handler, const FormData& form));
+};
+
+}  // namespace
+
+class AutofillProviderBrowserTest : public InProcessBrowserTest {
+ public:
+  AutofillProviderBrowserTest() {}
+  ~AutofillProviderBrowserTest() override {}
+
+  void SetUpOnMainThread() override {
+    content::WebContents* web_contents = WebContents();
+    ASSERT_TRUE(web_contents != NULL);
+
+    web_contents->RemoveUserData(
+        ContentAutofillDriverFactory::
+            kContentAutofillDriverFactoryWebContentsUserDataKey);
+
+    ContentAutofillDriverFactory::CreateForWebContentsAndDelegate(
+        web_contents, &autofill_client_, "en-US",
+        AutofillManager::DISABLE_AUTOFILL_DOWNLOAD_MANAGER,
+        &autofill_provider_);
+
+    embedded_test_server()->AddDefaultHandlers(base::FilePath(kDocRoot));
+    // Serve both a.com and b.com (and any other domain).
+    host_resolver()->AddRule("*", "127.0.0.1");
+    ASSERT_TRUE(embedded_test_server()->Start());
+  }
+
+  void TearDownOnMainThread() override {
+    testing::Mock::VerifyAndClearExpectations(&autofill_provider_);
+  }
+
+  content::RenderViewHost* RenderViewHost() {
+    return WebContents()->GetRenderViewHost();
+  }
+
+  content::WebContents* WebContents() {
+    return browser()->tab_strip_model()->GetActiveWebContents();
+  }
+
+  void SimulateUserTypingInFocuedField() {
+    content::WebContents* web_contents = WebContents();
+
+    content::SimulateKeyPress(web_contents, ui::DomKey::FromCharacter('O'),
+                              ui::DomCode::US_O, ui::VKEY_O, false, false,
+                              false, false);
+    content::SimulateKeyPress(web_contents, ui::DomKey::FromCharacter('R'),
+                              ui::DomCode::US_R, ui::VKEY_R, false, false,
+                              false, false);
+    content::SimulateKeyPress(web_contents, ui::DomKey::FromCharacter('A'),
+                              ui::DomCode::US_A, ui::VKEY_A, false, false,
+                              false, false);
+    content::SimulateKeyPress(web_contents, ui::DomKey::FromCharacter('R'),
+                              ui::DomCode::US_R, ui::VKEY_R, false, false,
+                              false, false);
+    content::SimulateKeyPress(web_contents, ui::DomKey::FromCharacter('Y'),
+                              ui::DomCode::US_Y, ui::VKEY_Y, false, false,
+                              false, false);
+  }
+
+ protected:
+  MockAutofillProvider autofill_provider_;
+
+ private:
+  TestAutofillClient autofill_client_;
+};
+
+IN_PROC_BROWSER_TEST_F(AutofillProviderBrowserTest,
+                       FrameDetachedOnFormlessSubmission) {
+  EXPECT_CALL(autofill_provider_,
+              MockableOnWillSubmitForm(testing::_, testing::_))
+      .Times(1);
+  ui_test_utils::NavigateToURL(
+      browser(), embedded_test_server()->GetURL(
+                     "/autofill/frame_detached_on_formless_submit.html"));
+
+  // Need to pay attention for a message that XHR has finished since there
+  // is no navigation to wait for.
+  content::DOMMessageQueue message_queue;
+
+  std::string focus(
+      "var iframe = document.getElementById('address_iframe');"
+      "var frame_doc = iframe.contentDocument;"
+      "frame_doc.getElementById('address_field').focus();");
+  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), focus));
+
+  SimulateUserTypingInFocuedField();
+  std::string fill_and_submit =
+      "var iframe = document.getElementById('address_iframe');"
+      "var frame_doc = iframe.contentDocument;"
+      "frame_doc.getElementById('submit_button').click();";
+
+  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit));
+  std::string message;
+  while (message_queue.WaitForMessage(&message)) {
+    if (message == "\"SUBMISSION_FINISHED\"")
+      break;
+  }
+  EXPECT_EQ("\"SUBMISSION_FINISHED\"", message);
+}
+
+IN_PROC_BROWSER_TEST_F(AutofillProviderBrowserTest,
+                       FrameDetachedOnFormSubmission) {
+  EXPECT_CALL(autofill_provider_,
+              MockableOnWillSubmitForm(testing::_, testing::_))
+      .Times(1);
+  ui_test_utils::NavigateToURL(
+      browser(), embedded_test_server()->GetURL(
+                     "/autofill/frame_detached_on_form_submit.html"));
+
+  // Need to pay attention for a message that XHR has finished since there
+  // is no navigation to wait for.
+  content::DOMMessageQueue message_queue;
+
+  std::string focus(
+      "var iframe = document.getElementById('address_iframe');"
+      "var frame_doc = iframe.contentDocument;"
+      "frame_doc.getElementById('address_field').focus();");
+  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), focus));
+
+  SimulateUserTypingInFocuedField();
+  std::string fill_and_submit =
+      "var iframe = document.getElementById('address_iframe');"
+      "var frame_doc = iframe.contentDocument;"
+      "frame_doc.getElementById('submit_button').click();";
+
+  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit));
+  std::string message;
+  while (message_queue.WaitForMessage(&message)) {
+    if (message == "\"SUBMISSION_FINISHED\"")
+      break;
+  }
+  EXPECT_EQ("\"SUBMISSION_FINISHED\"", message);
+}
+
+}  // namespace autofill
diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_browser_main.cc
index 2016723..3a1d9671 100644
--- a/chrome/browser/chrome_browser_main.cc
+++ b/chrome/browser/chrome_browser_main.cc
@@ -207,6 +207,10 @@
 #include "chrome/browser/first_run/upgrade_util_linux.h"
 #endif  // defined(OS_LINUX) && !defined(OS_CHROMEOS)
 
+#if defined(OS_LINUX)
+#include "components/crash/content/app/breakpad_linux.h"
+#endif
+
 #if defined(OS_MACOSX)
 #include <Security/Security.h>
 
@@ -1125,8 +1129,7 @@
 
 #if defined(OS_LINUX) || defined(OS_OPENBSD)
   // Set the product channel for crash reports.
-  base::debug::SetCrashKeyValue(crash_keys::kChannel,
-                                chrome::GetChannelString());
+  breakpad::SetChannelCrashKey(chrome::GetChannelString());
 #endif  // defined(OS_LINUX) || defined(OS_OPENBSD)
 
 #if defined(OS_MACOSX)
diff --git a/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge.cc b/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge.cc
index 1f1ce2ed..0be08405 100644
--- a/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge.cc
+++ b/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge.cc
@@ -137,6 +137,14 @@
 
 }  // namespace
 
+ArcAccessibilityHelperBridge::CountedAXTree::CountedAXTree(
+    AXTreeSourceArc* ax_tree)
+    : count(1U) {
+  tree.reset(ax_tree);
+}
+
+ArcAccessibilityHelperBridge::CountedAXTree::~CountedAXTree() {}
+
 // static
 ArcAccessibilityHelperBridge*
 ArcAccessibilityHelperBridge::GetForBrowserContext(
@@ -244,12 +252,11 @@
     bool is_notification_event = event_data->notification_key.has_value();
     if (is_notification_event) {
       std::string notification_key = event_data->notification_key.value();
-      if (event_data->event_type ==
-          arc::mojom::AccessibilityEventType::WINDOW_STATE_CHANGED) {
-        tree_source = CreateFromNotificationKey(notification_key);
-      } else {
-        tree_source = GetFromNotificationKey(notification_key);
-      }
+      bool increment_counter =
+          event_data->event_type ==
+          arc::mojom::AccessibilityEventType::WINDOW_STATE_CHANGED;
+      tree_source =
+          GetOrCreateFromNotificationKey(notification_key, increment_counter);
     } else {
       if (event_data->task_id == kNoTaskId)
         return;
@@ -317,21 +324,20 @@
   return tree_source;
 }
 
-AXTreeSourceArc* ArcAccessibilityHelperBridge::CreateFromNotificationKey(
-    const std::string& notification_key) {
-  CHECK_EQ(0U, notification_key_to_tree_.count(notification_key));
-
-  notification_key_to_tree_[notification_key].reset(new AXTreeSourceArc(this));
-  return notification_key_to_tree_[notification_key].get();
-}
-
-AXTreeSourceArc* ArcAccessibilityHelperBridge::GetFromNotificationKey(
-    const std::string& notification_key) const {
+AXTreeSourceArc* ArcAccessibilityHelperBridge::GetOrCreateFromNotificationKey(
+    const std::string& notification_key,
+    bool increment_counter) {
   auto tree_it = notification_key_to_tree_.find(notification_key);
-  if (tree_it == notification_key_to_tree_.end())
-    return nullptr;
+  if (tree_it == notification_key_to_tree_.end()) {
+    notification_key_to_tree_[notification_key].reset(
+        new CountedAXTree(new AXTreeSourceArc(this)));
+    return notification_key_to_tree_[notification_key]->tree.get();
+  }
 
-  return tree_it->second.get();
+  if (increment_counter)
+    tree_it->second->count++;
+
+  return tree_it->second->tree.get();
 }
 
 AXTreeSourceArc* ArcAccessibilityHelperBridge::GetFromTreeId(
@@ -346,9 +352,9 @@
   for (auto notification_it = notification_key_to_tree_.begin();
        notification_it != notification_key_to_tree_.end(); ++notification_it) {
     ui::AXTreeData tree_data;
-    notification_it->second->GetTreeData(&tree_data);
+    notification_it->second->tree->GetTreeData(&tree_data);
     if (tree_data.tree_id == tree_id)
-      return notification_it->second.get();
+      return notification_it->second->tree.get();
   }
 
   return nullptr;
@@ -470,12 +476,14 @@
 void ArcAccessibilityHelperBridge::OnNotificationSurfaceAdded(
     ArcNotificationSurface* surface) {
   const std::string& notification_key = surface->GetNotificationKey();
-  auto tree_it = notification_key_to_tree_.find(notification_key);
-  if (tree_it == notification_key_to_tree_.end())
+
+  AXTreeSourceArc* tree = GetOrCreateFromNotificationKey(
+      notification_key, false /* increment_counter */);
+  if (!tree)
     return;
 
   ui::AXTreeData tree_data;
-  if (!tree_it->second->GetTreeData(&tree_data))
+  if (!tree->GetTreeData(&tree_data))
     return;
 
   surface->SetAXTreeId(tree_data.tree_id);
@@ -495,7 +503,16 @@
 void ArcAccessibilityHelperBridge::OnNotificationSurfaceRemoved(
     ArcNotificationSurface* surface) {
   const std::string& notification_key = surface->GetNotificationKey();
-  notification_key_to_tree_.erase(notification_key);
+  auto it = notification_key_to_tree_.find(notification_key);
+  if (it == notification_key_to_tree_.end())
+    return;
+
+  it->second->count--;
+
+  CHECK(it->second->count >= 0);
+
+  if (it->second->count == 0)
+    notification_key_to_tree_.erase(notification_key);
 }
 
 }  // namespace arc
diff --git a/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge.h b/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge.h
index 5afd4967..aacc932 100644
--- a/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge.h
+++ b/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge.h
@@ -42,6 +42,14 @@
       public ArcAppListPrefs::Observer,
       public ArcNotificationSurfaceManager::Observer {
  public:
+  struct CountedAXTree {
+    explicit CountedAXTree(AXTreeSourceArc* ax_tree);
+    ~CountedAXTree();
+
+    uint32_t count;
+    std::unique_ptr<AXTreeSourceArc> tree;
+  };
+
   // Returns singleton instance for the given BrowserContext,
   // or nullptr if the browser |context| is not allowed to use ARC.
   static ArcAccessibilityHelperBridge* GetForBrowserContext(
@@ -81,10 +89,15 @@
   void OnNotificationSurfaceRemoved(ArcNotificationSurface* surface) override;
 
   const std::map<int32_t, std::unique_ptr<AXTreeSourceArc>>&
-  task_id_to_tree_for_test() {
+  task_id_to_tree_for_test() const {
     return task_id_to_tree_;
   }
 
+  const std::map<std::string, std::unique_ptr<CountedAXTree>>&
+  notification_key_to_tree_for_test() const {
+    return notification_key_to_tree_;
+  }
+
  protected:
   virtual aura::Window* GetActiveWindow();
 
@@ -97,16 +110,15 @@
   void OnActionResult(const ui::AXActionData& data, bool result) const;
 
   AXTreeSourceArc* GetOrCreateFromTaskId(int32_t task_id);
-  AXTreeSourceArc* CreateFromNotificationKey(
-      const std::string& notification_key);
-  AXTreeSourceArc* GetFromNotificationKey(
-      const std::string& notification_key) const;
+  AXTreeSourceArc* GetOrCreateFromNotificationKey(
+      const std::string& notification_key,
+      bool increment_counter);
   AXTreeSourceArc* GetFromTreeId(int32_t tree_id) const;
 
   Profile* const profile_;
   ArcBridgeService* const arc_bridge_service_;
   std::map<int32_t, std::unique_ptr<AXTreeSourceArc>> task_id_to_tree_;
-  std::map<std::string, std::unique_ptr<AXTreeSourceArc>>
+  std::map<std::string, std::unique_ptr<CountedAXTree>>
       notification_key_to_tree_;
 
   DISALLOW_COPY_AND_ASSIGN(ArcAccessibilityHelperBridge);
diff --git a/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge_unittest.cc b/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge_unittest.cc
index ba9ac8b..71f32611a 100644
--- a/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge_unittest.cc
+++ b/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge_unittest.cc
@@ -16,12 +16,19 @@
 #include "components/exo/shell_surface.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/arc/notification/arc_notification_surface.h"
 #include "ui/aura/window.h"
 #include "ui/display/display.h"
 #include "ui/display/manager/managed_display_info.h"
 
 namespace arc {
 
+namespace {
+
+const char kNotificationKey[] = "unit.test.notification";
+
+}  // namespace
+
 class ArcAccessibilityHelperBridgeTest : public testing::Test {
  public:
   class TestArcAccessibilityHelperBridge : public ArcAccessibilityHelperBridge {
@@ -46,6 +53,40 @@
     DISALLOW_COPY_AND_ASSIGN(TestArcAccessibilityHelperBridge);
   };
 
+  class ArcNotificationSurfaceTest : public ArcNotificationSurface {
+   public:
+    explicit ArcNotificationSurfaceTest(std::string notification_key)
+        : notification_key_(notification_key), ax_tree_id_(-1) {}
+
+    gfx::Size GetSize() const override { return gfx::Size(); }
+
+    aura::Window* GetWindow() const override { return nullptr; }
+
+    aura::Window* GetContentWindow() const override { return nullptr; }
+
+    const std::string& GetNotificationKey() const override {
+      return notification_key_;
+    }
+
+    void Attach(views::NativeViewHost* native_view_host) override {}
+
+    void Detach() override {}
+
+    bool IsAttached() const override { return false; }
+
+    views::NativeViewHost* GetAttachedHost() const override { return nullptr; }
+
+    void FocusSurfaceWindow() override {}
+
+    void SetAXTreeId(int32_t ax_tree_id) override { ax_tree_id_ = ax_tree_id; }
+
+    int32_t GetAXTreeId() const override { return ax_tree_id_; }
+
+   private:
+    const std::string notification_key_;
+    int32_t ax_tree_id_;
+  };
+
   ArcAccessibilityHelperBridgeTest() = default;
 
   void SetUp() override {
@@ -153,4 +194,66 @@
   ASSERT_EQ(0U, task_id_to_tree.size());
 }
 
+// Accessibility event and surface creation/removal are sent in different
+// channels, mojo and wayland. Order of those events can be changed. This is the
+// case where mojo events arrive earlier than surface creation/removal.
+TEST_F(ArcAccessibilityHelperBridgeTest, NotificationEventArriveFirst) {
+  base::CommandLine::ForCurrentProcess()->AppendSwitch(
+      chromeos::switches::kEnableChromeVoxArcSupport);
+
+  TestArcAccessibilityHelperBridge* helper_bridge =
+      accessibility_helper_bridge();
+
+  const auto& notification_key_to_tree_ =
+      helper_bridge->notification_key_to_tree_for_test();
+  ASSERT_EQ(0U, notification_key_to_tree_.size());
+
+  // Dispatch an accessibility event for creation of notification.
+  auto event1 = arc::mojom::AccessibilityEventData::New();
+  event1->event_type = arc::mojom::AccessibilityEventType::WINDOW_STATE_CHANGED;
+  event1->notification_key = base::make_optional<std::string>(kNotificationKey);
+  event1->node_data.push_back(arc::mojom::AccessibilityNodeInfoData::New());
+  helper_bridge->OnAccessibilityEvent(event1.Clone());
+
+  EXPECT_EQ(1U, notification_key_to_tree_.size());
+
+  // Add notification surface for the first event.
+  ArcNotificationSurfaceTest test_surface(kNotificationKey);
+  helper_bridge->OnNotificationSurfaceAdded(&test_surface);
+
+  // Confirm that axtree id is set to the surface.
+  auto it = notification_key_to_tree_.find(kNotificationKey);
+  EXPECT_NE(notification_key_to_tree_.end(), it);
+  AXTreeSourceArc* tree = it->second->tree.get();
+  ui::AXTreeData tree_data;
+  tree->GetTreeData(&tree_data);
+  EXPECT_EQ(tree_data.tree_id, test_surface.GetAXTreeId());
+
+  // Dispatch second event for notification creation before surface is removed.
+  auto event2 = arc::mojom::AccessibilityEventData::New();
+  event2->event_type = arc::mojom::AccessibilityEventType::WINDOW_STATE_CHANGED;
+  event2->notification_key = base::make_optional<std::string>(kNotificationKey);
+  event2->node_data.push_back(arc::mojom::AccessibilityNodeInfoData::New());
+  helper_bridge->OnAccessibilityEvent(event2.Clone());
+
+  EXPECT_EQ(1U, notification_key_to_tree_.size());
+
+  // Remove notification surface for the first event.
+  helper_bridge->OnNotificationSurfaceRemoved(&test_surface);
+
+  // Tree shouldn't be removed as a surface for the second event will come.
+  EXPECT_EQ(1U, notification_key_to_tree_.size());
+
+  // Add notification surface for the second event, and confirm that axtree id
+  // is set.
+  ArcNotificationSurfaceTest test_surface_2(kNotificationKey);
+  helper_bridge->OnNotificationSurfaceAdded(&test_surface_2);
+  EXPECT_EQ(tree_data.tree_id, test_surface_2.GetAXTreeId());
+
+  // Remove notification surface for the second event, and confirm that tree is
+  // deleted.
+  helper_bridge->OnNotificationSurfaceRemoved(&test_surface_2);
+  EXPECT_EQ(0U, notification_key_to_tree_.size());
+}
+
 }  // namespace arc
diff --git a/chrome/browser/chromeos/login/wizard_controller.cc b/chrome/browser/chromeos/login/wizard_controller.cc
index 58be25e9..130b963 100644
--- a/chrome/browser/chromeos/login/wizard_controller.cc
+++ b/chrome/browser/chromeos/login/wizard_controller.cc
@@ -323,8 +323,8 @@
       return;
     } else if (status == PrefService::INITIALIZATION_STATUS_WAITING) {
       GetLocalState()->AddPrefInitObserver(
-          base::Bind(&WizardController::OnLocalStateInitialized,
-                     weak_factory_.GetWeakPtr()));
+          base::BindOnce(&WizardController::OnLocalStateInitialized,
+                         weak_factory_.GetWeakPtr()));
     }
   }
 
diff --git a/chrome/browser/chromeos/system/timezone_resolver_manager.cc b/chrome/browser/chromeos/system/timezone_resolver_manager.cc
index 6dd464d..a5829b12 100644
--- a/chrome/browser/chromeos/system/timezone_resolver_manager.cc
+++ b/chrome/browser/chromeos/system/timezone_resolver_manager.cc
@@ -128,8 +128,8 @@
       g_browser_process->local_state()->GetInitializationStatus() ==
       PrefService::INITIALIZATION_STATUS_SUCCESS;
   g_browser_process->local_state()->AddPrefInitObserver(
-      base::Bind(&TimeZoneResolverManager::OnLocalStateInitialized,
-                 weak_factory_.GetWeakPtr()));
+      base::BindOnce(&TimeZoneResolverManager::OnLocalStateInitialized,
+                     weak_factory_.GetWeakPtr()));
 
   local_state_pref_change_registrar_.Init(g_browser_process->local_state());
   local_state_pref_change_registrar_.Add(
diff --git a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc
index cbccab4..bc8703f 100644
--- a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc
+++ b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc
@@ -7,8 +7,11 @@
 #include <stddef.h>
 
 #include "base/memory/ptr_util.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/common/pref_names.h"
 #include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_service.h"
+#include "crypto/sha2.h"
 #include "extensions/common/error_utils.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 
@@ -19,6 +22,31 @@
     "https://www.gstatic.com/securitykey/origins.json",
     "https://www.gstatic.com/securitykey/a/google.com/origins.json"};
 
+// ContainsAppIdByHash returns true iff the SHA-256 hash of one of the
+// elements of |list| equals |hash|.
+bool ContainsAppIdByHash(const base::ListValue& list,
+                         const std::vector<char>& hash) {
+  if (hash.size() != crypto::kSHA256Length) {
+    return false;
+  }
+
+  for (const auto& i : list) {
+    const std::string& s = i.GetString();
+    if (s.find('/') == std::string::npos) {
+      // No slashes mean that this is a webauthn RP ID, not a U2F AppID.
+      continue;
+    }
+
+    if (crypto::SHA256HashString(s).compare(0, crypto::kSHA256Length,
+                                            hash.data(),
+                                            crypto::kSHA256Length) == 0) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 }  // namespace
 
 namespace extensions {
@@ -87,24 +115,27 @@
   return RespondNow(OneArgument(base::MakeUnique<base::Value>(false)));
 }
 
-// TODO(agl/mab): remove special casing for individual attestation in
-// Javascript in favour of an enterprise policy, which can be accessed like
-// this:
-//
-// #include "chrome/browser/profiles/profile.h"
-// #include "components/prefs/pref_service.h"
-//
-//   Profile* const profile = Profile::FromBrowserContext(browser_context());
-//   const PrefService* const prefs = profile->GetPrefs();
-//   const base::ListValue* const permit_attestation =
-//       prefs->GetList(prefs::kSecurityKeyPermitAttestation);
-//
-//   for (size_t i = 0; i < permit_attestation->GetSize(); i++) {
-//     std::string value;
-//     if (!permit_attestation->GetString(i, &value)) {
-//       continue;
-//     }
-//   }
+CryptotokenPrivateIsAppIdHashInEnterpriseContextFunction::
+    CryptotokenPrivateIsAppIdHashInEnterpriseContextFunction()
+    : chrome_details_(this) {}
+
+ExtensionFunction::ResponseAction
+CryptotokenPrivateIsAppIdHashInEnterpriseContextFunction::Run() {
+  std::unique_ptr<cryptotoken_private::IsAppIdHashInEnterpriseContext::Params>
+      params(
+          cryptotoken_private::IsAppIdHashInEnterpriseContext::Params::Create(
+              *args_));
+  EXTENSION_FUNCTION_VALIDATE(params);
+
+  Profile* const profile = Profile::FromBrowserContext(browser_context());
+  const PrefService* const prefs = profile->GetPrefs();
+  const base::ListValue* const permit_attestation =
+      prefs->GetList(prefs::kSecurityKeyPermitAttestation);
+
+  return RespondNow(ArgumentList(
+      cryptotoken_private::IsAppIdHashInEnterpriseContext::Results::Create(
+          ContainsAppIdByHash(*permit_attestation, params->app_id_hash))));
+}
 
 }  // namespace api
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.h b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.h
index 55654b13..2642284 100644
--- a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.h
+++ b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.h
@@ -7,7 +7,9 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
+#include "base/values.h"
 #include "chrome/browser/extensions/chrome_extension_function_details.h"
 #include "chrome/common/extensions/api/cryptotoken_private.h"
 #include "extensions/browser/extension_function.h"
@@ -38,6 +40,22 @@
     ChromeExtensionFunctionDetails chrome_details_;
 };
 
+class CryptotokenPrivateIsAppIdHashInEnterpriseContextFunction
+    : public UIThreadExtensionFunction {
+ public:
+  CryptotokenPrivateIsAppIdHashInEnterpriseContextFunction();
+  DECLARE_EXTENSION_FUNCTION(
+      "cryptotokenPrivate.isAppIdHashInEnterpriseContext",
+      CRYPTOTOKENPRIVATE_ISAPPIDHASHINENTERPRISECONTEXT)
+
+ protected:
+  ~CryptotokenPrivateIsAppIdHashInEnterpriseContextFunction() override {}
+  ResponseAction Run() override;
+
+ private:
+  ChromeExtensionFunctionDetails chrome_details_;
+};
+
 }  // namespace api
 }  // namespace extensions
 
diff --git a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api_unittest.cc b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api_unittest.cc
index 937d4010..e198d041 100644
--- a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api_unittest.cc
+++ b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api_unittest.cc
@@ -12,8 +12,12 @@
 
 #include "chrome/browser/extensions/extension_api_unittest.h"
 #include "chrome/browser/extensions/extension_function_test_utils.h"
+#include "chrome/common/pref_names.h"
+#include "crypto/sha2.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+using crypto::SHA256HashString;
+
 namespace extensions {
 
 namespace {
@@ -30,64 +34,124 @@
       UIThreadExtensionFunction* function, bool* result) {
     const base::ListValue* result_list = function->GetResultList();
     if (!result_list) {
-      LOG(ERROR) << "Function has no result list.";
+      ADD_FAILURE() << "Function has no result list.";
       return false;
     }
 
     if (result_list->GetSize() != 1u) {
-      LOG(ERROR) << "Invalid number of results.";
+      ADD_FAILURE() << "Invalid number of results.";
       return false;
     }
 
     if (!result_list->GetBoolean(0, result)) {
-      LOG(ERROR) << "Result is not boolean.";
+      ADD_FAILURE() << "Result is not boolean.";
       return false;
     }
     return true;
   }
 
   bool GetCanOriginAssertAppIdResult(const std::string& origin,
-                                     const std::string& appId) {
+                                     const std::string& app_id,
+                                     bool *out_result) {
     scoped_refptr<api::CryptotokenPrivateCanOriginAssertAppIdFunction> function(
         new api::CryptotokenPrivateCanOriginAssertAppIdFunction());
     function->set_has_callback(true);
 
     std::unique_ptr<base::ListValue> args(new base::ListValue);
     args->AppendString(origin);
-    args->AppendString(appId);
+    args->AppendString(app_id);
 
-    extension_function_test_utils::RunFunction(
-        function.get(), std::move(args), browser(),
-        extension_function_test_utils::NONE);
+    if (!extension_function_test_utils::RunFunction(
+            function.get(), std::move(args), browser(),
+            extension_function_test_utils::NONE)) {
+      return false;
+    }
 
-    bool result;
-    GetSingleBooleanResult(function.get(), &result);
-    return result;
+    return GetSingleBooleanResult(function.get(), out_result);
+  }
+
+  bool GetAppIdHashInEnterpriseContext(const std::string& app_id,
+                                       bool* out_result) {
+    scoped_refptr<api::CryptotokenPrivateIsAppIdHashInEnterpriseContextFunction>
+        function(
+            new api::
+                CryptotokenPrivateIsAppIdHashInEnterpriseContextFunction());
+    function->set_has_callback(true);
+
+    auto args = std::make_unique<base::Value>(base::Value::Type::LIST);
+    args->GetList().emplace_back(
+        base::Value::BlobStorage(app_id.begin(), app_id.end()));
+
+    if (!extension_function_test_utils::RunFunction(
+            function.get(), base::ListValue::From(std::move(args)), browser(),
+            extension_function_test_utils::NONE)) {
+      return false;
+    }
+
+    return GetSingleBooleanResult(function.get(), out_result);
   }
 };
 
 TEST_F(CryptoTokenPrivateApiTest, CanOriginAssertAppId) {
   std::string origin1("https://www.example.com");
 
-  EXPECT_TRUE(GetCanOriginAssertAppIdResult(origin1, origin1));
+  bool result;
+  ASSERT_TRUE(GetCanOriginAssertAppIdResult(origin1, origin1, &result));
+  EXPECT_TRUE(result);
 
   std::string same_origin_appid("https://www.example.com/appId");
-  EXPECT_TRUE(GetCanOriginAssertAppIdResult(origin1, same_origin_appid));
+  ASSERT_TRUE(
+      GetCanOriginAssertAppIdResult(origin1, same_origin_appid, &result));
+  EXPECT_TRUE(result);
   std::string same_etld_plus_one_appid("https://appid.example.com/appId");
-  EXPECT_TRUE(GetCanOriginAssertAppIdResult(origin1, same_etld_plus_one_appid));
+  ASSERT_TRUE(GetCanOriginAssertAppIdResult(origin1, same_etld_plus_one_appid,
+                                            &result));
+  EXPECT_TRUE(result);
   std::string different_etld_plus_one_appid("https://www.different.com/appId");
-  EXPECT_FALSE(GetCanOriginAssertAppIdResult(origin1,
-                                             different_etld_plus_one_appid));
+  ASSERT_TRUE(GetCanOriginAssertAppIdResult(
+      origin1, different_etld_plus_one_appid, &result));
+  EXPECT_FALSE(result);
 
   // For legacy purposes, google.com is allowed to use certain appIds hosted at
   // gstatic.com.
   // TODO(juanlang): remove once the legacy constraints are removed.
   std::string google_origin("https://accounts.google.com");
   std::string gstatic_appid("https://www.gstatic.com/securitykey/origins.json");
-  EXPECT_TRUE(GetCanOriginAssertAppIdResult(google_origin, gstatic_appid));
+  ASSERT_TRUE(
+      GetCanOriginAssertAppIdResult(google_origin, gstatic_appid, &result));
+  EXPECT_TRUE(result);
   // Not all gstatic urls are allowed, just those specifically whitelisted.
   std::string gstatic_otherurl("https://www.gstatic.com/foobar");
-  EXPECT_FALSE(GetCanOriginAssertAppIdResult(google_origin, gstatic_otherurl));
+  ASSERT_TRUE(
+      GetCanOriginAssertAppIdResult(google_origin, gstatic_otherurl, &result));
+  EXPECT_FALSE(result);
+}
+
+TEST_F(CryptoTokenPrivateApiTest, IsAppIdHashInEnterpriseContext) {
+  const std::string example_com("https://example.com/");
+  const std::string example_com_hash(SHA256HashString(example_com));
+  const std::string rp_id_hash(SHA256HashString("example.com"));
+  const std::string foo_com_hash(SHA256HashString("https://foo.com/"));
+
+  bool result;
+  ASSERT_TRUE(GetAppIdHashInEnterpriseContext(example_com_hash, &result));
+  EXPECT_FALSE(result);
+  ASSERT_TRUE(GetAppIdHashInEnterpriseContext(foo_com_hash, &result));
+  EXPECT_FALSE(result);
+  ASSERT_TRUE(GetAppIdHashInEnterpriseContext(rp_id_hash, &result));
+  EXPECT_FALSE(result);
+
+  base::Value::ListStorage permitted_list;
+  permitted_list.emplace_back(example_com);
+  profile()->GetPrefs()->Set(prefs::kSecurityKeyPermitAttestation,
+                             base::Value(permitted_list));
+
+  ASSERT_TRUE(GetAppIdHashInEnterpriseContext(example_com_hash, &result));
+  EXPECT_TRUE(result);
+  ASSERT_TRUE(GetAppIdHashInEnterpriseContext(foo_com_hash, &result));
+  EXPECT_FALSE(result);
+  ASSERT_TRUE(GetAppIdHashInEnterpriseContext(rp_id_hash, &result));
+  EXPECT_FALSE(result);
 }
 
 }  // namespace
diff --git a/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc b/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
index 7a0c2a93..8ec3206c 100644
--- a/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
+++ b/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
@@ -281,7 +281,7 @@
     device_test_->SetDeviceProperty(kCellularDevicePath, shill::kMinProperty,
                                     base::Value("test_min"));
     device_test_->SetDeviceProperty(kCellularDevicePath,
-                                    shill::kModelIDProperty,
+                                    shill::kModelIdProperty,
                                     base::Value("test_model_id"));
     device_test_->SetSimLocked(kCellularDevicePath, false);
 
diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc
index aa40a61..e533e435 100644
--- a/chrome/browser/profiles/profile_impl.cc
+++ b/chrome/browser/profiles/profile_impl.cc
@@ -525,7 +525,7 @@
     // (successfully or not).  Note that we can use base::Unretained
     // because the PrefService is owned by this class and lives on
     // the same thread.
-    prefs_->AddPrefInitObserver(base::Bind(
+    prefs_->AddPrefInitObserver(base::BindOnce(
         &ProfileImpl::OnPrefsLoaded, base::Unretained(this), create_mode));
   } else {
     // Prefs were loaded synchronously so we can continue directly.
diff --git a/chrome/browser/resources/cryptotoken/usbenrollhandler.js b/chrome/browser/resources/cryptotoken/usbenrollhandler.js
index 2d53b07..3b2f2c6 100644
--- a/chrome/browser/resources/cryptotoken/usbenrollhandler.js
+++ b/chrome/browser/resources/cryptotoken/usbenrollhandler.js
@@ -207,12 +207,40 @@
     this.removeWrongVersionGnubby_(gnubby);
     return;
   }
+
+  var appIdHashBase64 = challenge['appIdHash'];
+  if (DEVICE_FACTORY_REGISTRY.getIndividualAttestation()
+          .requestIndividualAttestation(appIdHashBase64)) {
+    this.tryEnrollComplete_(gnubby, version, true);
+    return;
+  }
+
+  if (!chrome.cryptotokenPrivate) {
+    this.tryEnrollComplete_(gnubby, version, false);
+    return;
+  }
+
+  chrome.cryptotokenPrivate.isAppIdHashInEnterpriseContext(
+      decodeWebSafeBase64ToArray(appIdHashBase64),
+      this.tryEnrollComplete_.bind(this, gnubby, version));
+};
+
+/**
+ * Attempts enrolling a particular gnubby with a challenge of the appropriate
+ * version.
+ * @param {Gnubby} gnubby Gnubby instance
+ * @param {string} version Protocol version
+ * @param {boolean} individualAttest whether to send the individual-attestation
+ *     signal to the token.
+ * @private
+ */
+UsbEnrollHandler.prototype.tryEnrollComplete_ = function(
+    gnubby, version, individualAttest) {
+  var challenge = this.getChallengeOfVersion_(version);
   var challengeValue = B64_decode(challenge['challengeHash']);
-  var appIdHash = challenge['appIdHash'];
-  var individualAttest = DEVICE_FACTORY_REGISTRY.getIndividualAttestation()
-                             .requestIndividualAttestation(appIdHash);
+
   gnubby.enroll(
-      challengeValue, B64_decode(appIdHash),
+      challengeValue, B64_decode(challenge['appIdHash']),
       this.enrollCallback_.bind(this, gnubby, version), individualAttest);
 };
 
diff --git a/chrome/browser/resources/md_extensions/error_page.html b/chrome/browser/resources/md_extensions/error_page.html
index d574676..847dc02 100644
--- a/chrome/browser/resources/md_extensions/error_page.html
+++ b/chrome/browser/resources/md_extensions/error_page.html
@@ -103,35 +103,35 @@
         word-break: break-all;
       }
 
-      #devtools-controls {
+      .devtools-controls {
         padding: 0 var(--cr-section-padding);
       }
 
-      #stack-trace-heading {
+      .details-heading {
         @apply(--cr-title-text);
         align-items: center;
         display: flex;
         height: var(--cr-section-min-height);
       }
 
-      #stack-trace-container {
+      .stack-trace-container {
         list-style: none;
         margin-top: 0;
         padding: 0;
       }
 
-      #stack-trace-container li {
+      .stack-trace-container li {
         cursor: pointer;
         font-family: monospace;
         padding: 4px;
       }
 
-      #stack-trace-container li.selected,
-      #stack-trace-container li:hover {
+      .stack-trace-container li.selected,
+      .stack-trace-container li:hover {
         background: var(--google-blue-100);
       }
 
-      #dev-tool-button {
+      .dev-tool-button {
         margin-bottom: 20px;
         max-width: 300px;
       }
@@ -207,11 +207,18 @@
                 </div>
                 <template is="dom-if" if="[[computeIsRuntimeError_(item)]]">
                   <iron-collapse opened="[[isOpened_(index, selectedEntry_)]]">
-                    <div id="devtools-controls">
-                      <div id="stack-trace-heading">
+                    <div class="devtools-controls">
+                      <div class="details-heading">
+                        $i18n{errorContext}
+                      </div>
+                      <span class="context-url">
+                        [[getContextUrl_(
+                            item, '$i18nPolymer{errorContextUnknown}')]]
+                      </span>
+                      <div class="details-heading">
                         $i18n{stackTrace}
                       </div>
-                      <ul id="stack-trace-container">
+                      <ul class="stack-trace-container">
                         <template is="dom-repeat" items="[[item.stackTrace]]">
                           <li on-tap="onStackFrameTap_"
                               hidden="[[!shouldDisplayFrame_(item.url)]]"
@@ -221,7 +228,7 @@
                           </li>
                         </template>
                       </ul>
-                      <paper-button id="dev-tool-button" class="action-button"
+                      <paper-button class="devtool-button action-button"
                           disabled="[[!item.canInspect]]"
                           on-tap="onDevToolButtonTap_">
                         $i18n{openInDevtool}
diff --git a/chrome/browser/resources/md_extensions/error_page.js b/chrome/browser/resources/md_extensions/error_page.js
index 1b9ec7b..f11cb5d 100644
--- a/chrome/browser/resources/md_extensions/error_page.js
+++ b/chrome/browser/resources/md_extensions/error_page.js
@@ -91,6 +91,17 @@
     },
 
     /**
+     * @param {!ManifestError|!RuntimeError} error
+     * @param {string} unknown
+     * @return {string}
+     * @private
+     */
+    getContextUrl_: function(error, unknown) {
+      return error.contextUrl ? getRelativeUrl(error.contextUrl, error) :
+                                unknown;
+    },
+
+    /**
      * Watches for changes to |data| in order to fetch the corresponding
      * file source.
      * @private
diff --git a/chrome/browser/resources/settings/device_page/display.html b/chrome/browser/resources/settings/device_page/display.html
index a97f335..7423e99 100644
--- a/chrome/browser/resources/settings/device_page/display.html
+++ b/chrome/browser/resources/settings/device_page/display.html
@@ -227,8 +227,14 @@
         </div>
         <!-- Schedule settings -->
         <div class="settings-box indented">
-          <div id="nightLightScheduleLabel" class="start text-area">
-            $i18n{displayNightLightScheduleLabel}
+          <div class="start text-area">
+            <div id="nightLightScheduleLabel" class="label">
+              $i18n{displayNightLightScheduleLabel}
+            </div>
+            <div id="nightLightScheduleSubLabel" class="secondary label"
+                hidden$="[[!nightLightScheduleSubLabel_]]">
+              [[nightLightScheduleSubLabel_]]
+            </div>
           </div>
           <settings-dropdown-menu
               id="nightLightScheduleTypeDropDown"
diff --git a/chrome/browser/resources/settings/device_page/display.js b/chrome/browser/resources/settings/device_page/display.js
index 0d18d5e..7b863b9 100644
--- a/chrome/browser/resources/settings/device_page/display.js
+++ b/chrome/browser/resources/settings/device_page/display.js
@@ -143,10 +143,14 @@
       type: Boolean,
       value: false,
     },
+
+    /** @private */
+    nightLightScheduleSubLabel_: String,
   },
 
   observers: [
-    'onScheduleTypeChanged_(prefs.ash.night_light.schedule_type.*)',
+    'updateNightLightScheduleSettings_(prefs.ash.night_light.schedule_type.*,' +
+        ' prefs.ash.night_light.enabled.*)',
   ],
 
   /** @private {number} Selected mode index received from chrome. */
@@ -608,10 +612,24 @@
     }
   },
 
-  /** @private */
-  onScheduleTypeChanged_: function() {
+  /**
+   * Invoked when the status of Night Light or its schedule type are changed, in
+   * order to update the schedule settings, such as whether to show the custom
+   * schedule slider, and the schedule sub label.
+   * @private
+   */
+  updateNightLightScheduleSettings_: function() {
+    var scheduleType = this.getPref('ash.night_light.schedule_type').value;
     this.shouldOpenCustomScheduleCollapse_ =
-        this.getPref('ash.night_light.schedule_type').value ==
-        NightLightScheduleType.CUSTOM;
+        scheduleType == NightLightScheduleType.CUSTOM;
+
+    if (scheduleType == NightLightScheduleType.SUNSET_TO_SUNRISE) {
+      var nightLightStatus = this.getPref('ash.night_light.enabled').value;
+      this.nightLightScheduleSubLabel_ = nightLightStatus ?
+          this.i18n('displayNightLightOffAtSunrise') :
+          this.i18n('displayNightLightOnAtSunset');
+    } else {
+      this.nightLightScheduleSubLabel_ = '';
+    }
   },
 });
diff --git a/chrome/browser/resources/settings/device_page/night_light_slider.html b/chrome/browser/resources/settings/device_page/night_light_slider.html
index 641129f..d6dfdfa 100644
--- a/chrome/browser/resources/settings/device_page/night_light_slider.html
+++ b/chrome/browser/resources/settings/device_page/night_light_slider.html
@@ -36,6 +36,7 @@
         margin-top: -15px;
         position: absolute;
         width: 32px;
+        z-index: 3;
       }
 
       .knob:focus {
@@ -47,8 +48,7 @@
         border-radius: 6px;
         height: 12px;
         left: 0;
-        margin-left: 10px;
-        margin-top: 10px;
+        margin: 10px;
         position: absolute;
         width: 12px;
         z-index: 3;
diff --git a/chrome/browser/resources/settings/device_page/night_light_slider.js b/chrome/browser/resources/settings/device_page/night_light_slider.js
index 239a9f42..c47496e 100644
--- a/chrome/browser/resources/settings/device_page/night_light_slider.js
+++ b/chrome/browser/resources/settings/device_page/night_light_slider.js
@@ -83,6 +83,13 @@
 
     this.isRTL_ = window.getComputedStyle(this).direction == 'rtl';
 
+    this.$.sliderContainer.addEventListener('contextmenu', function(e) {
+      // Prevent the context menu from interfering with dragging the knobs using
+      // touch.
+      e.preventDefault();
+      return false;
+    });
+
     this.async(function() {
       // This is needed to make sure that the positions of the knobs and their
       // label bubbles are correctly updated when the display settings page is
diff --git a/chrome/browser/sessions/better_session_restore_browsertest.cc b/chrome/browser/sessions/better_session_restore_browsertest.cc
index 8934004..24946e0 100644
--- a/chrome/browser/sessions/better_session_restore_browsertest.cc
+++ b/chrome/browser/sessions/better_session_restore_browsertest.cc
@@ -168,20 +168,18 @@
         title_error_write_failed_(base::ASCIIToUTF16("ERROR_WRITE_FAILED")),
         title_error_empty_(base::ASCIIToUTF16("ERROR_EMPTY")) {
     // Set up the URL request filtering.
-    std::vector<std::string> test_files;
-    base::FilePath test_file_dir;
-    test_files.push_back("common.js");
-    test_files.push_back("cookies.html");
-    test_files.push_back("local_storage.html");
-    test_files.push_back("post.html");
-    test_files.push_back("post_with_password.html");
-    test_files.push_back("session_cookies.html");
-    test_files.push_back("session_storage.html");
-    test_files.push_back("subdomain_cookies.html");
+    test_files_.push_back("common.js");
+    test_files_.push_back("cookies.html");
+    test_files_.push_back("local_storage.html");
+    test_files_.push_back("post.html");
+    test_files_.push_back("post_with_password.html");
+    test_files_.push_back("session_cookies.html");
+    test_files_.push_back("session_storage.html");
+    test_files_.push_back("subdomain_cookies.html");
 
-    CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &test_file_dir));
-    test_file_dir =
-        test_file_dir.AppendASCII("chrome/test/data").AppendASCII(test_path_);
+    CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &test_file_dir_));
+    test_file_dir_ =
+        test_file_dir_.AppendASCII("chrome/test/data").AppendASCII(test_path_);
 
     // We are adding a URLLoaderInterceptor here, instead of in
     // SetUpOnMainThread(), because during a session restore the restored tab
@@ -193,11 +191,11 @@
               [&](content::URLLoaderInterceptor::RequestParams* params) {
                 std::string path = params->url_request.url.path();
                 std::string path_prefix = std::string("/") + test_path_;
-                for (auto& it : test_files) {
+                for (auto& it : test_files_) {
                   std::string file = path_prefix + it;
                   if (path == file) {
                     base::ScopedAllowBlockingForTesting allow_io;
-                    base::FilePath file_path = test_file_dir.AppendASCII(it);
+                    base::FilePath file_path = test_file_dir_.AppendASCII(it);
                     std::string contents;
                     CHECK(base::ReadFileToString(file_path, &contents));
 
@@ -234,9 +232,9 @@
       return;
     }
 
-    for (std::vector<std::string>::const_iterator it = test_files.begin();
-         it != test_files.end(); ++it) {
-      base::FilePath path = test_file_dir.AppendASCII(*it);
+    for (std::vector<std::string>::const_iterator it = test_files_.begin();
+         it != test_files_.end(); ++it) {
+      base::FilePath path = test_file_dir_.AppendASCII(*it);
       std::string contents;
       CHECK(base::ReadFileToString(path, &contents));
       net::URLRequestFilter::GetInstance()->AddUrlInterceptor(
@@ -429,6 +427,8 @@
  private:
   std::string last_upload_bytes_;
   const std::string fake_server_address_;
+  std::vector<std::string> test_files_;
+  base::FilePath test_file_dir_;
   const std::string test_path_;
   const base::string16 title_pass_;
   const base::string16 title_storing_;
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 9b1303d..b6c9ad4a 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2518,6 +2518,8 @@
       "views/tabs/window_finder_win.cc",
       "views/try_chrome_dialog_win/arrow_border.cc",
       "views/try_chrome_dialog_win/arrow_border.h",
+      "views/try_chrome_dialog_win/button_layout.cc",
+      "views/try_chrome_dialog_win/button_layout.h",
       "views/try_chrome_dialog_win/try_chrome_dialog.cc",
       "views/try_chrome_dialog_win/try_chrome_dialog.h",
       "views/uninstall_view.cc",
diff --git a/chrome/browser/ui/views/try_chrome_dialog_win/button_layout.cc b/chrome/browser/ui/views/try_chrome_dialog_win/button_layout.cc
new file mode 100644
index 0000000..febe172
--- /dev/null
+++ b/chrome/browser/ui/views/try_chrome_dialog_win/button_layout.cc
@@ -0,0 +1,100 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/try_chrome_dialog_win/button_layout.h"
+
+#include "base/logging.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/views/view.h"
+
+// static
+ButtonLayout* ButtonLayout::CreateAndInstall(views::View* view,
+                                             int view_width) {
+  ButtonLayout* layout = new ButtonLayout(view_width);
+  view->SetLayoutManager(layout);
+  return layout;
+}
+
+ButtonLayout::~ButtonLayout() = default;
+
+void ButtonLayout::Layout(views::View* host) {
+  const gfx::Size& host_size = host->bounds().size();
+
+  // Layout of the host within its parent must size it based on the |view_width|
+  // given to this layout manager at creation. If it happens to be different,
+  // the buttons will be sized and positioned based on the host's true size.
+  // This will result in either stretching or compressing the buttons, and may
+  // lead to elision of their text.
+  DCHECK_EQ(host_size.width(), view_width_);
+
+  const gfx::Size max_child_size = GetMaxChildPreferredSize(host);
+  const bool use_wide_buttons =
+      UseWideButtons(host_size.width(), max_child_size.width());
+  const bool has_two_buttons = HasTwoButtons(host);
+
+  // The buttons are all equal-sized.
+  gfx::Size button_size(0, max_child_size.height());
+  if (use_wide_buttons)
+    button_size.set_width(host_size.width());
+  else
+    button_size.set_width((host_size.width() - kPaddingBetweenButtons) / 2);
+
+  if (!use_wide_buttons) {
+    // The offset of the right-side narrow button.
+    int right_x = button_size.width() + kPaddingBetweenButtons;
+    if (has_two_buttons) {
+      host->child_at(0)->SetBoundsRect({{0, 0}, button_size});
+      host->child_at(1)->SetBoundsRect({{right_x, 0}, button_size});
+    } else {
+      // If there is only one narrow button, position it on the right.
+      host->child_at(0)->SetBoundsRect({{right_x, 0}, button_size});
+    }
+  } else {
+    host->child_at(0)->SetBoundsRect({{0, 0}, button_size});
+    if (has_two_buttons) {
+      int bottom_y = button_size.height() + kPaddingBetweenButtons;
+      host->child_at(1)->SetBoundsRect({{0, bottom_y}, button_size});
+    }
+  }
+}
+
+gfx::Size ButtonLayout::GetPreferredSize(const views::View* host) const {
+  const gfx::Size max_child_size = GetMaxChildPreferredSize(host);
+
+  // |view_width_| is a hard limit; the buttons will be sized and positioned to
+  // fill it.
+  if (HasTwoButtons(host) &&
+      UseWideButtons(view_width_, max_child_size.width())) {
+    // Two rows of equal height with padding between them.
+    return {view_width_, max_child_size.height() * 2 + kPaddingBetweenButtons};
+  }
+
+  // Only one button or the widest of two is sufficiently narrow, so only one
+  // row is needed.
+  return {view_width_, max_child_size.height()};
+}
+
+ButtonLayout::ButtonLayout(int view_width) : view_width_(view_width) {}
+
+// static
+bool ButtonLayout::HasTwoButtons(const views::View* host) {
+  const int child_count = host->child_count();
+  DCHECK_GE(child_count, 1);
+  DCHECK_LE(child_count, 2);
+  return child_count == 2;
+}
+
+// static
+gfx::Size ButtonLayout::GetMaxChildPreferredSize(const views::View* host) {
+  const bool has_two_buttons = HasTwoButtons(host);
+  gfx::Size max_child_size(host->child_at(0)->GetPreferredSize());
+  if (has_two_buttons)
+    max_child_size.SetToMax(host->child_at(1)->GetPreferredSize());
+  return max_child_size;
+}
+
+// static
+bool ButtonLayout::UseWideButtons(int host_width, int max_child_width) {
+  return max_child_width > (host_width - kPaddingBetweenButtons) / 2;
+}
diff --git a/chrome/browser/ui/views/try_chrome_dialog_win/button_layout.h b/chrome/browser/ui/views/try_chrome_dialog_win/button_layout.h
new file mode 100644
index 0000000..cd52355
--- /dev/null
+++ b/chrome/browser/ui/views/try_chrome_dialog_win/button_layout.h
@@ -0,0 +1,71 @@
+// 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_UI_VIEWS_TRY_CHROME_DIALOG_WIN_BUTTON_LAYOUT_H_
+#define CHROME_BROWSER_UI_VIEWS_TRY_CHROME_DIALOG_WIN_BUTTON_LAYOUT_H_
+
+#include "base/macros.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/views/layout/layout_manager.h"
+
+namespace views {
+class View;
+}
+
+// A LayoutManager that positions the TryChromeDialog's action button(s) based
+// on the number and their width requirements. The possible layouts are:
+//
+// One narrow button:  |           [button 1]|
+//
+// Two narrow buttons: |[button 1] [button 2]|
+//
+// One wide button:    |[      button 1    ]|
+//
+// Two wide buttons:   |[      button 1    ]|
+//                     |[      button 2    ]|
+//
+// An instance of this layout manager is expected to be installed on a View to
+// which only the one or two action buttons are added.
+class ButtonLayout : public views::LayoutManager {
+ public:
+  // Returns a new instance that has been made the manager of |view|. The
+  // dialog's one or two action button(s) must be the only children of |view|.
+  // |view_width| is the desired width of the view, which controls the width of
+  // the individual buttons as above. The layout manager of |view|'s parent must
+  // respect this width (by, for example, using SizeType::USE_PREF for the
+  // hosting column's size_type if it uses GridLayout).
+  static ButtonLayout* CreateAndInstall(views::View* view, int view_width);
+
+  ~ButtonLayout() override;
+
+ protected:
+  // views::LayoutManager:
+  void Layout(views::View* host) override;
+  gfx::Size GetPreferredSize(const views::View* host) const override;
+
+ private:
+  friend class ButtonLayoutTest;
+
+  // The horizontal or vertical space between two buttons.
+  enum { kPaddingBetweenButtons = 4 };
+
+  explicit ButtonLayout(int view_width);
+
+  // Returns true if |host| contains two buttons, or false if it contains only
+  // one.
+  static bool HasTwoButtons(const views::View* host);
+
+  // Returns the preferred size of the largest child of |host|.
+  static gfx::Size GetMaxChildPreferredSize(const views::View* host);
+
+  // Returns true if wide buttons must be used based on the given widths.
+  static bool UseWideButtons(int host_width, int max_child_width);
+
+  // The desired width of the view.
+  const int view_width_;
+
+  DISALLOW_COPY_AND_ASSIGN(ButtonLayout);
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_TRY_CHROME_DIALOG_WIN_BUTTON_LAYOUT_H_
diff --git a/chrome/browser/ui/views/try_chrome_dialog_win/button_layout_unittest.cc b/chrome/browser/ui/views/try_chrome_dialog_win/button_layout_unittest.cc
new file mode 100644
index 0000000..fd851bb
--- /dev/null
+++ b/chrome/browser/ui/views/try_chrome_dialog_win/button_layout_unittest.cc
@@ -0,0 +1,198 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/try_chrome_dialog_win/button_layout.h"
+
+#include <algorithm>
+#include <memory>
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/views/view.h"
+
+class ButtonLayoutTest
+    : public ::testing::TestWithParam<::testing::tuple<int, int>> {
+ private:
+  enum {
+    // The width of an imaginary host view in the test.
+    kFixedHostWidth = 100,
+  };
+
+ public:
+  // Various button widths to be tested.
+  enum {
+    // Magic width meaning no button at all.
+    kNoButton = 0,
+
+    // Some small "narrow" button that fits within half of the test's host.
+    kNarrowButtonMin = 8,
+
+    // The largest "narrow" button that could fit within the test's host.
+    kNarrowButtonMax =
+        (kFixedHostWidth - ButtonLayout::kPaddingBetweenButtons) / 2,
+
+    // Some mid-sized "narrow" button that could fit within the test's host.
+    kNarrowButtonMid = (kNarrowButtonMin + kNarrowButtonMax) / 2,
+
+    // The least wide "wide" button that could fit within the test's host.
+    kWideButtonMin = kNarrowButtonMax + 1,
+
+    // The largest "wide" button that could fit within the test's host.
+    kWideButtonMax = kFixedHostWidth,
+
+    // Some mid-sized "wide" button that could fit within the test's host.
+    kWideButtonMid = (kWideButtonMin + kWideButtonMax) / 2,
+
+    // A button that is too big to fit within the host.
+    kSuperSizedButton = kWideButtonMax + 1,
+  };
+
+ protected:
+  ButtonLayoutTest()
+      : layout_(ButtonLayout::CreateAndInstall(&host_, kFixedHostWidth)),
+        button_1_width_(::testing::get<0>(GetParam())),
+        button_2_width_(::testing::get<1>(GetParam())) {}
+
+  void SetUp() override {
+    ASSERT_NE(0, button_1_width_) << "Button 1 must always be present.";
+  }
+
+  views::View* host() { return &host_; }
+  views::LayoutManager* layout() { return layout_; }
+  bool has_two_buttons() const { return button_2_width_; }
+
+  // Adds one or two child views of widths specified by the test parameters.
+  void AddChildViews() {
+    auto view = std::make_unique<views::View>();
+    view->SetPreferredSize({button_1_width_, kButtonHeight});
+    host()->AddChildView(view.release());
+
+    if (has_two_buttons()) {
+      view = std::make_unique<views::View>();
+      view->SetPreferredSize({button_2_width_, kButtonHeight});
+      host()->AddChildView(view.release());
+    }
+  }
+
+  // Returns the (fixed) width of the host.
+  int GetExpectedWidth() const { return kFixedHostWidth; }
+
+  // Returns the height of the host, which is dependent on the number and widths
+  // of the children.
+  int GetExpectedHeight() const {
+    if (!has_two_buttons())
+      return kButtonHeight;
+    if (button_1_width_ <= kNarrowButtonMax &&
+        button_2_width_ <= kNarrowButtonMax) {
+      return kButtonHeight;
+    }
+    return 2 * kButtonHeight + ButtonLayout::kPaddingBetweenButtons;
+  }
+
+  // Returns the expected bounding rectangle for |child_number|.
+  gfx::Rect GetExpectedButtonBounds(int child_number) const {
+    gfx::Rect bounds;
+
+    // Width is determined by the max of the two buttons being bigger than
+    // the max narrow button.
+    if (std::max(button_1_width_, button_2_width_) > kNarrowButtonMax)
+      bounds.set_width(kWideButtonMax);
+    else
+      bounds.set_width(kNarrowButtonMax);
+
+    // All buttons have the same height.
+    bounds.set_height(kButtonHeight);
+
+    // Position is based on which button we're talking about.
+    switch (child_number) {
+      case 1:
+        // Offset button 1 if there's only one button and it's narrow.
+        if (!has_two_buttons() && bounds.width() == kNarrowButtonMax)
+          bounds.set_x(kRightButtonXOffset);
+        break;
+      case 2:
+        // Offset button 2 horizontally if the buttons are narrow; vertically,
+        // otherwise.
+        if (bounds.width() == kNarrowButtonMax)
+          bounds.set_x(kRightButtonXOffset);
+        else
+          bounds.set_y(kBottomButtonYOffset);
+        break;
+      default:
+        ADD_FAILURE() << "child_number out of bounds";
+        return gfx::Rect();
+    }
+
+    return bounds;
+  }
+
+  // Expects that the bounds of |view| are equal to |bounds|.
+  void ExpectViewBoundsEquals(const views::View* view,
+                              const gfx::Rect& bounds) {
+    const gfx::Rect& child_bounds = view->bounds();
+    EXPECT_EQ(child_bounds.x(), bounds.x());
+    EXPECT_EQ(child_bounds.y(), bounds.y());
+    EXPECT_EQ(child_bounds.width(), bounds.width());
+    EXPECT_EQ(child_bounds.height(), bounds.height());
+  }
+
+ private:
+  enum {
+    // The height of an imaginary button in the test.
+    kButtonHeight = 20,
+
+    // The horizontal offset of the right-hand button for narrow button layouts.
+    kRightButtonXOffset =
+        kNarrowButtonMax + ButtonLayout::kPaddingBetweenButtons,
+
+    // The vertical offset of the lower button for wide button layouts.
+    kBottomButtonYOffset = kButtonHeight + ButtonLayout::kPaddingBetweenButtons,
+  };
+
+  views::View host_;
+  ButtonLayout* const layout_;  // Owned by |host_|.
+  const int button_1_width_;
+  const int button_2_width_;
+
+  DISALLOW_COPY_AND_ASSIGN(ButtonLayoutTest);
+};
+
+TEST_P(ButtonLayoutTest, GetPreferredSize) {
+  AddChildViews();
+  const gfx::Size preferred_size = layout()->GetPreferredSize(host());
+  EXPECT_EQ(preferred_size.width(), GetExpectedWidth());
+  EXPECT_EQ(preferred_size.height(), GetExpectedHeight());
+}
+
+TEST_P(ButtonLayoutTest, Layout) {
+  AddChildViews();
+  host()->SetBounds(0, 0, GetExpectedWidth(), GetExpectedHeight());
+  layout()->Layout(host());
+
+  ExpectViewBoundsEquals(host()->child_at(0), GetExpectedButtonBounds(1));
+  if (has_two_buttons())
+    ExpectViewBoundsEquals(host()->child_at(1), GetExpectedButtonBounds(2));
+}
+
+// Test all combinations of one or two buttons at many sizes.
+INSTANTIATE_TEST_CASE_P(
+    ,
+    ButtonLayoutTest,
+    ::testing::Combine(::testing::Values(ButtonLayoutTest::kNarrowButtonMin,
+                                         ButtonLayoutTest::kNarrowButtonMid,
+                                         ButtonLayoutTest::kNarrowButtonMax,
+                                         ButtonLayoutTest::kWideButtonMin,
+                                         ButtonLayoutTest::kWideButtonMid,
+                                         ButtonLayoutTest::kWideButtonMax,
+                                         ButtonLayoutTest::kSuperSizedButton),
+                       ::testing::Values(ButtonLayoutTest::kNoButton,
+                                         ButtonLayoutTest::kNarrowButtonMin,
+                                         ButtonLayoutTest::kNarrowButtonMid,
+                                         ButtonLayoutTest::kNarrowButtonMax,
+                                         ButtonLayoutTest::kWideButtonMin,
+                                         ButtonLayoutTest::kWideButtonMid,
+                                         ButtonLayoutTest::kWideButtonMax,
+                                         ButtonLayoutTest::kSuperSizedButton)));
diff --git a/chrome/browser/ui/views/try_chrome_dialog_win/try_chrome_dialog.cc b/chrome/browser/ui/views/try_chrome_dialog_win/try_chrome_dialog.cc
index ae1d4a92..8069db6 100644
--- a/chrome/browser/ui/views/try_chrome_dialog_win/try_chrome_dialog.cc
+++ b/chrome/browser/ui/views/try_chrome_dialog_win/try_chrome_dialog.cc
@@ -17,6 +17,7 @@
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/ui/views/harmony/chrome_typography.h"
 #include "chrome/browser/ui/views/try_chrome_dialog_win/arrow_border.h"
+#include "chrome/browser/ui/views/try_chrome_dialog_win/button_layout.h"
 #include "chrome/browser/win/taskbar_icon_finder.h"
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
@@ -1036,58 +1037,53 @@
   // Padding around the left, top, and right of the logo.
   static constexpr int kLogoPadding = 10;
   static constexpr int kCloseButtonWidth = 24;
+  static constexpr int kCloseButtonTopPadding = 6;
   static constexpr int kCloseButtonRightPadding = 5;
-  static constexpr int kSpacingAfterHeadingHorizontal =
-      40 - kCloseButtonWidth - kCloseButtonRightPadding;
+  static constexpr int kSpacingAfterHeadingHorizontal = 40;
+  static constexpr int kSpacingHeadingToClose = kSpacingAfterHeadingHorizontal -
+                                                kCloseButtonWidth -
+                                                kCloseButtonRightPadding;
 
   // Padding around all sides of the text buttons (but not between them).
   static constexpr int kTextButtonPadding = 12;
-  static constexpr int kPaddingBetweenButtons = 4;
 
-  // First row: [pad][logo][pad][text][pad][close button].
+  // First two rows: [pad][logo][pad][text][pad][close button].
+  // Only the close button is in the first row, spanning both. The logo and main
+  // header are in the second row.
+  const int kLabelWidth = kToastWidth - kLogoPadding - logo_size.width() -
+                          kLogoPadding - kSpacingAfterHeadingHorizontal;
   columns = layout->AddColumnSet(0);
   columns->AddPaddingColumn(0, kLogoPadding - kBorderThickness);
   columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING, 0,
                      views::GridLayout::FIXED, logo_size.width(),
                      logo_size.height());
   columns->AddPaddingColumn(0, kLogoPadding);
-  columns->AddColumn(views::GridLayout::FILL, views::GridLayout::LEADING, 1,
-                     views::GridLayout::USE_PREF, 0, 0);
-  columns->AddPaddingColumn(0, kSpacingAfterHeadingHorizontal);
-  columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL, 0,
+  columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
+                     views::GridLayout::FIXED, kLabelWidth, 0);
+  columns->AddPaddingColumn(0, kSpacingHeadingToClose);
+  columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING, 0,
                      views::GridLayout::USE_PREF, 0, 0);
   columns->AddPaddingColumn(0, kCloseButtonRightPadding - kBorderThickness);
-  const int logo_padding = logo_size.width() + kLogoPadding;
 
-  // Optional second row: [pad][text].
+  // Optional third row: [pad][text].
+  const int logo_padding = logo_size.width() + kLogoPadding;
   columns = layout->AddColumnSet(1);
   columns->AddPaddingColumn(0, kLogoPadding - kBorderThickness + logo_padding);
-  columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 1,
-                     views::GridLayout::USE_PREF, 0, 0);
+  columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
+                     views::GridLayout::FIXED, kLabelWidth, 0);
 
-  // Third row: [pad][optional button][pad][button].
+  // Fourth row: [pad][buttons][pad].
   columns = layout->AddColumnSet(2);
   columns->AddPaddingColumn(0, kTextButtonPadding - kBorderThickness);
-  columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
-                     views::GridLayout::USE_PREF, 0, 0);
-  columns->AddPaddingColumn(0, kPaddingBetweenButtons);
   columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
                      views::GridLayout::USE_PREF, 0, 0);
   columns->AddPaddingColumn(0, kTextButtonPadding - kBorderThickness);
 
   // First row.
-  layout->StartRowWithPadding(0, 0, 0, kLogoPadding - kBorderThickness);
-  layout->AddView(logo.release());
-  // All variants have a main header.
-  auto header = base::MakeUnique<views::Label>(
-      l10n_util::GetStringUTF16(kExperiments[group_].heading_id),
-      CONTEXT_WINDOWS10_NATIVE);
-  header->SetBackgroundColor(kBackgroundColor);
-  header->SetEnabledColor(kHeaderColor);
-  header->SetMultiLine(true);
-  header->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-  layout->AddView(header.release());
-
+  layout->AddPaddingRow(0, kCloseButtonTopPadding - kBorderThickness);
+  layout->StartRow(0, 0, kLogoPadding - kCloseButtonTopPadding);
+  layout->SkipColumns(1);
+  layout->SkipColumns(1);
   // Close button if included in the variant.
   if (kExperiments[group_].close_style ==
           ExperimentVariations::CloseStyle::kCloseX ||
@@ -1100,13 +1096,27 @@
     close_button->set_tag(static_cast<int>(ButtonTag::CLOSE_BUTTON));
     close_button_ = close_button.get();
     DCHECK_EQ(close_button->GetPreferredSize().width(), kCloseButtonWidth);
-    layout->AddView(close_button.release());
+    layout->AddView(close_button.release(), 1, 2);
     close_button_->SetVisible(false);
   } else {
     layout->SkipColumns(1);
   }
 
-  // Second row: May have text or may be blank.
+  // Second row.
+  layout->StartRow(0, 0);
+  layout->AddView(logo.release());
+  // All variants have a main header.
+  auto header = base::MakeUnique<views::Label>(
+      l10n_util::GetStringUTF16(kExperiments[group_].heading_id),
+      CONTEXT_WINDOWS10_NATIVE);
+  header->SetBackgroundColor(kBackgroundColor);
+  header->SetEnabledColor(kHeaderColor);
+  header->SetMultiLine(true);
+  header->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+  layout->AddView(header.release());
+  layout->SkipColumns(1);
+
+  // Third row: May have text or may be blank.
   layout->StartRow(0, 1);
   const int body_string_id = kExperiments[group_].body_id;
   if (body_string_id) {
@@ -1114,36 +1124,47 @@
         l10n_util::GetStringUTF16(body_string_id), CONTEXT_WINDOWS10_NATIVE);
     body_text->SetBackgroundColor(kBackgroundColor);
     body_text->SetEnabledColor(kBodyColor);
+    body_text->SetMultiLine(true);
+    body_text->SetHorizontalAlignment(gfx::ALIGN_LEFT);
     layout->AddView(body_text.release());
   }
 
-  // Third row: one or two buttons depending on group.
-  layout->StartRowWithPadding(0, 2, 0, kTextButtonPadding);
-  bool has_no_thanks_button =
-      kExperiments[group_].close_style ==
-          ExperimentVariations::CloseStyle::kNoThanksButton ||
-      kExperiments[group_].close_style ==
-          ExperimentVariations::CloseStyle::kNoThanksButtonAndCloseX;
-  if (!has_no_thanks_button)
-    layout->SkipColumns(1);
+  // Fourth row: one or two buttons depending on group.
+  layout->AddPaddingRow(0, kTextButtonPadding);
+
+  static constexpr int kButtonsViewWidth =
+      kToastWidth - kTextButtonPadding - kTextButtonPadding;
+  auto buttons = std::make_unique<views::View>();
+  ButtonLayout::CreateAndInstall(buttons.get(), kButtonsViewWidth);
+
+  layout->StartRow(0, 2);
   auto accept_button = CreateWin10StyleButton(
       this, l10n_util::GetStringUTF16(IDS_WIN10_TOAST_OPEN_CHROME),
       TryChromeButtonType::OPEN_CHROME);
   accept_button->set_tag(static_cast<int>(ButtonTag::OK_BUTTON));
-  layout->AddView(accept_button.release());
+  buttons->AddChildView(accept_button.release());
 
-  if (has_no_thanks_button) {
+  if (kExperiments[group_].close_style ==
+          ExperimentVariations::CloseStyle::kNoThanksButton ||
+      kExperiments[group_].close_style ==
+          ExperimentVariations::CloseStyle::kNoThanksButtonAndCloseX) {
     auto no_thanks_button = CreateWin10StyleButton(
         this, l10n_util::GetStringUTF16(IDS_WIN10_TOAST_NO_THANKS),
         TryChromeButtonType::NO_THANKS);
     no_thanks_button->set_tag(static_cast<int>(ButtonTag::NO_THANKS_BUTTON));
-    layout->AddView(no_thanks_button.release());
+    buttons->AddChildView(no_thanks_button.release());
   }
 
+  layout->AddView(buttons.release());
+
   layout->AddPaddingRow(0, kTextButtonPadding - kBorderThickness);
 
-  const gfx::Size preferred = layout->GetPreferredSize(contents_view.get());
   popup_->SetContentsView(contents_view.release());
+
+  // Compute the preferred size after attaching the contents view to the popup,
+  // as doing such causes the theme to propagate through the view hierarchy.
+  // This propagation can cause views to change their size requirements.
+  const gfx::Size preferred = popup_->GetContentsView()->GetPreferredSize();
   popup_->SetBounds(context_->ComputePopupBounds(popup_, preferred));
   popup_->SetAlwaysOnTop(true);
 
diff --git a/chrome/browser/ui/webui/extensions/extensions_ui.cc b/chrome/browser/ui/webui/extensions/extensions_ui.cc
index 3953331..ba1d575 100644
--- a/chrome/browser/ui/webui/extensions/extensions_ui.cc
+++ b/chrome/browser/ui/webui/extensions/extensions_ui.cc
@@ -160,6 +160,9 @@
   source->AddLocalizedString("clearAll", IDS_MD_EXTENSIONS_ERROR_CLEAR_ALL);
   source->AddLocalizedString("anonymousFunction",
                              IDS_MD_EXTENSIONS_ERROR_ANONYMOUS_FUNCTION);
+  source->AddLocalizedString("errorContext", IDS_MD_EXTENSIONS_ERROR_CONTEXT);
+  source->AddLocalizedString("errorContextUnknown",
+                             IDS_MD_EXTENSIONS_ERROR_CONTEXT_UNKNOWN);
   source->AddLocalizedString("openInDevtool",
                              IDS_MD_EXTENSIONS_ERROR_LAUNCH_DEVTOOLS);
   source->AddLocalizedString("stackTrace", IDS_MD_EXTENSIONS_ERROR_STACK_TRACE);
diff --git a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
index f8ff39efd..8243e46 100644
--- a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
@@ -600,6 +600,10 @@
       {"displayMirror", IDS_SETTINGS_DISPLAY_MIRROR},
       {"displayMirrorDisplayName", IDS_SETTINGS_DISPLAY_MIRROR_DISPLAY_NAME},
       {"displayNightLightLabel", IDS_SETTINGS_DISPLAY_NIGHT_LIGHT_LABEL},
+      {"displayNightLightOnAtSunset",
+       IDS_SETTINGS_DISPLAY_NIGHT_LIGHT_ON_AT_SUNSET},
+      {"displayNightLightOffAtSunrise",
+       IDS_SETTINGS_DISPLAY_NIGHT_LIGHT_OFF_AT_SUNRISE},
       {"displayNightLightScheduleCustom",
        IDS_SETTINGS_DISPLAY_NIGHT_LIGHT_SCHEDULE_CUSTOM},
       {"displayNightLightScheduleLabel",
diff --git a/chrome/common/crash_keys.cc b/chrome/common/crash_keys.cc
index 9f8d130..a611c1c 100644
--- a/chrome/common/crash_keys.cc
+++ b/chrome/common/crash_keys.cc
@@ -47,12 +47,6 @@
   //     RegisterWebViewCrashKeys(),
   // chromecast/crash/cast_crash_keys.cc::RegisterCastCrashKeys().
   base::debug::CrashKey fixed_keys[] = {
-#if defined(OS_MACOSX) || defined(OS_WIN)
-    {kMetricsClientId, kSmallSize},
-#else
-    {kClientId, kSmallSize},
-#endif
-    {kChannel, kSmallSize},
     {kActiveURL, kLargeSize},
     {kNumVariations, kSmallSize},
     {kVariations, kHugeSize},
@@ -71,8 +65,6 @@
   std::vector<base::debug::CrashKey> keys(
       fixed_keys, fixed_keys + arraysize(fixed_keys));
 
-  crash_keys::GetCrashKeysForCommandLineSwitches(&keys);
-
   // Register the extension IDs.
   {
     static char formatted_keys[kExtensionIDMaxCount][sizeof(kExtensionID) + 1] =
diff --git a/chrome/common/crash_keys_unittest.cc b/chrome/common/crash_keys_unittest.cc
index ea57a03..5d88b0d 100644
--- a/chrome/common/crash_keys_unittest.cc
+++ b/chrome/common/crash_keys_unittest.cc
@@ -17,9 +17,12 @@
 #include "components/crash/core/common/crash_key.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+using crash_reporter::GetCrashKeyValue;
+
 class CrashKeysTest : public testing::Test {
  public:
   void SetUp() override {
+    crash_reporter::ResetCrashKeysForTesting();
     crash_reporter::InitializeCrashKeys();
     self_ = this;
     base::debug::SetCrashKeyReportingFunctions(
@@ -29,6 +32,7 @@
 
   void TearDown() override {
     base::debug::ResetCrashLoggingForTesting();
+    crash_reporter::ResetCrashKeysForTesting();
     self_ = NULL;
   }
 
@@ -137,9 +141,10 @@
 
   crash_keys::SetCrashKeysFromCommandLine(command_line);
 
-  EXPECT_EQ("--vv=1", GetKeyValue("switch-1"));
-  EXPECT_EQ("--vvv", GetKeyValue("switch-2"));
-  EXPECT_EQ("--enable-multi-profiles", GetKeyValue("switch-3"));
-  EXPECT_EQ("--device-management-url=https://foo/bar", GetKeyValue("switch-4"));
-  EXPECT_FALSE(HasCrashKey("switch-5"));
+  EXPECT_EQ("--vv=1", GetCrashKeyValue("switch-1"));
+  EXPECT_EQ("--vvv", GetCrashKeyValue("switch-2"));
+  EXPECT_EQ("--enable-multi-profiles", GetCrashKeyValue("switch-3"));
+  EXPECT_EQ("--device-management-url=https://foo/bar",
+            GetCrashKeyValue("switch-4"));
+  EXPECT_TRUE(GetCrashKeyValue("switch-5").empty());
 }
diff --git a/chrome/common/extensions/api/cryptotoken_private.idl b/chrome/common/extensions/api/cryptotoken_private.idl
index 8a091bb..23544d0 100644
--- a/chrome/common/extensions/api/cryptotoken_private.idl
+++ b/chrome/common/extensions/api/cryptotoken_private.idl
@@ -19,5 +19,13 @@
     static void canOriginAssertAppId(DOMString securityOrigin,
                                      DOMString appIdUrl,
                                      AppIdCallback callback);
+
+    // Checks whether the given appId is specified in the
+    // SecurityKeyPermitAttestation policy. This causes a signal to be sent to
+    // the token that informs it that an individually-identifying attestation
+    // certificate may be used. Without that signal, the token is required to
+    // use its batch attestation certificate.
+    static void isAppIdHashInEnterpriseContext(ArrayBuffer appIdHash,
+                                               AppIdCallback callback);
   };
 };
diff --git a/chrome/installer/setup/installer_crash_reporter_client.cc b/chrome/installer/setup/installer_crash_reporter_client.cc
index 791898e..5c4db7d 100644
--- a/chrome/installer/setup/installer_crash_reporter_client.cc
+++ b/chrome/installer/setup/installer_crash_reporter_client.cc
@@ -101,7 +101,7 @@
 }
 
 size_t InstallerCrashReporterClient::RegisterCrashKeys() {
-  return installer::RegisterCrashKeys();
+  return 0;
 }
 
 bool InstallerCrashReporterClient::IsRunningUnattended() {
diff --git a/chrome/installer/setup/installer_crash_reporting.cc b/chrome/installer/setup/installer_crash_reporting.cc
index 9ac63cb9..c8a6824 100644
--- a/chrome/installer/setup/installer_crash_reporting.cc
+++ b/chrome/installer/setup/installer_crash_reporting.cc
@@ -93,17 +93,6 @@
     crash_keys::SetMetricsClientIdFromGUID(client_info->client_id);
 }
 
-size_t RegisterCrashKeys() {
-  static constexpr base::debug::CrashKey kFixedKeys[] = {
-      {crash_keys::kMetricsClientId, crash_keys::kSmallSize},
-  };
-  std::vector<base::debug::CrashKey> keys(std::begin(kFixedKeys),
-                                          std::end(kFixedKeys));
-  crash_keys::GetCrashKeysForCommandLineSwitches(&keys);
-  return base::debug::InitCrashKeys(keys.data(), keys.size(),
-                                    crash_keys::kChunkMaxLength);
-}
-
 void SetInitialCrashKeys(const InstallerState& state) {
   using crash_reporter::CrashKeyString;
 
diff --git a/chrome/installer/setup/installer_crash_reporting.h b/chrome/installer/setup/installer_crash_reporting.h
index 6128b61a..34bbac1 100644
--- a/chrome/installer/setup/installer_crash_reporting.h
+++ b/chrome/installer/setup/installer_crash_reporting.h
@@ -19,9 +19,6 @@
 // Sets up the crash reporting system for the installer.
 void ConfigureCrashReporting(const InstallerState& installer_state);
 
-// Registers all crash keys used by the installer.
-size_t RegisterCrashKeys();
-
 // Sets all crash keys that are available during process startup. These do not
 // vary during execution so this function will not need to be called more than
 // once.
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 66c0f67..39d64e256 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -444,6 +444,7 @@
       "../browser/autocomplete/autocomplete_browsertest.cc",
       "../browser/autofill/autofill_browsertest.cc",
       "../browser/autofill/autofill_metrics_browsertest.cc",
+      "../browser/autofill/autofill_provider_browsertest.cc",
       "../browser/autofill/autofill_server_browsertest.cc",
       "../browser/autofill/content_autofill_driver_browsertest.cc",
       "../browser/autofill/form_structure_browsertest.cc",
@@ -2563,9 +2564,11 @@
   ]
 
   if (is_win) {
+    assert(toolkit_views)
     sources += [
       "../browser/notifications/mock_notification_image_retainer.cc",
       "../browser/notifications/mock_notification_image_retainer.h",
+      "../browser/ui/views/try_chrome_dialog_win/button_layout_unittest.cc",
     ]
   }
 
diff --git a/chrome/test/data/autofill/frame_detached_on_form_submit.html b/chrome/test/data/autofill/frame_detached_on_form_submit.html
new file mode 100644
index 0000000..b68d127dc
--- /dev/null
+++ b/chrome/test/data/autofill/frame_detached_on_form_submit.html
@@ -0,0 +1,25 @@
+<html>
+<body>
+
+<script>
+
+function delayedUpload() {
+  window.domAutomationController.send("SUBMISSION_FINISHED");
+}
+
+function receiveMessage(event) {
+  var address_iframe = document.getElementById('address_iframe');
+  address_iframe.parentNode.removeChild(address_iframe);
+  setTimeout(delayedUpload, 0);
+}
+
+window.addEventListener("message", receiveMessage, false);
+
+</script>
+
+<iframe src="inner_frame_address_form.html" id="address_iframe" name="address_iframe">
+</iframe>
+
+
+</body>
+</html>
diff --git a/chrome/test/data/autofill/frame_detached_on_formless_submit.html b/chrome/test/data/autofill/frame_detached_on_formless_submit.html
new file mode 100644
index 0000000..85b9be7
--- /dev/null
+++ b/chrome/test/data/autofill/frame_detached_on_formless_submit.html
@@ -0,0 +1,25 @@
+<html>
+<body>
+
+<script>
+
+function delayedUpload() {
+  window.domAutomationController.send("SUBMISSION_FINISHED");
+}
+
+function receiveMessage(event) {
+  var address_iframe = document.getElementById('address_iframe');
+  address_iframe.parentNode.removeChild(address_iframe);
+  setTimeout(delayedUpload, 0);
+}
+
+window.addEventListener("message", receiveMessage, false);
+
+</script>
+
+<iframe src="inner_frame_address_formless.html" id="address_iframe" name="address_iframe">
+</iframe>
+
+
+</body>
+</html>
diff --git a/chrome/test/data/autofill/inner_frame_address_form.html b/chrome/test/data/autofill/inner_frame_address_form.html
new file mode 100644
index 0000000..cfa42da6
--- /dev/null
+++ b/chrome/test/data/autofill/inner_frame_address_form.html
@@ -0,0 +1,15 @@
+<html>
+<body>
+
+<script>
+function send_post() {
+  window.parent.postMessage("SubmitComplete", "*");
+}
+</script>
+<form action="inner_frame_address_form.html" onsubmit="send_post(); return false;"
+      id="deleting_form">
+  <input type="text" id="address_field" name="address" autocomplete="on">
+   <input type="submit" id="submit_button" name="submit_button">
+</form>
+</body>
+</html>
diff --git a/chrome/test/data/autofill/inner_frame_address_formless.html b/chrome/test/data/autofill/inner_frame_address_formless.html
new file mode 100644
index 0000000..76df33c1
--- /dev/null
+++ b/chrome/test/data/autofill/inner_frame_address_formless.html
@@ -0,0 +1,12 @@
+<html>
+<body>
+
+<script>
+function send_post() {
+  window.parent.postMessage("SubmitComplete", "*");
+}
+</script>
+  <input type="text" id="address_field" name="address" autocomplete="on">
+  <input type="button" id="submit_button" name="submit_button" onclick="send_post()">
+</body>
+</html>
diff --git a/chrome/test/data/webui/extensions/extension_error_page_test.js b/chrome/test/data/webui/extensions/extension_error_page_test.js
index b4f74823..30ecbb5 100644
--- a/chrome/test/data/webui/extensions/extension_error_page_test.js
+++ b/chrome/test/data/webui/extensions/extension_error_page_test.js
@@ -160,11 +160,8 @@
             renderProcessId: 111,
             renderViewId: 222,
             canInspect: true,
-            stackTrace: [{
-              url: 'url',
-              lineNumber: 123,
-              columnNumber: 321
-            }],
+            contextUrl: 'http://test.com',
+            stackTrace: [{url: 'url', lineNumber: 123, columnNumber: 321}],
           },
           runtimeErrorBase);
       // Add a new runtime error to the end.
@@ -210,6 +207,13 @@
         lineNumber: 123,
         columnNumber: 321,
       });
+
+      expectEquals(
+          'Unknown',
+          ironCollapses[0].querySelector('.context-url').textContent.trim());
+      expectEquals(
+          nextRuntimeError.contextUrl,
+          ironCollapses[1].querySelector('.context-url').textContent.trim());
     });
   });
 
diff --git a/chromecast/crash/cast_crash_keys.cc b/chromecast/crash/cast_crash_keys.cc
index 15d9c1e..b2d513ff 100644
--- a/chromecast/crash/cast_crash_keys.cc
+++ b/chromecast/crash/cast_crash_keys.cc
@@ -16,8 +16,6 @@
       // chrome/common/crash_keys.cc. When http://crbug.com/598854 is fixed,
       // remove these and refactor as necessary.
 
-      {::crash_keys::kClientId, ::crash_keys::kSmallSize},
-      {::crash_keys::kChannel, ::crash_keys::kSmallSize},
       {"url-chunk", ::crash_keys::kLargeSize},
       {::crash_keys::kNumVariations, ::crash_keys::kSmallSize},
       {::crash_keys::kVariations, ::crash_keys::kHugeSize},
diff --git a/chromeos/network/onc/onc_translation_tables.cc b/chromeos/network/onc/onc_translation_tables.cc
index a3e8acc..2266cf6 100644
--- a/chromeos/network/onc/onc_translation_tables.cc
+++ b/chromeos/network/onc/onc_translation_tables.cc
@@ -401,7 +401,7 @@
     {::onc::cellular::kMDN, shill::kMdnProperty},
     {::onc::cellular::kMEID, shill::kMeidProperty},
     {::onc::cellular::kMIN, shill::kMinProperty},
-    {::onc::cellular::kModelID, shill::kModelIDProperty},
+    {::onc::cellular::kModelID, shill::kModelIdProperty},
     {::onc::cellular::kPRLVersion, shill::kPRLVersionProperty},
     {::onc::cellular::kScanning, shill::kScanningProperty},
     // This field is converted during translation, see onc_translator_*.
diff --git a/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.cc b/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.cc
index 494312c3..cb3899b7 100644
--- a/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.cc
+++ b/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.cc
@@ -370,15 +370,9 @@
   auto result = InitializeTask(std::move(config));
 
   // Report initialization status to UMA.
-  // TODO(hiroh): crbug.com/793251.
-  //              Not report "Media.ChromeArcVDA.InitializeResult" after
-  //              autototests will be updated as not using ChromeArcVDA.
   const int RESULT_MAX =
       static_cast<int>(mojom::VideoDecodeAccelerator::Result::RESULT_MAX);
   UMA_HISTOGRAM_ENUMERATION(
-      "Media.ChromeArcVideoDecodeAccelerator.InitializeResult",
-      static_cast<int>(result), RESULT_MAX);
-  UMA_HISTOGRAM_ENUMERATION(
       "Media.GpuArcVideoDecodeAccelerator.InitializeResult",
       static_cast<int>(result), RESULT_MAX);
   std::move(callback).Run(result);
diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components/autofill/content/renderer/autofill_agent.cc
index cc96c2eb..e60334a 100644
--- a/components/autofill/content/renderer/autofill_agent.cc
+++ b/components/autofill/content/renderer/autofill_agent.cc
@@ -410,7 +410,7 @@
   was_query_node_autofilled_ = element_.IsAutofilled();
   form_util::FillForm(form, element_);
   if (!element_.Form().IsNull())
-    last_interacted_form_ = element_.Form();
+    UpdateLastInteractedForm(element_.Form());
 
   GetAutofillDriver()->DidFillAutofillFormData(form, base::TimeTicks::Now());
 }
@@ -794,10 +794,10 @@
                                             ElementChangeSource source) {
   // Remember the last form the user interacted with.
   if (source == ElementChangeSource::WILL_SEND_SUBMIT_EVENT) {
-    last_interacted_form_ = form;
+    UpdateLastInteractedForm(form);
   } else if (source == ElementChangeSource::TEXTFIELD_CHANGED) {
     if (!element.Form().IsNull()) {
-      last_interacted_form_ = element.Form();
+      UpdateLastInteractedForm(element.Form());
     } else {
       // Remove invisible elements
       for (auto it = formless_elements_user_edited_.begin();
@@ -809,9 +809,9 @@
         }
       }
       formless_elements_user_edited_.insert(element);
-      constructed_form_.reset(new FormData());
-      if (!CollectFormlessElements(constructed_form_.get())) {
-        constructed_form_.reset();
+      provisionally_saved_form_ = std::make_unique<FormData>();
+      if (!CollectFormlessElements(provisionally_saved_form_.get())) {
+        provisionally_saved_form_.reset();
       } else {
         last_interacted_form_.Reset();
       }
@@ -847,18 +847,13 @@
 
   if (source == SubmissionSource::FRAME_DETACHED) {
     // Should not access the frame because it is now detached. Instead, use
-    // |constructed_form_| or |last_interacted_form_| depending on whether the
-    // form is formless or not.
-    if (!last_interacted_form_.IsNull()) {
-      FireHostSubmitEvents(last_interacted_form_, /*known_success=*/true);
-    } else if (constructed_form_) {
-      FireHostSubmitEvents(*constructed_form_, /*known_success=*/true);
-    }
+    // |provisionally_saved_form_|.
+    if (provisionally_saved_form_)
+      FireHostSubmitEvents(*provisionally_saved_form_, /*known_success=*/true);
   } else {
     FormData form_data;
-    if (GetSubmittedForm(&form_data)) {
+    if (GetSubmittedForm(&form_data))
       FireHostSubmitEvents(form_data, /*known_success=*/true);
-    }
   }
   ResetLastInteractedElements();
 }
@@ -873,17 +868,23 @@
 
 bool AutofillAgent::GetSubmittedForm(FormData* form) {
   if (!last_interacted_form_.IsNull()) {
-    return form_util::ExtractFormData(last_interacted_form_, form);
+    if (form_util::ExtractFormData(last_interacted_form_, form)) {
+      return true;
+    } else if (provisionally_saved_form_) {
+      *form = *provisionally_saved_form_;
+      return true;
+    }
   } else if (formless_elements_user_edited_.size() != 0 &&
              !form_util::IsSomeControlElementVisible(
                  formless_elements_user_edited_)) {
     // we check if all the elements the user has interacted with are gone,
-    // to decide if submission has occurred, and use the constructed_form_
-    // saved in OnProvisionallySaveForm() if fail to construct form.
+    // to decide if submission has occurred, and use the
+    // provisionally_saved_form_ saved in OnProvisionallySaveForm() if fail to
+    // construct form.
     if (CollectFormlessElements(form)) {
       return true;
-    } else if (constructed_form_) {
-      *form = *constructed_form_;
+    } else if (provisionally_saved_form_) {
+      *form = *provisionally_saved_form_;
       return true;
     }
   }
@@ -893,7 +894,16 @@
 void AutofillAgent::ResetLastInteractedElements() {
   last_interacted_form_.Reset();
   formless_elements_user_edited_.clear();
-  constructed_form_.reset();
+  provisionally_saved_form_.reset();
+}
+
+void AutofillAgent::UpdateLastInteractedForm(blink::WebFormElement form) {
+  last_interacted_form_ = form;
+  provisionally_saved_form_ = std::make_unique<FormData>();
+  if (!form_util::ExtractFormData(last_interacted_form_,
+                                  provisionally_saved_form_.get())) {
+    provisionally_saved_form_.reset();
+  }
 }
 
 const mojom::AutofillDriverPtr& AutofillAgent::GetAutofillDriver() {
diff --git a/components/autofill/content/renderer/autofill_agent.h b/components/autofill/content/renderer/autofill_agent.h
index f289417..eeff3d2 100644
--- a/components/autofill/content/renderer/autofill_agent.h
+++ b/components/autofill/content/renderer/autofill_agent.h
@@ -245,10 +245,11 @@
   virtual bool IsUserGesture() const;
 
   // Attempt to get submitted FormData from last_interacted_form_ or
-  // constructed_form_, return true if |form| is set.
+  // provisionally_saved_form_, return true if |form| is set.
   bool GetSubmittedForm(FormData* form);
 
   void ResetLastInteractedElements();
+  void UpdateLastInteractedForm(blink::WebFormElement form);
 
   // Formerly cached forms for all frames, now only caches forms for the current
   // frame.
@@ -273,7 +274,10 @@
   // When dealing with forms that don't use a <form> tag, we keep track of the
   // elements the user has modified so we can determine when submission occurs.
   std::set<blink::WebInputElement> formless_elements_user_edited_;
-  std::unique_ptr<FormData> constructed_form_;
+
+  // The form user interacted, it is used if last_interacted_form_ or formless
+  // form can't be converted to FormData at the time of form submission.
+  std::unique_ptr<FormData> provisionally_saved_form_;
 
   // Was the query node autofilled prior to previewing the form?
   bool was_query_node_autofilled_;
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index 5735775c..c9a2e82 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -290,6 +290,8 @@
     "test_autofill_driver.h",
     "test_autofill_external_delegate.cc",
     "test_autofill_external_delegate.h",
+    "test_autofill_provider.cc",
+    "test_autofill_provider.h",
     "test_personal_data_manager.cc",
     "test_personal_data_manager.h",
     "test_region_data_loader.cc",
diff --git a/components/autofill/core/browser/test_autofill_provider.cc b/components/autofill/core/browser/test_autofill_provider.cc
new file mode 100644
index 0000000..6ed1370
--- /dev/null
+++ b/components/autofill/core/browser/test_autofill_provider.cc
@@ -0,0 +1,50 @@
+// 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 "components/autofill/core/browser/test_autofill_provider.h"
+
+namespace autofill {
+
+void TestAutofillProvider::OnQueryFormFieldAutofill(
+    AutofillHandlerProxy* handler,
+    int32_t id,
+    const FormData& form,
+    const FormFieldData& field,
+    const gfx::RectF& bounding_box) {}
+
+void TestAutofillProvider::OnTextFieldDidChange(
+    AutofillHandlerProxy* handler,
+    const FormData& form,
+    const FormFieldData& field,
+    const gfx::RectF& bounding_box,
+    const base::TimeTicks timestamp) {}
+
+void TestAutofillProvider::OnTextFieldDidScroll(
+    AutofillHandlerProxy* handler,
+    const FormData& form,
+    const FormFieldData& field,
+    const gfx::RectF& bounding_box) {}
+
+bool TestAutofillProvider::OnWillSubmitForm(AutofillHandlerProxy* handler,
+                                            const FormData& form,
+                                            const base::TimeTicks timestamp) {
+  return false;
+}
+
+void TestAutofillProvider::OnFocusNoLongerOnForm(
+    AutofillHandlerProxy* handler) {}
+
+void TestAutofillProvider::OnFocusOnFormField(AutofillHandlerProxy* handler,
+                                              const FormData& form,
+                                              const FormFieldData& field,
+                                              const gfx::RectF& bounding_box) {}
+
+void TestAutofillProvider::OnDidFillAutofillFormData(
+    AutofillHandlerProxy* handler,
+    const FormData& form,
+    base::TimeTicks timestamp) {}
+
+void TestAutofillProvider::Reset(AutofillHandlerProxy* handler) {}
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/test_autofill_provider.h b/components/autofill/core/browser/test_autofill_provider.h
new file mode 100644
index 0000000..7e140a80
--- /dev/null
+++ b/components/autofill/core/browser/test_autofill_provider.h
@@ -0,0 +1,47 @@
+// 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 COMPONENTS_AUTOFILL_CORE_BROWSER_TEST_AUTOFILL_PROVIDER_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_TEST_AUTOFILL_PROVIDER_H_
+
+#include "components/autofill/core/browser/autofill_provider.h"
+
+namespace autofill {
+
+class TestAutofillProvider : public AutofillProvider {
+ public:
+  ~TestAutofillProvider() override{};
+
+  // AutofillProvider:
+  void OnQueryFormFieldAutofill(AutofillHandlerProxy* handler,
+                                int32_t id,
+                                const FormData& form,
+                                const FormFieldData& field,
+                                const gfx::RectF& bounding_box) override;
+  void OnTextFieldDidChange(AutofillHandlerProxy* handler,
+                            const FormData& form,
+                            const FormFieldData& field,
+                            const gfx::RectF& bounding_box,
+                            const base::TimeTicks timestamp) override;
+  void OnTextFieldDidScroll(AutofillHandlerProxy* handler,
+                            const FormData& form,
+                            const FormFieldData& field,
+                            const gfx::RectF& bounding_box) override;
+  bool OnWillSubmitForm(AutofillHandlerProxy* handler,
+                        const FormData& form,
+                        const base::TimeTicks timestamp) override;
+  void OnFocusNoLongerOnForm(AutofillHandlerProxy* handler) override;
+  void OnFocusOnFormField(AutofillHandlerProxy* handler,
+                          const FormData& form,
+                          const FormFieldData& field,
+                          const gfx::RectF& bounding_box) override;
+  void OnDidFillAutofillFormData(AutofillHandlerProxy* handler,
+                                 const FormData& form,
+                                 base::TimeTicks timestamp) override;
+  void Reset(AutofillHandlerProxy* handler) override;
+};
+
+}  // namespace autofill
+
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_TEST_AUTOFILL_PROVIDER_H_
diff --git a/components/crash/content/app/breakpad_linux.cc b/components/crash/content/app/breakpad_linux.cc
index 42a50e8..620a9b9c 100644
--- a/components/crash/content/app/breakpad_linux.cc
+++ b/components/crash/content/app/breakpad_linux.cc
@@ -247,7 +247,7 @@
   if (!GetEnableCrashReporterSwitchParts(command_line, &switch_parts))
     return;
 
-  base::debug::SetCrashKeyValue(crash_keys::kChannel, switch_parts[1]);
+  SetChannelCrashKey(switch_parts[1]);
 }
 #endif
 
@@ -2017,6 +2017,11 @@
   PostEnableBreakpadInitialization();
 }
 
+void SetChannelCrashKey(const std::string& channel) {
+  static crash_reporter::CrashKeyString<16> channel_key("channel");
+  channel_key.Set(channel);
+}
+
 #if defined(OS_ANDROID)
 void InitNonBrowserCrashReporterForAndroid(const std::string& process_type) {
   SanitizationInfo sanitization_info;
diff --git a/components/crash/content/app/breakpad_linux.h b/components/crash/content/app/breakpad_linux.h
index 4a2a429..9ee8555 100644
--- a/components/crash/content/app/breakpad_linux.h
+++ b/components/crash/content/app/breakpad_linux.h
@@ -16,6 +16,9 @@
 // Turns on the crash reporter in any process.
 extern void InitCrashReporter(const std::string& process_type);
 
+// Sets the product/distribution channel crash key.
+void SetChannelCrashKey(const std::string& channel);
+
 #if defined(OS_ANDROID)
 extern void InitCrashKeysForTesting();
 
diff --git a/components/crash/core/DEPS b/components/crash/core/DEPS
index 05041de..40c8ac8 100644
--- a/components/crash/core/DEPS
+++ b/components/crash/core/DEPS
@@ -5,6 +5,7 @@
   "+components/crash/core",
   "+third_party/crashpad/crashpad/client/annotation.h",
   "+third_party/crashpad/crashpad/client/annotation_list.h",
+  "+third_party/crashpad/crashpad/client/crashpad_info.h",
   "+third_party/breakpad/breakpad/src/common/simple_string_dictionary.h",
   "-net",
 ]
diff --git a/components/crash/core/common/BUILD.gn b/components/crash/core/common/BUILD.gn
index e995b54..4f67529f 100644
--- a/components/crash/core/common/BUILD.gn
+++ b/components/crash/core/common/BUILD.gn
@@ -35,7 +35,7 @@
     "crash_key_base_support.h",
   ]
 
-  defines = [ "CRASH_CORE_COMMON_IMPLEMENTATION" ]
+  defines = []
 
   # This target is not always a component, depending on the implementation.
   # When it is not a component, annotating functions with the standard
@@ -44,7 +44,10 @@
   # wrapper macro CRASH_KEY_EXPORT that only evaluates to CRASH_EXPORT if this
   # target is really a component.
   if (crash_key_target_type == "component") {
-    defines += [ "CRASH_KEY_EXPORT=CRASH_EXPORT" ]
+    defines += [
+      "CRASH_KEY_EXPORT=CRASH_EXPORT",
+      "CRASH_CORE_COMMON_IMPLEMENTATION",
+    ]
   }
 
   deps = [
diff --git a/components/crash/core/common/crash_key.h b/components/crash/core/common/crash_key.h
index a6eab451..951c7e9 100644
--- a/components/crash/core/common/crash_key.h
+++ b/components/crash/core/common/crash_key.h
@@ -217,6 +217,16 @@
 // Initializes the crash key subsystem if it is required.
 CRASH_KEY_EXPORT void InitializeCrashKeys();
 
+#if defined(UNIT_TEST) || defined(CRASH_CORE_COMMON_IMPLEMENTATION)
+// Returns a value for the crash key named |key_name|. For Crashpad-based
+// clients, this returns the first instance found of the name.
+CRASH_KEY_EXPORT std::string GetCrashKeyValue(const std::string& key_name);
+
+// Resets crash key state and, depending on the platform, de-initializes
+// the system.
+CRASH_KEY_EXPORT void ResetCrashKeysForTesting();
+#endif
+
 }  // namespace crash_reporter
 
 #undef USE_CRASHPAD_ANNOTATION
diff --git a/components/crash/core/common/crash_key_breakpad.cc b/components/crash/core/common/crash_key_breakpad.cc
index 47b9597..dcd406c 100644
--- a/components/crash/core/common/crash_key_breakpad.cc
+++ b/components/crash/core/common/crash_key_breakpad.cc
@@ -7,6 +7,7 @@
 
 #include "components/crash/core/common/crash_key.h"
 
+#include "base/debug/crash_logging.h"
 #include "base/format_macros.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/stringprintf.h"
@@ -107,4 +108,17 @@
   InitializeCrashKeyBaseSupport();
 }
 
+std::string GetCrashKeyValue(const std::string& key_name) {
+  const char* value =
+      internal::GetCrashKeyStorage()->GetValueForKey(key_name.c_str());
+  if (value)
+    return value;
+  return std::string();
+}
+
+void ResetCrashKeysForTesting() {
+  internal::ResetCrashKeyStorageForTesting();
+  base::debug::SetCrashKeyImplementation(nullptr);
+}
+
 }  // namespace crash_reporter
diff --git a/components/crash/core/common/crash_key_breakpad_ios.mm b/components/crash/core/common/crash_key_breakpad_ios.mm
index b476937..288453b 100644
--- a/components/crash/core/common/crash_key_breakpad_ios.mm
+++ b/components/crash/core/common/crash_key_breakpad_ios.mm
@@ -44,7 +44,7 @@
   NSString* value_ns = base::SysUTF8ToNSString(value.as_string());
 
   WithBreakpadRefSync(^(BreakpadRef ref) {
-    BreakpadSetKeyValue(ref, key, value_ns);
+    BreakpadAddUploadParameter(ref, key, value_ns);
   });
 }
 
@@ -52,13 +52,14 @@
   NSString* key = base::SysUTF8ToNSString(name_);
 
   WithBreakpadRefSync(^(BreakpadRef ref) {
-    BreakpadRemoveKeyValue(ref, key);
+    BreakpadRemoveUploadParameter(ref, key);
   });
 }
 
 bool CrashKeyStringImpl::is_set() const {
   __block bool is_set = false;
-  NSString* key = base::SysUTF8ToNSString(name_);
+  NSString* key = base::SysUTF8ToNSString(
+      std::string(BREAKPAD_SERVER_PARAMETER_PREFIX) + name_);
 
   WithBreakpadRefSync(^(BreakpadRef ref) {
     is_set = BreakpadKeyValue(ref, key) != nil;
@@ -73,4 +74,21 @@
   InitializeCrashKeyBaseSupport();
 }
 
+std::string GetCrashKeyValue(const std::string& key_name) {
+  __block NSString* value;
+  NSString* key = base::SysUTF8ToNSString(
+      std::string(BREAKPAD_SERVER_PARAMETER_PREFIX) + key_name);
+
+  internal::WithBreakpadRefSync(^(BreakpadRef ref) {
+    value = BreakpadKeyValue(ref, key);
+  });
+
+  return base::SysNSStringToUTF8(value);
+}
+
+void ResetCrashKeysForTesting() {
+  // There's no way to do this on iOS without tearing down the
+  // BreakpadController.
+}
+
 }  // namespace crash_reporter
diff --git a/components/crash/core/common/crash_key_crashpad.cc b/components/crash/core/common/crash_key_crashpad.cc
index 7cd7f8df..929392e 100644
--- a/components/crash/core/common/crash_key_crashpad.cc
+++ b/components/crash/core/common/crash_key_crashpad.cc
@@ -7,8 +7,10 @@
 
 #include "components/crash/core/common/crash_key.h"
 
+#include "base/debug/crash_logging.h"
 #include "components/crash/core/common/crash_key_base_support.h"
 #include "third_party/crashpad/crashpad/client/annotation_list.h"
+#include "third_party/crashpad/crashpad/client/crashpad_info.h"
 
 namespace crash_reporter {
 
@@ -17,4 +19,33 @@
   InitializeCrashKeyBaseSupport();
 }
 
+// Returns a value for the crash key named |key_name|. For Crashpad-based
+// clients, this returns the first instance found of the name.
+std::string GetCrashKeyValue(const std::string& key_name) {
+  auto* annotation_list = crashpad::AnnotationList::Get();
+  if (annotation_list) {
+    for (crashpad::Annotation* annotation : *annotation_list) {
+      if (key_name == annotation->name()) {
+        return std::string(static_cast<const char*>(annotation->value()),
+                           annotation->size());
+      }
+    }
+  }
+
+  return std::string();
+}
+
+void ResetCrashKeysForTesting() {
+  // The AnnotationList should not be deleted because the static Annotation
+  // object data still reference the link nodes.
+  auto* annotation_list = crashpad::AnnotationList::Get();
+  if (annotation_list) {
+    for (crashpad::Annotation* annotation : *annotation_list) {
+      annotation->Clear();
+    }
+  }
+
+  base::debug::SetCrashKeyImplementation(nullptr);
+}
+
 }  // namespace crash_reporter
diff --git a/components/crash/core/common/crash_key_stubs.cc b/components/crash/core/common/crash_key_stubs.cc
index deb145f..f978394 100644
--- a/components/crash/core/common/crash_key_stubs.cc
+++ b/components/crash/core/common/crash_key_stubs.cc
@@ -28,4 +28,10 @@
 
 void InitializeCrashKeys() {}
 
+std::string GetCrashKeyValue(const std::string& key_name) {
+  return std::string();
+}
+
+void ResetCrashKeysForTesting() {}
+
 }  // namespace crash_reporter
diff --git a/components/crash/core/common/crash_keys.cc b/components/crash/core/common/crash_keys.cc
index d6d43f2a..3fcbc16 100644
--- a/components/crash/core/common/crash_keys.cc
+++ b/components/crash/core/common/crash_keys.cc
@@ -8,6 +8,7 @@
 #include "base/debug/crash_logging.h"
 #include "base/format_macros.h"
 #include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
@@ -17,29 +18,26 @@
 
 namespace crash_keys {
 
-#if defined(OS_MACOSX)
-// Crashpad owns the "guid" key. Chrome's metrics client ID is a separate ID
-// carried in a distinct "metrics_client_id" field.
+namespace {
+
+#if defined(OS_MACOSX) || defined(OS_WIN)
+// When using Crashpad, the crash reporting client ID is the responsibility of
+// Crashpad. It is not set directly by Chrome. To make the metrics client ID
+// available on the server, it's stored in a distinct key.
 const char kMetricsClientId[] = "metrics_client_id";
-#elif defined(OS_WIN)
-// TODO(scottmg): While transitioning to Crashpad, there are some executables
-// that use Crashpad (which use kMetricsClientId), and some that use Breakpad
-// (kClientId), and they both use this file. For now we define both, but once
-// Breakpad is no longer used on Windows, we will no longer need kClientId, and
-// this can be combined with the OS_MACOSX block above.
-const char kMetricsClientId[] = "metrics_client_id";
-const char kClientId[] = "guid";
 #else
-const char kClientId[] = "guid";
+// When using Breakpad instead of Crashpad, the crash reporting client ID is the
+// same as the metrics client ID.
+const char kMetricsClientId[] = "guid";
 #endif
 
-const char kChannel[] = "channel";
+crash_reporter::CrashKeyString<40> client_id_key(kMetricsClientId);
+
+}  // namespace
 
 const char kNumVariations[] = "num-experiments";
 const char kVariations[] = "variations";
 
-const char kSwitchFormat[] = "switch-%" PRIuS;
-
 void SetMetricsClientIdFromGUID(const std::string& metrics_client_guid) {
   std::string stripped_guid(metrics_client_guid);
   // Remove all instance of '-' char from the GUID. So BCD-WXY becomes BCDWXY.
@@ -48,15 +46,7 @@
   if (stripped_guid.empty())
     return;
 
-#if defined(OS_MACOSX) || defined(OS_WIN)
-  // The crash client ID is maintained by Crashpad and is distinct from the
-  // metrics client ID, which is carried in its own key.
-  base::debug::SetCrashKeyValue(kMetricsClientId, stripped_guid);
-#else
-  // The crash client ID is set by the application when Breakpad is in use.
-  // The same ID as the metrics client ID is used.
-  base::debug::SetCrashKeyValue(kClientId, stripped_guid);
-#endif
+  client_id_key.Set(stripped_guid);
 }
 
 void ClearMetricsClientId() {
@@ -73,7 +63,7 @@
   // it needs to use the metrics client ID as its stable crash client ID, so
   // leave its client ID intact even when metrics reporting is disabled while
   // the application is running.
-  base::debug::ClearCrashKey(kMetricsClientId);
+  client_id_key.Clear();
 #endif
 }
 
@@ -96,46 +86,39 @@
   base::debug::SetCrashKeyValue(kVariations, variations_string);
 }
 
-void GetCrashKeysForCommandLineSwitches(
-    std::vector<base::debug::CrashKey>* keys) {
-  DCHECK(keys);
+using SwitchesCrashKey = crash_reporter::CrashKeyString<64>;
+static SwitchesCrashKey switches_keys[] = {
+    {"switch-1", SwitchesCrashKey::Tag::kArray},
+    {"switch-2", SwitchesCrashKey::Tag::kArray},
+    {"switch-3", SwitchesCrashKey::Tag::kArray},
+    {"switch-4", SwitchesCrashKey::Tag::kArray},
+    {"switch-5", SwitchesCrashKey::Tag::kArray},
+    {"switch-6", SwitchesCrashKey::Tag::kArray},
+    {"switch-7", SwitchesCrashKey::Tag::kArray},
+    {"switch-8", SwitchesCrashKey::Tag::kArray},
+    {"switch-9", SwitchesCrashKey::Tag::kArray},
+    {"switch-10", SwitchesCrashKey::Tag::kArray},
+    {"switch-11", SwitchesCrashKey::Tag::kArray},
+    {"switch-12", SwitchesCrashKey::Tag::kArray},
+    {"switch-13", SwitchesCrashKey::Tag::kArray},
+    {"switch-14", SwitchesCrashKey::Tag::kArray},
+    {"switch-15", SwitchesCrashKey::Tag::kArray},
+};
 
-  // Use static storage for formatted key names, since they will persist for
-  // the duration of the program.
-  static char formatted_keys[kSwitchesMaxCount][sizeof(kSwitchFormat) + 1] =
-      {{ 0 }};
-  const size_t formatted_key_len = sizeof(formatted_keys[0]);
-
-  // sizeof(kSwitchFormat) is platform-dependent, so make sure formatted_keys
-  // actually have space for a 2-digit switch number plus null-terminator.
-  static_assert(formatted_key_len >= 10,
-                "insufficient space for \"switch-NN\"");
-
-  for (size_t i = 0; i < kSwitchesMaxCount; ++i) {
-    // Name the keys using 1-based indexing.
-    int n = base::snprintf(formatted_keys[i], formatted_key_len, kSwitchFormat,
-                           i + 1);
-    DCHECK_GT(n, 0);
-    base::debug::CrashKey crash_key = { formatted_keys[i], kSmallSize };
-    keys->push_back(crash_key);
-  }
-}
+static crash_reporter::CrashKeyString<4> num_switches_key("num-switches");
 
 void SetSwitchesFromCommandLine(const base::CommandLine& command_line,
                                 SwitchFilterFunction skip_filter) {
   const base::CommandLine::StringVector& argv = command_line.argv();
 
   // Set the number of switches in case size > kNumSwitches.
-  // num-switches is capped at 15 entries, so only two digits are stored.
-  static crash_reporter::CrashKeyString<2> num_switches_key("num-switches");
-  num_switches_key.Set(base::StringPrintf("%" PRIuS, argv.size() - 1));
+  num_switches_key.Set(base::NumberToString(argv.size() - 1));
 
-  size_t key_i = 1;  // Key names are 1-indexed.
+  size_t key_i = 0;
 
   // Go through the argv, skipping the exec path. Stop if there are too many
   // switches to hold in crash keys.
-  for (size_t i = 1; i < argv.size() && key_i <= crash_keys::kSwitchesMaxCount;
-       ++i) {
+  for (size_t i = 1; i < argv.size() && key_i < arraysize(switches_keys); ++i) {
 #if defined(OS_WIN)
     std::string switch_str = base::WideToUTF8(argv[i]);
 #else
@@ -146,13 +129,19 @@
     if (skip_filter && (*skip_filter)(switch_str))
       continue;
 
-    std::string key = base::StringPrintf(kSwitchFormat, key_i++);
-    base::debug::SetCrashKeyValue(key, switch_str);
+    switches_keys[key_i++].Set(switch_str);
   }
 
   // Clear any remaining switches.
-  for (; key_i <= kSwitchesMaxCount; ++key_i)
-    base::debug::ClearCrashKey(base::StringPrintf(kSwitchFormat, key_i));
+  for (; key_i < arraysize(switches_keys); ++key_i)
+    switches_keys[key_i].Clear();
+}
+
+void ResetCommandLineForTesting() {
+  num_switches_key.Clear();
+  for (auto& key : switches_keys) {
+    key.Clear();
+  }
 }
 
 }  // namespace crash_keys
diff --git a/components/crash/core/common/crash_keys.h b/components/crash/core/common/crash_keys.h
index 39cce75..73f1385 100644
--- a/components/crash/core/common/crash_keys.h
+++ b/components/crash/core/common/crash_keys.h
@@ -12,6 +12,7 @@
 
 #include "base/debug/crash_logging.h"
 #include "build/build_config.h"
+#include "components/crash/core/common/crash_key.h"
 
 namespace base {
 class CommandLine;
@@ -28,10 +29,6 @@
 // Sets the list of active experiment/variations info.
 void SetVariationsList(const std::vector<std::string>& variations);
 
-// Adds a common set of crash keys for holding command-line switches to |keys|.
-void GetCrashKeysForCommandLineSwitches(
-    std::vector<base::debug::CrashKey>* keys);
-
 // A function returning true if |flag| is a switch that should be filtered out
 // of crash keys.
 using SwitchFilterFunction = bool (*)(const std::string& flag);
@@ -42,6 +39,9 @@
 void SetSwitchesFromCommandLine(const base::CommandLine& command_line,
                                 SwitchFilterFunction skip_filter);
 
+// Clears all the CommandLine-related crash keys.
+void ResetCommandLineForTesting();
+
 // Crash Key Constants /////////////////////////////////////////////////////////
 
 // kChunkMaxLength is the platform-specific maximum size that a value in a
@@ -71,24 +71,6 @@
 
 // Crash Key Name Constants ////////////////////////////////////////////////////
 
-// The GUID used to identify this client to the crash system.
-#if defined(OS_MACOSX)
-// When using Crashpad, the crash reporting client ID is the responsibility of
-// Crashpad. It is not set directly by Chrome. To make the metrics client ID
-// available on the server, it's stored in a distinct key.
-extern const char kMetricsClientId[];
-#elif defined(OS_WIN)
-extern const char kMetricsClientId[];
-extern const char kClientId[];
-#else
-// When using Breakpad instead of Crashpad, the crash reporting client ID is the
-// same as the metrics client ID.
-extern const char kClientId[];
-#endif
-
-// The product release/distribution channel.
-extern const char kChannel[];
-
 // The total number of experiments the instance has.
 extern const char kNumVariations[];
 
@@ -96,14 +78,6 @@
 // typically set by SetExperimentList.
 extern const char kVariations[];
 
-// The maximum number of command line switches to process. |kSwitchFormat|
-// should be formatted with an integer in the range [1, kSwitchesMaxCount].
-const size_t kSwitchesMaxCount = 15;
-
-// A printf-style format string naming the set of crash keys corresponding to
-// at most |kSwitchesMaxCount| command line switches.
-extern const char kSwitchFormat[];
-
 }  // namespace crash_keys
 
 #endif  // COMPONENTS_CRASH_CORE_COMMON_CRASH_KEYS_H_
diff --git a/components/crash/core/common/crash_keys_unittest.cc b/components/crash/core/common/crash_keys_unittest.cc
index 466c5fe..eca2879 100644
--- a/components/crash/core/common/crash_keys_unittest.cc
+++ b/components/crash/core/common/crash_keys_unittest.cc
@@ -18,21 +18,22 @@
 #include "components/crash/core/common/crash_key.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+using crash_reporter::GetCrashKeyValue;
+
+// The number of switch-N keys declared in SetSwitchesFromCommandLine().
+constexpr int kSwitchesMaxCount = 15;
+
 class CrashKeysTest : public testing::Test {
  public:
   void SetUp() override {
+    ResetData();
+
     crash_reporter::InitializeCrashKeys();
     self_ = this;
     base::debug::SetCrashKeyReportingFunctions(
         &SetCrashKeyValue, &ClearCrashKey);
   }
 
-  bool InitSwitchesCrashKeys() {
-    std::vector<base::debug::CrashKey> keys;
-    crash_keys::GetCrashKeysForCommandLineSwitches(&keys);
-    return InitCrashKeys(keys);
-  }
-
   bool InitVariationsCrashKeys() {
     std::vector<base::debug::CrashKey> keys = {
         {crash_keys::kNumVariations, crash_keys::kSmallSize},
@@ -42,6 +43,7 @@
 
   void TearDown() override {
     base::debug::ResetCrashLoggingForTesting();
+    ResetData();
     self_ = nullptr;
   }
 
@@ -72,6 +74,11 @@
     self_->keys_.erase(key.as_string());
   }
 
+  void ResetData() {
+    crash_keys::ResetCommandLineForTesting();
+    crash_reporter::ResetCrashKeysForTesting();
+  }
+
   static CrashKeysTest* self_;
 
   std::map<std::string, std::string> keys_;
@@ -80,49 +87,46 @@
 CrashKeysTest* CrashKeysTest::self_ = nullptr;
 
 TEST_F(CrashKeysTest, Switches) {
-  ASSERT_TRUE(InitSwitchesCrashKeys());
-
   // Set three switches.
   {
     base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
     for (size_t i = 1; i <= 3; ++i)
       command_line.AppendSwitch(base::StringPrintf("--flag-%" PRIuS, i));
     crash_keys::SetSwitchesFromCommandLine(command_line, nullptr);
-    EXPECT_EQ("--flag-1", GetKeyValue("switch-1"));
-    EXPECT_EQ("--flag-2", GetKeyValue("switch-2"));
-    EXPECT_EQ("--flag-3", GetKeyValue("switch-3"));
-    EXPECT_FALSE(HasCrashKey("switch-4"));
+    EXPECT_EQ("--flag-1", GetCrashKeyValue("switch-1"));
+    EXPECT_EQ("--flag-2", GetCrashKeyValue("switch-2"));
+    EXPECT_EQ("--flag-3", GetCrashKeyValue("switch-3"));
+    EXPECT_TRUE(GetCrashKeyValue("switch-4").empty());
   }
 
   // Set more than the max switches.
   {
     base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
-    const size_t kMax = crash_keys::kSwitchesMaxCount + 2;
+    const size_t kMax = kSwitchesMaxCount + 2;
     EXPECT_GT(kMax, static_cast<size_t>(15));
     for (size_t i = 1; i <= kMax; ++i)
       command_line.AppendSwitch(base::StringPrintf("--many-%" PRIuS, i));
     crash_keys::SetSwitchesFromCommandLine(command_line, nullptr);
-    EXPECT_EQ("--many-1", GetKeyValue("switch-1"));
-    EXPECT_EQ("--many-9", GetKeyValue("switch-9"));
-    EXPECT_EQ("--many-15", GetKeyValue("switch-15"));
-    EXPECT_FALSE(HasCrashKey("switch-16"));
-    EXPECT_FALSE(HasCrashKey("switch-17"));
+    EXPECT_EQ("--many-1", GetCrashKeyValue("switch-1"));
+    EXPECT_EQ("--many-9", GetCrashKeyValue("switch-9"));
+    EXPECT_EQ("--many-15", GetCrashKeyValue("switch-15"));
+    EXPECT_TRUE(GetCrashKeyValue("switch-16").empty());
+    EXPECT_TRUE(GetCrashKeyValue("switch-17").empty());
   }
 
   // Set fewer to ensure that old ones are erased.
   {
     base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
-    for (size_t i = 1; i <= 5; ++i)
-      command_line.AppendSwitch(base::StringPrintf("--fewer-%" PRIuS, i));
+    for (int i = 1; i <= 5; ++i)
+      command_line.AppendSwitch(base::StringPrintf("--fewer-%d", i));
     crash_keys::SetSwitchesFromCommandLine(command_line, nullptr);
-    EXPECT_EQ("--fewer-1", GetKeyValue("switch-1"));
-    EXPECT_EQ("--fewer-2", GetKeyValue("switch-2"));
-    EXPECT_EQ("--fewer-3", GetKeyValue("switch-3"));
-    EXPECT_EQ("--fewer-4", GetKeyValue("switch-4"));
-    EXPECT_EQ("--fewer-5", GetKeyValue("switch-5"));
-    for (size_t i = 6; i < 20; ++i)
-      EXPECT_FALSE(HasCrashKey(base::StringPrintf(crash_keys::kSwitchFormat,
-                                                  i)));
+    EXPECT_EQ("--fewer-1", GetCrashKeyValue("switch-1"));
+    EXPECT_EQ("--fewer-2", GetCrashKeyValue("switch-2"));
+    EXPECT_EQ("--fewer-3", GetCrashKeyValue("switch-3"));
+    EXPECT_EQ("--fewer-4", GetCrashKeyValue("switch-4"));
+    EXPECT_EQ("--fewer-5", GetCrashKeyValue("switch-5"));
+    for (int i = 6; i < 20; ++i)
+      EXPECT_TRUE(GetCrashKeyValue(base::StringPrintf("switch-%d", i)).empty());
   }
 }
 
@@ -135,10 +139,6 @@
 }  // namespace
 
 TEST_F(CrashKeysTest, FilterFlags) {
-  ASSERT_TRUE(InitSwitchesCrashKeys());
-
-  using crash_keys::kSwitchesMaxCount;
-
   base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
   command_line.AppendSwitch("--not-boring-1");
   command_line.AppendSwitch("--boring");
@@ -152,11 +152,11 @@
 
   // If the boring keys are filtered out, every single key should now be
   // not-boring.
-  for (size_t i = 1; i <= kSwitchesMaxCount; ++i) {
-    std::string switch_name = base::StringPrintf(crash_keys::kSwitchFormat, i);
-    std::string switch_value = base::StringPrintf("--not-boring-%" PRIuS, i);
-    EXPECT_EQ(switch_value, GetKeyValue(switch_name)) << "switch_name is " <<
-        switch_name;
+  for (int i = 1; i <= kSwitchesMaxCount; ++i) {
+    std::string switch_name = base::StringPrintf("switch-%d", i);
+    std::string switch_value = base::StringPrintf("--not-boring-%d", i);
+    EXPECT_EQ(switch_value, GetCrashKeyValue(switch_name))
+        << "switch_name is " << switch_name;
   }
 }
 
diff --git a/components/metrics/file_metrics_provider.cc b/components/metrics/file_metrics_provider.cc
index f49eefc..fe01db1eb 100644
--- a/components/metrics/file_metrics_provider.cc
+++ b/components/metrics/file_metrics_provider.cc
@@ -157,6 +157,12 @@
   }
   ~SourceInfo() {}
 
+  struct FoundFile {
+    base::FilePath path;
+    base::FileEnumerator::FileInfo info;
+  };
+  using FoundFiles = base::flat_map<base::Time, FoundFile>;
+
   // How to access this source (file/dir, atomic/active).
   const SourceType type;
 
@@ -167,6 +173,9 @@
   // a directory is being monitored.
   base::FilePath directory;
 
+  // The files found in the above directory, ordered by last-modified.
+  std::unique_ptr<FoundFiles> found_files;
+
   // Where on disk the file is located. If a directory is being monitored,
   // this will be updated for whatever file is being read.
   base::FilePath path;
@@ -272,69 +281,75 @@
   DCHECK_EQ(SOURCE_HISTOGRAMS_ATOMIC_DIR, source->type);
   DCHECK(!source->directory.empty());
 
-  // Open the directory and find all the files, remembering the last-modified
-  // time of each.
-  struct FoundFile {
-    base::FilePath path;
-    base::FileEnumerator::FileInfo info;
-  };
-  base::flat_map<base::Time, FoundFile> found_files;
-  base::FilePath file_path;
-  base::Time now_time = base::Time::Now();
+  // Cumulative directory stats. These will remain zero if the directory isn't
+  // scanned but that's okay since any work they would cause to be done below
+  // would have been done during the first call where the directory was fully
+  // scanned.
   size_t total_size_kib = 0;  // Using KiB allows 4TiB even on 32-bit builds.
   size_t file_count = 0;
   size_t delete_count = 0;
-  base::FileEnumerator file_iter(source->directory, /*recursive=*/false,
-                                 base::FileEnumerator::FILES);
-  FoundFile found_file;
-  for (found_file.path = file_iter.Next(); !found_file.path.empty();
-       found_file.path = file_iter.Next()) {
-    found_file.info = file_iter.GetInfo();
 
-    // Ignore directories and zero-sized files.
-    if (found_file.info.IsDirectory() || found_file.info.GetSize() == 0)
-      continue;
+  base::Time now_time = base::Time::Now();
+  if (!source->found_files) {
+    source->found_files = base::MakeUnique<SourceInfo::FoundFiles>();
+    base::FileEnumerator file_iter(source->directory, /*recursive=*/false,
+                                   base::FileEnumerator::FILES);
+    SourceInfo::FoundFile found_file;
 
-    // Ignore temporary files.
-    base::FilePath::CharType first_character =
-        found_file.path.BaseName().value().front();
-    if (first_character == FILE_PATH_LITERAL('.') ||
-        first_character == FILE_PATH_LITERAL('_')) {
-      continue;
+    // Open the directory and find all the files, remembering the last-modified
+    // time of each.
+    for (found_file.path = file_iter.Next(); !found_file.path.empty();
+         found_file.path = file_iter.Next()) {
+      found_file.info = file_iter.GetInfo();
+
+      // Ignore directories.
+      if (found_file.info.IsDirectory())
+        continue;
+
+      // Ignore temporary files.
+      base::FilePath::CharType first_character =
+          found_file.path.BaseName().value().front();
+      if (first_character == FILE_PATH_LITERAL('.') ||
+          first_character == FILE_PATH_LITERAL('_')) {
+        continue;
+      }
+
+      // Ignore non-PMA (Persistent Memory Allocator) files.
+      if (found_file.path.Extension() !=
+          base::PersistentMemoryAllocator::kFileExtension) {
+        continue;
+      }
+
+      // Process real files.
+      total_size_kib += found_file.info.GetSize() >> 10;
+      base::Time modified = found_file.info.GetLastModifiedTime();
+      if (modified > source->last_seen) {
+        // This file hasn't been read. Remember it (unless from the future).
+        if (modified <= now_time)
+          source->found_files->emplace(modified, std::move(found_file));
+        ++file_count;
+      } else {
+        // This file has been read. Try to delete it. Ignore any errors because
+        // the file may be un-removeable by this process. It could, for example,
+        // have been created by a privileged process like setup.exe. Even if it
+        // is not removed, it will continue to be ignored bacuse of the older
+        // modification time.
+        base::DeleteFile(found_file.path, /*recursive=*/false);
+        ++delete_count;
+      }
     }
 
-    // Ignore non-PMA (Persistent Memory Allocator) files.
-    if (found_file.path.Extension() !=
-        base::PersistentMemoryAllocator::kFileExtension) {
-      continue;
-    }
-
-    // Process real files.
-    total_size_kib += found_file.info.GetSize() >> 10;
-    base::Time modified = found_file.info.GetLastModifiedTime();
-    if (modified > source->last_seen) {
-      // This file hasn't been read. Remember it (unless it's from the future).
-      if (modified <= now_time)
-        found_files.emplace(modified, std::move(found_file));
-      ++file_count;
-    } else {
-      // This file has been read. Try to delete it. Ignore any errors because
-      // the file may be un-removeable by this process. It could, for example,
-      // have been created by a privileged process like setup.exe. Even if it
-      // is not removed, it will continue to be ignored bacuse of the older
-      // modification time.
-      base::DeleteFile(found_file.path, /*recursive=*/false);
-      ++delete_count;
-    }
+    UMA_HISTOGRAM_COUNTS_100("UMA.FileMetricsProvider.DirectoryFiles",
+                             file_count);
   }
 
-  UMA_HISTOGRAM_COUNTS_100("UMA.FileMetricsProvider.DirectoryFiles",
-                           file_count);
-
   // Filter files from the front until one is found for processing.
   bool have_file = false;
-  while (!found_files.empty()) {
-    const FoundFile& found = found_files.begin()->second;
+  while (!source->found_files->empty()) {
+    SourceInfo::FoundFile found =
+        std::move(source->found_files->begin()->second);
+    source->found_files->erase(source->found_files->begin());
+
     bool too_many =
         source->max_dir_files > 0 && file_count > source->max_dir_files;
     bool too_big =
@@ -350,7 +365,6 @@
       RecordAccessResult(too_many ? ACCESS_RESULT_TOO_MANY_FILES
                                   : too_big ? ACCESS_RESULT_TOO_MANY_BYTES
                                             : ACCESS_RESULT_TOO_OLD);
-      found_files.erase(found_files.begin());
       continue;
     }
 
@@ -364,7 +378,6 @@
     // Record the result. Success will be recorded by the caller.
     if (result != ACCESS_RESULT_THIS_PID)
       RecordAccessResult(result);
-    found_files.erase(found_files.begin());
   }
 
   UMA_HISTOGRAM_COUNTS_100("UMA.FileMetricsProvider.DeletedFiles",
@@ -442,6 +455,11 @@
       // When there are no more files, ACCESS_RESULT_DOESNT_EXIST will be
       // returned and the loop will exit above.
     } while (result != ACCESS_RESULT_SUCCESS && !source->directory.empty());
+
+    // If the set of known files is empty, clear the object so the next run
+    // will do a fresh scan of the directory.
+    if (source->found_files && source->found_files->empty())
+      source->found_files.reset();
   }
 }
 
diff --git a/components/metrics/file_metrics_provider_unittest.cc b/components/metrics/file_metrics_provider_unittest.cc
index f668955e..350ce67 100644
--- a/components/metrics/file_metrics_provider_unittest.cc
+++ b/components/metrics/file_metrics_provider_unittest.cc
@@ -523,6 +523,14 @@
             base::PersistentMemoryAllocator::MEMORY_DELETED);
       });
 
+  {
+    base::File empty(metrics_files.GetPath().AppendASCII("h4.pma"),
+                     base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+  }
+  base::TouchFile(metrics_files.GetPath().AppendASCII("h4.pma"),
+                  base_time + base::TimeDelta::FromMinutes(4),
+                  base_time + base::TimeDelta::FromMinutes(4));
+
   // Register the file and allow the "checker" task to run.
   provider()->RegisterSource(FileMetricsProvider::Params(
       metrics_files.GetPath(),
@@ -534,6 +542,7 @@
   EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h1.pma")));
   EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h2.pma")));
   EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h3.pma")));
+  EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h4.pma")));
 
   // H1 should be skipped and H2 available.
   OnDidCreateMetricsLog();
@@ -542,13 +551,16 @@
   EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("h1.pma")));
   EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h2.pma")));
   EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h3.pma")));
+  EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h4.pma")));
 
-  // Nothing else should be found.
+  // Nothing else should be found but the last (valid but empty) file will
+  // stick around to be processed later (should it get expanded).
   OnDidCreateMetricsLog();
   RunTasks();
   EXPECT_EQ(0U, GetIndependentHistogramCount());
   EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("h2.pma")));
   EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("h3.pma")));
+  EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h4.pma")));
 }
 
 TEST_P(FileMetricsProviderTest, AccessTimeLimitedDirectory) {
diff --git a/components/prefs/pref_notifier_impl.cc b/components/prefs/pref_notifier_impl.cc
index 0e4d0cb..525e146 100644
--- a/components/prefs/pref_notifier_impl.cc
+++ b/components/prefs/pref_notifier_impl.cc
@@ -71,8 +71,8 @@
   all_prefs_pref_observers_.RemoveObserver(observer);
 }
 
-void PrefNotifierImpl::AddInitObserver(base::Callback<void(bool)> obs) {
-  init_observers_.push_back(obs);
+void PrefNotifierImpl::AddInitObserver(base::OnceCallback<void(bool)> obs) {
+  init_observers_.push_back(std::move(obs));
 }
 
 void PrefNotifierImpl::OnPreferenceChanged(const std::string& path) {
@@ -82,14 +82,14 @@
 void PrefNotifierImpl::OnInitializationCompleted(bool succeeded) {
   DCHECK(thread_checker_.CalledOnValidThread());
 
-  // We must make a copy of init_observers_ and clear it before we run
+  // We must move init_observers_ to a local variable before we run
   // observers, or we can end up in this method re-entrantly before
   // clearing the observers list.
-  PrefInitObserverList observers(init_observers_);
-  init_observers_.clear();
+  PrefInitObserverList observers;
+  std::swap(observers, init_observers_);
 
   for (auto& observer : observers)
-    observer.Run(succeeded);
+    std::move(observer).Run(succeeded);
 }
 
 void PrefNotifierImpl::FireObservers(const std::string& path) {
diff --git a/components/prefs/pref_notifier_impl.h b/components/prefs/pref_notifier_impl.h
index 68a2408..e0c6035 100644
--- a/components/prefs/pref_notifier_impl.h
+++ b/components/prefs/pref_notifier_impl.h
@@ -43,7 +43,7 @@
   // We run the callback once, when initialization completes. The bool
   // parameter will be set to true for successful initialization,
   // false for unsuccessful.
-  void AddInitObserver(base::Callback<void(bool)> observer);
+  void AddInitObserver(base::OnceCallback<void(bool)> observer);
 
   void SetPrefService(PrefService* pref_service);
 
@@ -59,7 +59,7 @@
   typedef base::hash_map<std::string, std::unique_ptr<PrefObserverList>>
       PrefObserverMap;
 
-  typedef std::list<base::Callback<void(bool)>> PrefInitObserverList;
+  typedef std::list<base::OnceCallback<void(bool)>> PrefInitObserverList;
 
   const PrefObserverMap* pref_observers() const { return &pref_observers_; }
 
diff --git a/components/prefs/pref_service.cc b/components/prefs/pref_service.cc
index e913ecf1..1adbb77 100644
--- a/components/prefs/pref_service.cc
+++ b/components/prefs/pref_service.cc
@@ -365,8 +365,8 @@
   pref_notifier_->RemovePrefObserver(path, obs);
 }
 
-void PrefService::AddPrefInitObserver(base::Callback<void(bool)> obs) {
-  pref_notifier_->AddInitObserver(obs);
+void PrefService::AddPrefInitObserver(base::OnceCallback<void(bool)> obs) {
+  pref_notifier_->AddInitObserver(std::move(obs));
 }
 
 PrefRegistry* PrefService::DeprecatedGetPrefRegistry() {
diff --git a/components/prefs/pref_service.h b/components/prefs/pref_service.h
index 2b46b19..2a013aa 100644
--- a/components/prefs/pref_service.h
+++ b/components/prefs/pref_service.h
@@ -296,7 +296,7 @@
   // We run the callback once, when initialization completes. The bool
   // parameter will be set to true for successful initialization,
   // false for unsuccessful.
-  void AddPrefInitObserver(base::Callback<void(bool)> callback);
+  void AddPrefInitObserver(base::OnceCallback<void(bool)> callback);
 
   // Returns the PrefRegistry object for this service. You should not
   // use this; the intent is for no registrations to take place after
diff --git a/components/proximity_auth/bluetooth_low_energy_connection_finder_unittest.cc b/components/proximity_auth/bluetooth_low_energy_connection_finder_unittest.cc
index ab3d063..33cef50 100644
--- a/components/proximity_auth/bluetooth_low_energy_connection_finder_unittest.cc
+++ b/components/proximity_auth/bluetooth_low_energy_connection_finder_unittest.cc
@@ -205,9 +205,8 @@
     uuid_list.push_back(advertisement_uuid);
     device::BluetoothDevice::ServiceDataMap service_data_map;
     service_data_map[advertisement_uuid] = eid_vector;
-
     device_->UpdateAdvertisementData(kRssi, uuid_list, service_data_map,
-                                     nullptr);
+                                     {} /* manufacturer_data */, nullptr);
   }
 
   scoped_refptr<device::MockBluetoothAdapter> adapter_;
diff --git a/content/browser/accessibility/accessibility_tree_formatter_blink.cc b/content/browser/accessibility/accessibility_tree_formatter_blink.cc
index 9d3bca65..dd45e718 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_blink.cc
+++ b/content/browser/accessibility/accessibility_tree_formatter_blink.cc
@@ -2,6 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "content/browser/accessibility/accessibility_tree_formatter_blink.h"
+
+#include <math.h>
 #include <stddef.h>
 
 #include <utility>
@@ -12,7 +15,6 @@
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
-#include "content/browser/accessibility/accessibility_tree_formatter_blink.h"
 #include "content/browser/accessibility/browser_accessibility_manager.h"
 #include "ui/accessibility/ax_enums.h"
 #include "ui/gfx/geometry/rect_conversions.h"
@@ -190,7 +192,7 @@
        attr_index <= ui::AX_FLOAT_ATTRIBUTE_LAST;
        ++attr_index) {
     auto attr = static_cast<ui::AXFloatAttribute>(attr_index);
-    if (node.HasFloatAttribute(attr))
+    if (node.HasFloatAttribute(attr) && isfinite(node.GetFloatAttribute(attr)))
       dict->SetDouble(ui::ToString(attr), node.GetFloatAttribute(attr));
   }
 
diff --git a/content/browser/accessibility/accessibility_tree_formatter_win.cc b/content/browser/accessibility/accessibility_tree_formatter_win.cc
index d370745..b7af54ee 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_win.cc
+++ b/content/browser/accessibility/accessibility_tree_formatter_win.cc
@@ -4,6 +4,7 @@
 
 #include "content/browser/accessibility/accessibility_tree_formatter.h"
 
+#include <math.h>
 #include <oleacc.h>
 #include <stddef.h>
 #include <stdint.h>
@@ -837,17 +838,23 @@
   if (S_OK != QueryIAccessibleValue(node.Get(), ia2value.GetAddressOf()))
     return;  // No IA2Value, we are finished with this node.
 
-  base::win::ScopedVariant currentValue;
-  if (ia2value->get_currentValue(currentValue.Receive()) == S_OK)
-    dict->SetDouble("currentValue", V_R8(currentValue.ptr()));
+  base::win::ScopedVariant current_value;
+  if (ia2value->get_currentValue(current_value.Receive()) == S_OK &&
+      isfinite(V_R8(current_value.ptr()))) {
+    dict->SetDouble("currentValue", V_R8(current_value.ptr()));
+  }
 
-  base::win::ScopedVariant minimumValue;
-  if (ia2value->get_minimumValue(minimumValue.Receive()) == S_OK)
-    dict->SetDouble("minimumValue", V_R8(minimumValue.ptr()));
+  base::win::ScopedVariant minimum_value;
+  if (ia2value->get_minimumValue(minimum_value.Receive()) == S_OK &&
+      isfinite(V_R8(minimum_value.ptr()))) {
+    dict->SetDouble("minimumValue", V_R8(minimum_value.ptr()));
+  }
 
-  base::win::ScopedVariant maximumValue;
-  if (ia2value->get_maximumValue(maximumValue.Receive()) == S_OK)
-    dict->SetDouble("maximumValue", V_R8(maximumValue.ptr()));
+  base::win::ScopedVariant maximum_value;
+  if (ia2value->get_maximumValue(maximum_value.Receive()) == S_OK &&
+      isfinite(V_R8(maximum_value.ptr()))) {
+    dict->SetDouble("maximumValue", V_R8(maximum_value.ptr()));
+  }
 }
 
 base::string16 AccessibilityTreeFormatterWin::ProcessTreeForOutput(
diff --git a/content/browser/accessibility/browser_accessibility_cocoa.mm b/content/browser/accessibility/browser_accessibility_cocoa.mm
index a49c9b7..b817c9f 100644
--- a/content/browser/accessibility/browser_accessibility_cocoa.mm
+++ b/content/browser/accessibility/browser_accessibility_cocoa.mm
@@ -2739,7 +2739,8 @@
   if (IsMenuRelated(browserAccessibility_->GetRole()))
     [actions addObject:NSAccessibilityCancelAction];
 
-  if ([self internalRole] == ui::AX_ROLE_SLIDER) {
+  if ([self internalRole] == ui::AX_ROLE_SLIDER ||
+      [self internalRole] == ui::AX_ROLE_SPIN_BUTTON) {
     [actions addObjectsFromArray:@[
       NSAccessibilityIncrementAction, NSAccessibilityDecrementAction
     ]];
diff --git a/content/browser/accessibility/dump_accessibility_events_browsertest.cc b/content/browser/accessibility/dump_accessibility_events_browsertest.cc
index a4dad94..8d245a2a 100644
--- a/content/browser/accessibility/dump_accessibility_events_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_events_browsertest.cc
@@ -189,6 +189,11 @@
 }
 
 IN_PROC_BROWSER_TEST_F(DumpAccessibilityEventsTest,
+                       AccessibilityEventsAriaSliderValueBothChange) {
+  RunEventTest(FILE_PATH_LITERAL("aria-slider-value-both-change.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityEventsTest,
                        AccessibilityEventsAriaSliderValueChange) {
   RunEventTest(FILE_PATH_LITERAL("aria-slider-value-change.html"));
 }
@@ -199,6 +204,21 @@
 }
 
 IN_PROC_BROWSER_TEST_F(DumpAccessibilityEventsTest,
+                       AccessibilityEventsAriaSpinButtonValueBothChange) {
+  RunEventTest(FILE_PATH_LITERAL("aria-spinbutton-value-both-change.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityEventsTest,
+                       AccessibilityEventsAriaSpinButtonValueChange) {
+  RunEventTest(FILE_PATH_LITERAL("aria-spinbutton-value-change.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityEventsTest,
+                       AccessibilityEventsAriaSpinButtonValueTextChange) {
+  RunEventTest(FILE_PATH_LITERAL("aria-spinbutton-valuetext-change.html"));
+}
+
+IN_PROC_BROWSER_TEST_F(DumpAccessibilityEventsTest,
                        AccessibilityEventsAddAlert) {
   RunEventTest(FILE_PATH_LITERAL("add-alert.html"));
 }
diff --git a/content/browser/bad_message.h b/content/browser/bad_message.h
index bddb5857..cc3a0206 100644
--- a/content/browser/bad_message.h
+++ b/content/browser/bad_message.h
@@ -216,6 +216,7 @@
   RFH_INTERFACE_PROVIDER_SUPERFLUOUS = 189,
   AIRH_UNEXPECTED_BITSTREAM = 190,
   ARH_UNEXPECTED_BITSTREAM = 191,
+  RDH_NULL_CLIENT = 192,
 
   // Please add new elements here. The naming convention is abbreviated class
   // name (e.g. RenderFrameHost becomes RFH) plus a unique description of the
diff --git a/content/browser/loader/resource_dispatcher_host_impl.cc b/content/browser/loader/resource_dispatcher_host_impl.cc
index 67035a5..a677791 100644
--- a/content/browser/loader/resource_dispatcher_host_impl.cc
+++ b/content/browser/loader/resource_dispatcher_host_impl.cc
@@ -2228,6 +2228,12 @@
     const net::NetworkTrafficAnnotationTag& traffic_annotation) {
   DCHECK_EQ(mojom::kURLLoadOptionNone,
             options & ~mojom::kURLLoadOptionSynchronous);
+  if (!url_loader_client) {
+    VLOG(1) << "Killed renderer for null client";
+    bad_message::ReceivedBadMessage(requester_info->filter(),
+                                    bad_message::RDH_NULL_CLIENT);
+    return;
+  }
   bool is_sync_load = options & mojom::kURLLoadOptionSynchronous;
   OnRequestResourceInternal(requester_info, routing_id, request_id,
                             is_sync_load, request, std::move(mojo_request),
diff --git a/content/browser/media/session/media_session_controller.h b/content/browser/media/session/media_session_controller.h
index ca3a8874..3a9ea7c 100644
--- a/content/browser/media/session/media_session_controller.h
+++ b/content/browser/media/session/media_session_controller.h
@@ -44,7 +44,7 @@
 
   // Must be called when a pause occurs on the renderer side media player; keeps
   // the MediaSession instance in sync with renderer side behavior.
-  void OnPlaybackPaused();
+  virtual void OnPlaybackPaused();
 
   // MediaSessionObserver implementation.
   void OnSuspend(int player_id) override;
diff --git a/content/browser/media/session/media_session_controllers_manager.cc b/content/browser/media/session/media_session_controllers_manager.cc
index c0eecf5..6eb3dd4 100644
--- a/content/browser/media/session/media_session_controllers_manager.cc
+++ b/content/browser/media/session/media_session_controllers_manager.cc
@@ -28,8 +28,7 @@
 
 MediaSessionControllersManager::MediaSessionControllersManager(
     MediaWebContentsObserver* media_web_contents_observer)
-    : media_web_contents_observer_(media_web_contents_observer) {
-}
+    : media_web_contents_observer_(media_web_contents_observer) {}
 
 MediaSessionControllersManager::~MediaSessionControllersManager() = default;
 
diff --git a/content/browser/media/session/media_session_controllers_manager.h b/content/browser/media/session/media_session_controllers_manager.h
index 11e248e9..cf991cc 100644
--- a/content/browser/media/session/media_session_controllers_manager.h
+++ b/content/browser/media/session/media_session_controllers_manager.h
@@ -11,6 +11,7 @@
 
 #include "base/macros.h"
 #include "base/time/time.h"
+#include "content/common/content_export.h"
 #include "content/public/browser/web_contents_observer.h"  // For MediaPlayerId.
 
 namespace media {
@@ -25,9 +26,11 @@
 
 // MediaSessionControllersManager is a delegate of MediaWebContentsObserver that
 // handles MediaSessionController instances.
-class MediaSessionControllersManager {
+class CONTENT_EXPORT MediaSessionControllersManager {
  public:
   using MediaPlayerId = WebContentsObserver::MediaPlayerId;
+  using ControllersMap =
+      std::map<MediaPlayerId, std::unique_ptr<MediaSessionController>>;
 
   explicit MediaSessionControllersManager(
       MediaWebContentsObserver* media_web_contents_observer);
@@ -52,11 +55,11 @@
   void OnEnd(const MediaPlayerId& id);
 
  private:
+  friend class MediaSessionControllersManagerTest;
+
   // Weak pointer because |this| is owned by |media_web_contents_observer_|.
   MediaWebContentsObserver* const media_web_contents_observer_;
 
-  using ControllersMap =
-      std::map<MediaPlayerId, std::unique_ptr<MediaSessionController>>;
   ControllersMap controllers_map_;
 
   DISALLOW_COPY_AND_ASSIGN(MediaSessionControllersManager);
diff --git a/content/browser/media/session/media_session_controllers_manager_unittest.cc b/content/browser/media/session/media_session_controllers_manager_unittest.cc
new file mode 100644
index 0000000..6177149
--- /dev/null
+++ b/content/browser/media/session/media_session_controllers_manager_unittest.cc
@@ -0,0 +1,209 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/media/session/media_session_controllers_manager.h"
+
+#include "base/command_line.h"
+#include "build/build_config.h"
+#include "content/browser/media/session/media_session_controller.h"
+#include "content/test/test_render_view_host.h"
+#include "content/test/test_web_contents.h"
+#include "media/base/media_content_type.h"
+#include "media/base/media_switches.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::StrictMock;
+
+namespace content {
+
+namespace {
+
+class MockMediaSessionController : public MediaSessionController {
+ public:
+  MockMediaSessionController(
+      const WebContentsObserver::MediaPlayerId& id,
+      MediaWebContentsObserver* media_web_contents_observer)
+      : MediaSessionController(id, media_web_contents_observer) {}
+
+  MOCK_METHOD0(OnPlaybackPaused, void());
+};
+
+}  // namespace
+
+class MediaSessionControllersManagerTest
+    : public RenderViewHostImplTestHarness,
+      public ::testing::WithParamInterface<std::tuple<bool, bool>> {
+ public:
+  // Indices of the tuple parameters.
+  static const int kIsInternalMediaSessionEnabled = 0;
+  static const int kIsAudioFocusEnabled = 1;
+
+  void SetUp() override {
+    RenderViewHostImplTestHarness::SetUp();
+
+#if !defined(OS_ANDROID)
+    if (IsInternalMediaSessionEnabled()) {
+      base::CommandLine::ForCurrentProcess()->AppendSwitch(
+          switches::kEnableInternalMediaSession);
+    }
+    if (IsAudioFocusEnabled()) {
+      base::CommandLine::ForCurrentProcess()->AppendSwitch(
+          switches::kEnableAudioFocus);
+    }
+#endif
+
+    media_player_id_ = MediaSessionControllersManager::MediaPlayerId(
+        contents()->GetMainFrame(), 1);
+    mock_media_session_controller_ =
+        std::make_unique<StrictMock<MockMediaSessionController>>(
+            media_player_id_, contents()->media_web_contents_observer());
+    mock_media_session_controller_ptr_ = mock_media_session_controller_.get();
+    manager_ = std::make_unique<MediaSessionControllersManager>(
+        contents()->media_web_contents_observer());
+  }
+
+  bool IsInternalMediaSessionEnabled() const {
+    return std::get<kIsInternalMediaSessionEnabled>(GetParam());
+  }
+
+  bool IsAudioFocusEnabled() const {
+    return std::get<kIsAudioFocusEnabled>(GetParam());
+  }
+
+  bool IsMediaSessionEnabled() const {
+#if defined(OS_ANDROID) || defined(OS_CHROMEOS)
+    return true;
+#else
+    return IsInternalMediaSessionEnabled() || IsAudioFocusEnabled();
+#endif
+  }
+
+  MediaSessionControllersManager::ControllersMap* GetControllersMap() {
+    return &manager_->controllers_map_;
+  }
+
+  void TearDown() override {
+    manager_.reset();
+    RenderViewHostImplTestHarness::TearDown();
+  }
+
+ protected:
+  MediaSessionControllersManager::MediaPlayerId media_player_id_;
+  std::unique_ptr<StrictMock<MockMediaSessionController>>
+      mock_media_session_controller_;
+  StrictMock<MockMediaSessionController>* mock_media_session_controller_ptr_ =
+      nullptr;
+  std::unique_ptr<MediaSessionControllersManager> manager_;
+};
+
+TEST_P(MediaSessionControllersManagerTest, RequestPlayAddsSessionsToMap) {
+  EXPECT_TRUE(GetControllersMap()->empty());
+
+  EXPECT_TRUE(manager_->RequestPlay(media_player_id_, true, false,
+                                    media::MediaContentType::Transient));
+  if (!IsMediaSessionEnabled()) {
+    EXPECT_TRUE(GetControllersMap()->empty());
+  } else {
+    EXPECT_EQ(1U, GetControllersMap()->size());
+    EXPECT_TRUE(
+        manager_->RequestPlay(MediaSessionControllersManager::MediaPlayerId(
+                                  contents()->GetMainFrame(), 2),
+                              true, false, media::MediaContentType::Transient));
+    EXPECT_EQ(2U, GetControllersMap()->size());
+  }
+}
+
+TEST_P(MediaSessionControllersManagerTest, RepeatAddsOfInitializablePlayer) {
+  // If not enabled, no adds will occur, as RequestPlay returns early.
+  if (!IsMediaSessionEnabled())
+    return;
+
+  EXPECT_TRUE(GetControllersMap()->empty());
+
+  EXPECT_TRUE(manager_->RequestPlay(media_player_id_, true, false,
+                                    media::MediaContentType::Transient));
+  EXPECT_EQ(1U, GetControllersMap()->size());
+
+  EXPECT_TRUE(manager_->RequestPlay(media_player_id_, true, false,
+                                    media::MediaContentType::Transient));
+  EXPECT_EQ(1U, GetControllersMap()->size());
+}
+
+TEST_P(MediaSessionControllersManagerTest, RenderFrameDeletedRemovesHost) {
+  EXPECT_TRUE(GetControllersMap()->empty());
+
+  // Nothing should be removed if not enabled.
+  if (!IsMediaSessionEnabled()) {
+    // Artifically add controller to show early return.
+    GetControllersMap()->insert(std::make_pair(
+        media_player_id_, std::move(mock_media_session_controller_)));
+    EXPECT_EQ(1U, GetControllersMap()->size());
+
+    manager_->RenderFrameDeleted(contents()->GetMainFrame());
+    EXPECT_EQ(1U, GetControllersMap()->size());
+  } else {
+    EXPECT_TRUE(manager_->RequestPlay(media_player_id_, true, false,
+                                      media::MediaContentType::Transient));
+    EXPECT_EQ(1U, GetControllersMap()->size());
+
+    manager_->RenderFrameDeleted(contents()->GetMainFrame());
+    EXPECT_TRUE(GetControllersMap()->empty());
+  }
+}
+
+TEST_P(MediaSessionControllersManagerTest, OnPauseCallsPlaybackPaused) {
+  // Artifically add controller to show early return.
+  GetControllersMap()->insert(std::make_pair(
+      media_player_id_, std::move(mock_media_session_controller_)));
+  if (IsMediaSessionEnabled())
+    EXPECT_CALL(*mock_media_session_controller_ptr_, OnPlaybackPaused());
+
+  manager_->OnPause(media_player_id_);
+}
+
+TEST_P(MediaSessionControllersManagerTest, OnPauseIdNotFound) {
+  // If MediaSession is not enabled, we don't remove anything, nothing to check.
+  if (!IsMediaSessionEnabled())
+    return;
+
+  // Artifically add controller to show early return.
+  GetControllersMap()->insert(std::make_pair(
+      media_player_id_, std::move(mock_media_session_controller_)));
+
+  MediaSessionControllersManager::MediaPlayerId id2 =
+      MediaSessionControllersManager::MediaPlayerId(contents()->GetMainFrame(),
+                                                    2);
+  manager_->OnPause(id2);
+}
+
+TEST_P(MediaSessionControllersManagerTest, OnEndRemovesMediaPlayerId) {
+  EXPECT_TRUE(GetControllersMap()->empty());
+
+  // No op if not enabled.
+  if (!IsMediaSessionEnabled()) {
+    // Artifically add controller to show early return.
+    GetControllersMap()->insert(std::make_pair(
+        media_player_id_, std::move(mock_media_session_controller_)));
+    EXPECT_EQ(1U, GetControllersMap()->size());
+
+    manager_->OnEnd(media_player_id_);
+    EXPECT_EQ(1U, GetControllersMap()->size());
+  } else {
+    EXPECT_TRUE(manager_->RequestPlay(media_player_id_, true, false,
+                                      media::MediaContentType::Transient));
+    EXPECT_EQ(1U, GetControllersMap()->size());
+
+    manager_->OnEnd(media_player_id_);
+    EXPECT_TRUE(GetControllersMap()->empty());
+  }
+}
+
+// First bool is to indicate whether InternalMediaSession is enabled.
+// Second bool is to indicate whether AudioFocus is enabled.
+INSTANTIATE_TEST_CASE_P(MediaSessionEnabledTestInstances,
+                        MediaSessionControllersManagerTest,
+                        ::testing::Combine(::testing::Bool(),
+                                           ::testing::Bool()));
+}  // namespace content
diff --git a/content/network/http_server_properties_pref_delegate.cc b/content/network/http_server_properties_pref_delegate.cc
index 10fd007..af2aebc9 100644
--- a/content/network/http_server_properties_pref_delegate.cc
+++ b/content/network/http_server_properties_pref_delegate.cc
@@ -43,7 +43,7 @@
   // too.
   if (pref_service_->GetInitializationStatus() ==
       PrefService::INITIALIZATION_STATUS_WAITING) {
-    pref_service_->AddPrefInitObserver(base::Bind(
+    pref_service_->AddPrefInitObserver(base::BindOnce(
         [](const base::Closure& callback, bool) { callback.Run(); }, callback));
   }
 }
diff --git a/content/public/test/test_url_loader_client.h b/content/public/test/test_url_loader_client.h
index e73cfea..2b20d18f 100644
--- a/content/public/test/test_url_loader_client.h
+++ b/content/public/test/test_url_loader_client.h
@@ -26,7 +26,7 @@
 //   TestURLLoaderClient client;
 //   factory_->CreateLoaderAndStart(..., client.CreateInterfacePtr(), ...);
 //   client.RunUntilComplete();
-//   EXPECT_EQ(net::OK, client.status().error_code);
+//   EXPECT_EQ(net::OK, client.completion_status().error_code);
 //   ...
 class TestURLLoaderClient final : public mojom::URLLoaderClient {
  public:
diff --git a/content/shell/browser/layout_test/devtools_protocol_test_bindings.cc b/content/shell/browser/layout_test/devtools_protocol_test_bindings.cc
index deedca5d..9653884 100644
--- a/content/shell/browser/layout_test/devtools_protocol_test_bindings.cc
+++ b/content/shell/browser/layout_test/devtools_protocol_test_bindings.cc
@@ -4,6 +4,7 @@
 
 #include "content/shell/browser/layout_test/devtools_protocol_test_bindings.h"
 
+#include "base/command_line.h"
 #include "base/json/json_reader.h"
 #include "base/json/string_escape.h"
 #include "base/strings/string_number_conversions.h"
@@ -13,6 +14,7 @@
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
+#include "content/shell/common/layout_test/layout_test_switches.h"
 
 #if !defined(OS_ANDROID)
 #include "content/public/browser/devtools_frontend_host.h"
@@ -52,7 +54,10 @@
   if (spec.rfind(".js") != spec.length() - 3)
     return test_url;
   spec = spec.substr(0, pos + dir.length()) +
-         "resources/inspector-protocol-test.html?" + spec;
+         "resources/inspector-protocol-test.html?test=" + spec;
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kDebugDevTools))
+    spec += "&debug=true";
   *is_protocol_test = true;
   return GURL(spec);
 }
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 9e7e596d..f92337c3 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1326,6 +1326,7 @@
     "../browser/media/midi_host_unittest.cc",
     "../browser/media/session/audio_focus_manager_unittest.cc",
     "../browser/media/session/media_session_controller_unittest.cc",
+    "../browser/media/session/media_session_controllers_manager_unittest.cc",
     "../browser/media/session/media_session_impl_service_routing_unittest.cc",
     "../browser/media/session/media_session_impl_uma_unittest.cc",
     "../browser/media/session/media_session_uma_helper_unittest.cc",
diff --git a/content/test/data/accessibility/aria/aria-spinbutton-expected-android.txt b/content/test/data/accessibility/aria/aria-spinbutton-expected-android.txt
index b2cdc66a..6c8e6bc6 100644
--- a/content/test/data/accessibility/aria/aria-spinbutton-expected-android.txt
+++ b/content/test/data/accessibility/aria/aria-spinbutton-expected-android.txt
@@ -1,2 +1,3 @@
 android.webkit.WebView focusable focused scrollable
 ++android.widget.EditText role_description='spin button' clickable focusable name='Inner text' range_current_value=5
+++android.widget.EditText role_description='spin button' clickable focusable name='Inner text' range_min=1 range_max=10 range_current_value=5
diff --git a/content/test/data/accessibility/aria/aria-spinbutton-expected-mac.txt b/content/test/data/accessibility/aria/aria-spinbutton-expected-mac.txt
index 41e3b9a4..22a5970 100644
--- a/content/test/data/accessibility/aria/aria-spinbutton-expected-mac.txt
+++ b/content/test/data/accessibility/aria/aria-spinbutton-expected-mac.txt
@@ -1,3 +1,5 @@
 AXWebArea AXRoleDescription='HTML content'
 ++AXIncrementor AXRoleDescription='stepper' AXValue='5'
 ++++AXStaticText AXRoleDescription='text' AXValue='Inner text'
+++AXIncrementor AXRoleDescription='stepper' AXValue='5'
+++++AXStaticText AXRoleDescription='text' AXValue='Inner text'
diff --git a/content/test/data/accessibility/aria/aria-spinbutton-expected-win.txt b/content/test/data/accessibility/aria/aria-spinbutton-expected-win.txt
index 8241a5f..91294fe 100644
--- a/content/test/data/accessibility/aria/aria-spinbutton-expected-win.txt
+++ b/content/test/data/accessibility/aria/aria-spinbutton-expected-win.txt
@@ -1,3 +1,5 @@
-ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE ia2_hypertext='<obj0>'
-++ROLE_SYSTEM_SPINBUTTON value='5' FOCUSABLE valuetext:5 ia2_hypertext='Inner text'
+ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE ia2_hypertext='<obj0><obj1>'
+++ROLE_SYSTEM_SPINBUTTON value='5' FOCUSABLE valuetext:5 ia2_hypertext='Inner text' currentValue=5.00
+++++ROLE_SYSTEM_STATICTEXT name='Inner text' ia2_hypertext='Inner text'
+++ROLE_SYSTEM_SPINBUTTON value='5' FOCUSABLE valuetext:5 ia2_hypertext='Inner text' currentValue=5.00 minimumValue=1.00 maximumValue=10.00
 ++++ROLE_SYSTEM_STATICTEXT name='Inner text' ia2_hypertext='Inner text'
diff --git a/content/test/data/accessibility/aria/aria-spinbutton.html b/content/test/data/accessibility/aria/aria-spinbutton.html
index ae5d52c2..f5fa0ab 100644
--- a/content/test/data/accessibility/aria/aria-spinbutton.html
+++ b/content/test/data/accessibility/aria/aria-spinbutton.html
@@ -3,9 +3,15 @@
 @WIN-ALLOW:ia2_hypertext=*
 @WIN-ALLOW:value*
 @WIN-DENY:value='http://*'
+@WIN-ALLOW:currentValue*
+@WIN-ALLOW:maximumValue*
+@WIN-ALLOW:minimumValue*
 -->
 <html>
 <body>
 <div tabindex=0 role="spinbutton" aria-valuenow="5">Inner text</div>
+<div tabindex=0 role="spinbutton" aria-valuenow="5" aria-valuemin="1" aria-valuemax="10">
+  Inner text
+</div>
 </body>
 </html>
diff --git a/content/test/data/accessibility/event/aria-slider-value-both-change.html b/content/test/data/accessibility/event/aria-slider-value-both-change.html
index 7970e4c..7547c5aa 100644
--- a/content/test/data/accessibility/event/aria-slider-value-both-change.html
+++ b/content/test/data/accessibility/event/aria-slider-value-both-change.html
@@ -6,8 +6,9 @@
 <!DOCTYPE html>
 <html>
 <body>
-<div id="slider" role="slider" aria-valuenow="1" aria-valuetext="1%" 
+<div id="slider" role="slider" aria-valuenow="1" aria-valuetext="1%"
      aria-orientation="vertical">
+</div>
 <script>
   function go() {
     document.getElementById('slider').setAttribute('aria-valuenow', '2');
diff --git a/content/test/data/accessibility/event/aria-slider-value-change.html b/content/test/data/accessibility/event/aria-slider-value-change.html
index 7e241eb3..b8fc94f 100644
--- a/content/test/data/accessibility/event/aria-slider-value-change.html
+++ b/content/test/data/accessibility/event/aria-slider-value-change.html
@@ -7,6 +7,7 @@
 <html>
 <body>
 <div id="slider" role="slider" aria-valuenow="1">
+</div>
 <script>
   function go() {
     document.getElementById('slider').setAttribute('aria-valuenow', '2');
diff --git a/content/test/data/accessibility/event/aria-slider-valuetext-change.html b/content/test/data/accessibility/event/aria-slider-valuetext-change.html
index 4dcda29..d8c46bdc 100644
--- a/content/test/data/accessibility/event/aria-slider-valuetext-change.html
+++ b/content/test/data/accessibility/event/aria-slider-valuetext-change.html
@@ -6,8 +6,9 @@
 <!DOCTYPE html>
 <html>
 <body>
-<div id="slider" role="slider" aria-valuenow="1" aria-valuetext="1%" 
+<div id="slider" role="slider" aria-valuenow="1" aria-valuetext="1%"
      aria-orientation="vertical">
+</div>
 <script>
   function go() {
     document.getElementById('slider').setAttribute('aria-valuetext', '2%');
diff --git a/content/test/data/accessibility/event/aria-spinbutton-value-both-change-expected-mac.txt b/content/test/data/accessibility/event/aria-spinbutton-value-both-change-expected-mac.txt
new file mode 100644
index 0000000..f162f1c
--- /dev/null
+++ b/content/test/data/accessibility/event/aria-spinbutton-value-both-change-expected-mac.txt
@@ -0,0 +1 @@
+AXValueChanged on AXIncrementor
diff --git a/content/test/data/accessibility/event/aria-spinbutton-value-both-change-expected-win.txt b/content/test/data/accessibility/event/aria-spinbutton-value-both-change-expected-win.txt
new file mode 100644
index 0000000..2e7b489
--- /dev/null
+++ b/content/test/data/accessibility/event/aria-spinbutton-value-both-change-expected-win.txt
@@ -0,0 +1 @@
+EVENT_OBJECT_VALUECHANGE on role=ROLE_SYSTEM_SPINBUTTON value="2%"
diff --git a/content/test/data/accessibility/event/aria-spinbutton-value-both-change.html b/content/test/data/accessibility/event/aria-spinbutton-value-both-change.html
new file mode 100644
index 0000000..d87eac2c
--- /dev/null
+++ b/content/test/data/accessibility/event/aria-spinbutton-value-both-change.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<!--
+@WIN-DENY:*
+@WIN-ALLOW:EVENT_OBJECT_VALUECHANGE*
+@MAC-DENY:AXLayoutComplete*
+-->
+<html>
+<body>
+<div id="spinbutton" role="spinbutton" aria-valuenow="1" aria-valuetext="1%">
+</div>
+<script>
+  function go() {
+    document.getElementById('spinbutton').setAttribute('aria-valuenow', '2');
+    document.getElementById('spinbutton').setAttribute('aria-valuetext', '2%');
+  }
+</script>
+</body>
+</html>
diff --git a/content/test/data/accessibility/event/aria-spinbutton-value-change-expected-mac.txt b/content/test/data/accessibility/event/aria-spinbutton-value-change-expected-mac.txt
new file mode 100644
index 0000000..f162f1c
--- /dev/null
+++ b/content/test/data/accessibility/event/aria-spinbutton-value-change-expected-mac.txt
@@ -0,0 +1 @@
+AXValueChanged on AXIncrementor
diff --git a/content/test/data/accessibility/event/aria-spinbutton-value-change-expected-win.txt b/content/test/data/accessibility/event/aria-spinbutton-value-change-expected-win.txt
new file mode 100644
index 0000000..4f40b59
--- /dev/null
+++ b/content/test/data/accessibility/event/aria-spinbutton-value-change-expected-win.txt
@@ -0,0 +1 @@
+EVENT_OBJECT_VALUECHANGE on role=ROLE_SYSTEM_SPINBUTTON value="2"
diff --git a/content/test/data/accessibility/event/aria-spinbutton-value-change.html b/content/test/data/accessibility/event/aria-spinbutton-value-change.html
new file mode 100644
index 0000000..85cf91c
--- /dev/null
+++ b/content/test/data/accessibility/event/aria-spinbutton-value-change.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<!--
+@WIN-DENY:*
+@WIN-ALLOW:EVENT_OBJECT_VALUECHANGE*
+@MAC-DENY:AXLayoutComplete*
+-->
+<html>
+<body>
+<div id="spinbutton" role="spinbutton" aria-valuenow="1">
+</div>
+<script>
+  function go() {
+    document.getElementById('spinbutton').setAttribute('aria-valuenow', '2');
+  }
+</script>
+</body>
+</html>
diff --git a/content/test/data/accessibility/event/aria-spinbutton-valuetext-change-expected-mac.txt b/content/test/data/accessibility/event/aria-spinbutton-valuetext-change-expected-mac.txt
new file mode 100644
index 0000000..f162f1c
--- /dev/null
+++ b/content/test/data/accessibility/event/aria-spinbutton-valuetext-change-expected-mac.txt
@@ -0,0 +1 @@
+AXValueChanged on AXIncrementor
diff --git a/content/test/data/accessibility/event/aria-spinbutton-valuetext-change-expected-win.txt b/content/test/data/accessibility/event/aria-spinbutton-valuetext-change-expected-win.txt
new file mode 100644
index 0000000..2e7b489
--- /dev/null
+++ b/content/test/data/accessibility/event/aria-spinbutton-valuetext-change-expected-win.txt
@@ -0,0 +1 @@
+EVENT_OBJECT_VALUECHANGE on role=ROLE_SYSTEM_SPINBUTTON value="2%"
diff --git a/content/test/data/accessibility/event/aria-spinbutton-valuetext-change.html b/content/test/data/accessibility/event/aria-spinbutton-valuetext-change.html
new file mode 100644
index 0000000..b0f299e
--- /dev/null
+++ b/content/test/data/accessibility/event/aria-spinbutton-valuetext-change.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<!--
+@WIN-DENY:*
+@WIN-ALLOW:EVENT_OBJECT_VALUECHANGE*
+@MAC-DENY:AXLayoutComplete*
+-->
+<html>
+<body>
+<div id="spinbutton" role="spinbutton" aria-valuenow="1" aria-valuetext="1%">
+</div>
+<script>
+  function go() {
+    document.getElementById('spinbutton').setAttribute('aria-valuetext', '2%');
+  }
+</script>
+</body>
+</html>
diff --git a/content/test/data/accessibility/html/input-number-expected-android.txt b/content/test/data/accessibility/html/input-number-expected-android.txt
index 8ee77f0..28fe070 100644
--- a/content/test/data/accessibility/html/input-number-expected-android.txt
+++ b/content/test/data/accessibility/html/input-number-expected-android.txt
@@ -1,3 +1,4 @@
 android.webkit.WebView focusable focused scrollable
 ++android.view.View
-++++android.widget.EditText role_description='spin button' clickable editable_text focusable has_non_empty_value name='1' input_type=2 text_change_added_count=1
+++++android.widget.EditText role_description='spin button' clickable editable_text focusable has_non_empty_value name='1' input_type=2 range_current_value=1 text_change_added_count=1
+++++android.widget.EditText role_description='spin button' clickable editable_text focusable has_non_empty_value name='6' input_type=2 range_min=5 range_max=10 range_current_value=6 text_change_added_count=1
diff --git a/content/test/data/accessibility/html/input-number-expected-blink.txt b/content/test/data/accessibility/html/input-number-expected-blink.txt
index d46e2cb..787e16a 100644
--- a/content/test/data/accessibility/html/input-number-expected-blink.txt
+++ b/content/test/data/accessibility/html/input-number-expected-blink.txt
@@ -1,9 +1,16 @@
 rootWebArea
 ++genericContainer
-++++spinButton value='1'
+++++spinButton value='1' valueForRange=1.00
 ++++++genericContainer
 ++++++++staticText name='1'
 ++++++++++inlineTextBox name='1'
 ++++++spinButton
 ++++++++button
 ++++++++button
+++++spinButton value='6' valueForRange=6.00 minValueForRange=5.00 maxValueForRange=10.00
+++++++genericContainer
+++++++++staticText name='6'
+++++++++++inlineTextBox name='6'
+++++++spinButton
+++++++++button
+++++++++button
diff --git a/content/test/data/accessibility/html/input-number-expected-mac.txt b/content/test/data/accessibility/html/input-number-expected-mac.txt
index 2a7f16a0..f5cf1d4 100644
--- a/content/test/data/accessibility/html/input-number-expected-mac.txt
+++ b/content/test/data/accessibility/html/input-number-expected-mac.txt
@@ -1,3 +1,4 @@
 AXWebArea AXRoleDescription='HTML content'
 ++AXGroup AXRoleDescription='group'
 ++++AXIncrementor AXRoleDescription='stepper' AXValue='1'
+++++AXIncrementor AXRoleDescription='stepper' AXValue='6'
diff --git a/content/test/data/accessibility/html/input-number-expected-win.txt b/content/test/data/accessibility/html/input-number-expected-win.txt
index d3c5ac1c..0d89d868 100644
--- a/content/test/data/accessibility/html/input-number-expected-win.txt
+++ b/content/test/data/accessibility/html/input-number-expected-win.txt
@@ -1,3 +1,4 @@
 ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE ia2_hypertext='<obj0>'
-++IA2_ROLE_SECTION ia2_hypertext='<obj0>'
-++++ROLE_SYSTEM_SPINBUTTON value='1' FOCUSABLE valuetext:1 text-input-type:number ia2_hypertext='1'
+++IA2_ROLE_SECTION ia2_hypertext='<obj0><obj1>'
+++++ROLE_SYSTEM_SPINBUTTON value='1' FOCUSABLE valuetext:1 text-input-type:number ia2_hypertext='1' currentValue=1.00
+++++ROLE_SYSTEM_SPINBUTTON value='6' FOCUSABLE valuetext:6 text-input-type:number ia2_hypertext='6' currentValue=6.00 minimumValue=5.00 maximumValue=10.00
diff --git a/content/test/data/accessibility/html/input-number.html b/content/test/data/accessibility/html/input-number.html
index 7137262..2433542 100644
--- a/content/test/data/accessibility/html/input-number.html
+++ b/content/test/data/accessibility/html/input-number.html
@@ -5,11 +5,19 @@
 @WIN-ALLOW:text-input-type*
 @WIN-ALLOW:value*
 @WIN-DENY:value='http://*'
+@WIN-ALLOW:currentValue*
+@WIN-ALLOW:maximumValue*
+@WIN-ALLOW:minimumValue*
+@WIN-ALLOW:valuetext*
 @BLINK-ALLOW:value*
-@BLINK-DENY:value='http://*
+@BLINK-ALLOW:currentValue*
+@BLINK-ALLOW:maximumValue*
+@BLINK-ALLOW:minimumValue*
+@BLINK-ALLOW:valuetext*
 -->
 <html>
   <body>
     <input type="number" value="1">
+    <input type="number" value="6" min="5" max="10">
   </body>
 </html>
diff --git a/content/test/data/accessibility/html/input-range-expected-win.txt b/content/test/data/accessibility/html/input-range-expected-win.txt
index 63fae26..d24877b 100644
--- a/content/test/data/accessibility/html/input-range-expected-win.txt
+++ b/content/test/data/accessibility/html/input-range-expected-win.txt
@@ -1,3 +1,3 @@
 ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE ia2_hypertext='<obj0>'
 ++IA2_ROLE_SECTION ia2_hypertext='<obj0>'
-++++ROLE_SYSTEM_SLIDER FOCUSABLE IA2_STATE_HORIZONTAL xml-roles:slider valuetext:5 currentValue=5.00
\ No newline at end of file
+++++ROLE_SYSTEM_SLIDER FOCUSABLE IA2_STATE_HORIZONTAL xml-roles:slider valuetext:5 currentValue=5.00 minimumValue=1.00 maximumValue=10.00
diff --git a/content/test/data/accessibility/html/input-range.html b/content/test/data/accessibility/html/input-range.html
index b5f2aee..e5699fb 100644
--- a/content/test/data/accessibility/html/input-range.html
+++ b/content/test/data/accessibility/html/input-range.html
@@ -2,11 +2,15 @@
 @WIN-DENY:description*
 @WIN-DENY:name*
 @WIN-ALLOW:currentValue*
+@WIN-ALLOW:maximumValue*
+@WIN-ALLOW:minimumValue*
 @WIN-ALLOW:ia2_hypertext=*
 @WIN-ALLOW:valuetext*
 @WIN-ALLOW:xml-roles:*
 @BLINK-ALLOW:value*
 @BLINK-ALLOW:currentValue*
+@BLINK-ALLOW:maximumValue*
+@BLINK-ALLOW:minimumValue*
 @BLINK-ALLOW:valuetext*
 -->
 <html>
diff --git a/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothAdapter.java b/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothAdapter.java
index dbe0e697..0f94a8e 100644
--- a/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothAdapter.java
+++ b/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothAdapter.java
@@ -13,6 +13,7 @@
 import android.content.IntentFilter;
 import android.os.Build;
 import android.os.ParcelUuid;
+import android.util.SparseArray;
 
 import org.chromium.base.Log;
 import org.chromium.base.annotations.CalledByNative;
@@ -20,6 +21,7 @@
 import org.chromium.components.location.LocationUtils;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * Exposes android.bluetooth.BluetoothAdapter as necessary for C++
@@ -254,9 +256,45 @@
                 }
             }
 
-            nativeCreateOrUpdateDeviceOnScan(mNativeBluetoothAdapterAndroid,
-                    result.getDevice().getAddress(), result.getDevice(), result.getRssi(),
-                    uuid_strings, result.getScanRecord_getTxPowerLevel());
+            String[] serviceDataKeys;
+            byte[][] serviceDataValues;
+            Map<ParcelUuid, byte[]> serviceData = result.getScanRecord_getServiceData();
+            if (serviceData == null) {
+                serviceDataKeys = new String[] {};
+                serviceDataValues = new byte[][] {};
+            } else {
+                serviceDataKeys = new String[serviceData.size()];
+                serviceDataValues = new byte[serviceData.size()][];
+                int i = 0;
+                for (Map.Entry<ParcelUuid, byte[]> serviceDataItem : serviceData.entrySet()) {
+                    serviceDataKeys[i] = serviceDataItem.getKey().toString();
+                    serviceDataValues[i++] = serviceDataItem.getValue();
+                }
+            }
+
+            int[] manufacturerDataKeys;
+            byte[][] manufacturerDataValues;
+            SparseArray<byte[]> manufacturerData =
+                    result.getScanRecord_getManufacturerSpecificData();
+            if (manufacturerData == null) {
+                manufacturerDataKeys = new int[] {};
+                manufacturerDataValues = new byte[][] {};
+            } else {
+                manufacturerDataKeys = new int[manufacturerData.size()];
+                manufacturerDataValues = new byte[manufacturerData.size()][];
+                for (int i = 0; i < manufacturerData.size(); i++) {
+                    manufacturerDataKeys[i] = manufacturerData.keyAt(i);
+                    manufacturerDataValues[i] = manufacturerData.valueAt(i);
+                }
+            }
+
+            // Object can be destroyed, but Android keeps calling onScanResult.
+            if (mNativeBluetoothAdapterAndroid != 0) {
+                nativeCreateOrUpdateDeviceOnScan(mNativeBluetoothAdapterAndroid,
+                        result.getDevice().getAddress(), result.getDevice(), result.getRssi(),
+                        uuid_strings, result.getScanRecord_getTxPowerLevel(), serviceDataKeys,
+                        serviceDataValues, manufacturerDataKeys, manufacturerDataValues);
+            }
         }
 
         @Override
@@ -317,7 +355,8 @@
     // http://crbug.com/505554
     private native void nativeCreateOrUpdateDeviceOnScan(long nativeBluetoothAdapterAndroid,
             String address, Object bluetoothDeviceWrapper, int rssi, String[] advertisedUuids,
-            int txPower);
+            int txPower, String[] serviceDataKeys, Object[] serviceDataValues,
+            int[] manufacturerDataKeys, Object[] manufacturerDataValues);
 
     // Binds to BluetoothAdapterAndroid::nativeOnAdapterStateChanged
     private native void nativeOnAdapterStateChanged(
diff --git a/device/bluetooth/android/java/src/org/chromium/device/bluetooth/Wrappers.java b/device/bluetooth/android/java/src/org/chromium/device/bluetooth/Wrappers.java
index 66f0f68..c8bf2f82 100644
--- a/device/bluetooth/android/java/src/org/chromium/device/bluetooth/Wrappers.java
+++ b/device/bluetooth/android/java/src/org/chromium/device/bluetooth/Wrappers.java
@@ -23,6 +23,7 @@
 import android.content.pm.PackageManager;
 import android.os.Build;
 import android.os.ParcelUuid;
+import android.util.SparseArray;
 
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
@@ -33,6 +34,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 /**
@@ -297,6 +299,14 @@
             return mScanResult.getScanRecord().getServiceUuids();
         }
 
+        public Map<ParcelUuid, byte[]> getScanRecord_getServiceData() {
+            return mScanResult.getScanRecord().getServiceData();
+        }
+
+        public SparseArray<byte[]> getScanRecord_getManufacturerSpecificData() {
+            return mScanResult.getScanRecord().getManufacturerSpecificData();
+        }
+
         public int getScanRecord_getTxPowerLevel() {
             return mScanResult.getScanRecord().getTxPowerLevel();
         }
diff --git a/device/bluetooth/bluetooth_adapter_android.cc b/device/bluetooth/bluetooth_adapter_android.cc
index e3aa67f6..d034e36 100644
--- a/device/bluetooth/bluetooth_adapter_android.cc
+++ b/device/bluetooth/bluetooth_adapter_android.cc
@@ -16,6 +16,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "device/bluetooth/android/wrappers.h"
 #include "device/bluetooth/bluetooth_advertisement.h"
+#include "device/bluetooth/bluetooth_device.h"
 #include "device/bluetooth/bluetooth_device_android.h"
 #include "device/bluetooth/bluetooth_discovery_session_outcome.h"
 #include "jni/ChromeBluetoothAdapter_jni.h"
@@ -25,6 +26,9 @@
 using base::android::AppendJavaStringArrayToStringVector;
 using base::android::JavaParamRef;
 using base::android::JavaRef;
+using base::android::JavaByteArrayToByteVector;
+using base::android::JavaArrayOfByteArrayToStringVector;
+using base::android::JavaIntArrayToIntVector;
 
 namespace {
 // The poll interval in ms when there is no active discovery. This
@@ -176,7 +180,13 @@
         bluetooth_device_wrapper,  // Java Type: bluetoothDeviceWrapper
     int32_t rssi,
     const JavaParamRef<jobjectArray>& advertised_uuids,  // Java Type: String[]
-    int32_t tx_power) {
+    int32_t tx_power,
+    const JavaParamRef<jobjectArray>& service_data_keys,  // Java Type: String[]
+    const JavaParamRef<jobjectArray>& service_data_values,  // Java Type: byte[]
+    const JavaParamRef<jintArray>& manufacturer_data_keys,  // Java Type: int[]
+    const JavaParamRef<jobjectArray>&
+        manufacturer_data_values  // Java Type: byte[]
+    ) {
   std::string device_address = ConvertJavaStringToUTF8(env, address);
   auto iter = devices_.find(device_address);
 
@@ -204,11 +214,39 @@
     advertised_bluetooth_uuids.push_back(BluetoothUUID(std::move(uuid)));
   }
 
+  std::vector<std::string> service_data_keys_vector;
+  std::vector<std::string> service_data_values_vector;
+  AppendJavaStringArrayToStringVector(env, service_data_keys,
+                                      &service_data_keys_vector);
+  JavaArrayOfByteArrayToStringVector(env, service_data_values,
+                                     &service_data_values_vector);
+  BluetoothDeviceAndroid::ServiceDataMap service_data_map;
+  for (size_t i = 0; i < service_data_keys_vector.size(); i++) {
+    service_data_map.insert(
+        {BluetoothUUID(service_data_keys_vector[i]),
+         std::vector<uint8_t>(service_data_values_vector[i].begin(),
+                              service_data_values_vector[i].end())});
+  }
+
+  std::vector<jint> manufacturer_data_keys_vector;
+  std::vector<std::string> manufacturer_data_values_vector;
+  JavaIntArrayToIntVector(env, manufacturer_data_keys,
+                          &manufacturer_data_keys_vector);
+  JavaArrayOfByteArrayToStringVector(env, manufacturer_data_values,
+                                     &manufacturer_data_values_vector);
+  BluetoothDeviceAndroid::ManufacturerDataMap manufacturer_data_map;
+  for (size_t i = 0; i < manufacturer_data_keys_vector.size(); i++) {
+    manufacturer_data_map.insert(
+        {static_cast<uint16_t>(manufacturer_data_keys_vector[i]),
+         std::vector<uint8_t>(manufacturer_data_values_vector[i].begin(),
+                              manufacturer_data_values_vector[i].end())});
+  }
+
   int8_t clamped_tx_power = BluetoothDevice::ClampPower(tx_power);
 
   device_android->UpdateAdvertisementData(
       BluetoothDevice::ClampPower(rssi), std::move(advertised_bluetooth_uuids),
-      {} /* service_data */,
+      service_data_map, manufacturer_data_map,
       // Android uses INT32_MIN to indicate no Advertised Tx Power.
       // https://developer.android.com/reference/android/bluetooth/le/ScanRecord.html#getTxPowerLevel()
       tx_power == INT32_MIN ? nullptr : &clamped_tx_power);
diff --git a/device/bluetooth/bluetooth_adapter_android.h b/device/bluetooth/bluetooth_adapter_android.h
index d6c2ba3..5c04c34a 100644
--- a/device/bluetooth/bluetooth_adapter_android.h
+++ b/device/bluetooth/bluetooth_adapter_android.h
@@ -102,7 +102,16 @@
       int32_t rssi,
       const base::android::JavaParamRef<jobjectArray>&
           advertised_uuids,  // Java Type: String[]
-      int32_t tx_power);
+      int32_t tx_power,
+      const base::android::JavaParamRef<jobjectArray>&
+          service_data_keys,  // Java Type: String[]
+      const base::android::JavaParamRef<jobjectArray>&
+          service_data_values,  // Java Type: byte[]
+      const base::android::JavaParamRef<jintArray>&
+          manufacturer_data_keys,  // Java Type: int[]
+      const base::android::JavaParamRef<jobjectArray>&
+          manufacturer_data_values  // Java Type: byte[]
+      );
 
  protected:
   BluetoothAdapterAndroid();
diff --git a/device/bluetooth/bluetooth_adapter_mac.mm b/device/bluetooth/bluetooth_adapter_mac.mm
index b01cc27c..9384b8f 100644
--- a/device/bluetooth/bluetooth_adapter_mac.mm
+++ b/device/bluetooth/bluetooth_adapter_mac.mm
@@ -523,6 +523,8 @@
           << base::SysNSStringToUTF8([advertisement_data description]);
 
   // Get Advertised UUIDs
+  // Core Specification Supplement (CSS) v7, Part 1.1
+  // https://developer.apple.com/documentation/corebluetooth/cbadvertisementdataserviceuuidskey
   BluetoothDevice::UUIDList advertised_uuids;
   NSArray* service_uuids =
       [advertisement_data objectForKey:CBAdvertisementDataServiceUUIDsKey];
@@ -538,6 +540,8 @@
   }
 
   // Get Service Data.
+  // Core Specification Supplement (CSS) v7, Part 1.11
+  // https://developer.apple.com/documentation/corebluetooth/cbadvertisementdataservicedatakey
   BluetoothDevice::ServiceDataMap service_data_map;
   NSDictionary* service_data =
       [advertisement_data objectForKey:CBAdvertisementDataServiceDataKey];
@@ -549,14 +553,36 @@
                              std::vector<uint8_t>(bytes, bytes + length));
   }
 
+  // Get Manufacturer Data.
+  // "Size: 2 or more octets
+  // The first 2 octets contain the Company Identifier Code followed
+  // by additional manufacturer specific data"
+  // Core Specification Supplement (CSS) v7, Part 1.4
+  // https://developer.apple.com/documentation/corebluetooth/cbadvertisementdatamanufacturerdatakey
+  //
+  BluetoothDevice::ManufacturerDataMap manufacturer_data_map;
+  NSData* manufacturer_data =
+      [advertisement_data objectForKey:CBAdvertisementDataManufacturerDataKey];
+  const uint8_t* bytes = static_cast<const uint8_t*>([manufacturer_data bytes]);
+  size_t length = [manufacturer_data length];
+  if (length > 1) {
+    const uint16_t manufacturer_id = bytes[0] | bytes[1] << 8;
+    manufacturer_data_map.emplace(
+        manufacturer_id, std::vector<uint8_t>(bytes + 2, bytes + length));
+  }
+
   // Get Tx Power.
+  // "Size: 1 octet
+  //  0xXX: -127 to +127 dBm"
+  // Core Specification Supplement (CSS) v7, Part 1.5
+  // https://developer.apple.com/documentation/corebluetooth/cbadvertisementdatatxpowerlevelkey
   NSNumber* tx_power =
       [advertisement_data objectForKey:CBAdvertisementDataTxPowerLevelKey];
   int8_t clamped_tx_power = BluetoothDevice::ClampPower([tx_power intValue]);
 
   device_mac->UpdateAdvertisementData(
       BluetoothDevice::ClampPower(rssi), std::move(advertised_uuids),
-      std::move(service_data_map),
+      std::move(service_data_map), std::move(manufacturer_data_map),
       tx_power == nil ? nullptr : &clamped_tx_power);
 
   if (is_new_device) {
diff --git a/device/bluetooth/bluetooth_device.cc b/device/bluetooth/bluetooth_device.cc
index aa3e976..b8988d23 100644
--- a/device/bluetooth/bluetooth_device.cc
+++ b/device/bluetooth/bluetooth_device.cc
@@ -413,16 +413,19 @@
 
 std::string BluetoothDevice::GetIdentifier() const { return GetAddress(); }
 
-void BluetoothDevice::UpdateAdvertisementData(int8_t rssi,
-                                              UUIDList advertised_uuids,
-                                              ServiceDataMap service_data,
-                                              const int8_t* tx_power) {
+void BluetoothDevice::UpdateAdvertisementData(
+    int8_t rssi,
+    UUIDList advertised_uuids,
+    ServiceDataMap service_data,
+    ManufacturerDataMap manufacturer_data,
+    const int8_t* tx_power) {
   UpdateTimestamp();
 
   inquiry_rssi_ = rssi;
 
   device_uuids_.ReplaceAdvertisedUUIDs(std::move(advertised_uuids));
   service_data_ = std::move(service_data);
+  manufacturer_data_ = std::move(manufacturer_data);
 
   if (tx_power != nullptr) {
     inquiry_tx_power_ = *tx_power;
@@ -435,6 +438,7 @@
   inquiry_rssi_ = base::nullopt;
   device_uuids_.ClearAdvertisedUUIDs();
   service_data_.clear();
+  manufacturer_data_.clear();
   inquiry_tx_power_ = base::nullopt;
   GetAdapter()->NotifyDeviceChanged(this);
 }
diff --git a/device/bluetooth/bluetooth_device.h b/device/bluetooth/bluetooth_device.h
index 91334697..69efd03 100644
--- a/device/bluetooth/bluetooth_device.h
+++ b/device/bluetooth/bluetooth_device.h
@@ -547,6 +547,7 @@
   void UpdateAdvertisementData(int8_t rssi,
                                UUIDList advertised_uuids,
                                ServiceDataMap service_data,
+                               ManufacturerDataMap manufacturer_data,
                                const int8_t* tx_power);
 
   // Called by BluetoothAdapter when it stops discoverying.
diff --git a/device/bluetooth/bluetooth_device_unittest.cc b/device/bluetooth/bluetooth_device_unittest.cc
index 6f331c13..5d62ca3 100644
--- a/device/bluetooth/bluetooth_device_unittest.cc
+++ b/device/bluetooth/bluetooth_device_unittest.cc
@@ -45,6 +45,7 @@
 
 using UUIDSet = BluetoothDevice::UUIDSet;
 using ServiceDataMap = BluetoothDevice::ServiceDataMap;
+using ManufacturerDataMap = BluetoothDevice::ManufacturerDataMap;
 
 class BluetoothGetServiceTest : public BluetoothTest {
  public:
@@ -174,9 +175,8 @@
   EXPECT_EQ(0u, uuids.size());
 }
 
-#if defined(OS_MACOSX) || defined(OS_CHROMEOS) || defined(OS_LINUX)
-// TODO(ortuno): Enable on Android once it supports Service Data.
-// http://crbug.com/639408
+#if defined(OS_MACOSX) || defined(OS_CHROMEOS) || defined(OS_LINUX) || \
+    defined(OS_ANDROID)
 TEST_F(BluetoothTest, GetServiceDataUUIDs_GetServiceDataForUUID) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
@@ -194,6 +194,7 @@
   BluetoothDevice* device1 = SimulateLowEnergyDevice(4);
   EXPECT_TRUE(device1->GetServiceData().empty());
   EXPECT_TRUE(device1->GetServiceDataUUIDs().empty());
+  EXPECT_TRUE(device1->GetManufacturerData().empty());
 
   // Receive Advertisement with service data.
   BluetoothDevice* device2 = SimulateLowEnergyDevice(1);
@@ -204,8 +205,9 @@
             device2->GetServiceDataUUIDs());
   EXPECT_EQ(std::vector<uint8_t>({1}),
             *device2->GetServiceDataForUUID(BluetoothUUID(kTestUUIDHeartRate)));
-
-  // Receive Advertisement with no service data.
+  EXPECT_EQ(std::vector<uint8_t>({1, 2, 3, 4}),
+            *device2->GetManufacturerDataForID(kTestManufacturerId));
+  // Receive Advertisement with no service and manufacturer data.
   SimulateLowEnergyDevice(3);
 
 // TODO(crbug.com/707039): Remove #if once the BlueZ caching behavior is
@@ -223,11 +225,12 @@
 #else
   EXPECT_TRUE(device2->GetServiceData().empty());
   EXPECT_TRUE(device2->GetServiceDataUUIDs().empty());
+  EXPECT_TRUE(device2->GetManufacturerData().empty());
   EXPECT_EQ(nullptr,
             device2->GetServiceDataForUUID(BluetoothUUID(kTestUUIDHeartRate)));
 #endif
 
-  // Receive Advertisement with new service data.
+  // Receive Advertisement with new service data and empty manufacturer data.
   SimulateLowEnergyDevice(2);
 
   EXPECT_EQ(ServiceDataMap(
@@ -239,6 +242,8 @@
             device2->GetServiceDataUUIDs());
   EXPECT_EQ(std::vector<uint8_t>({}),
             *device2->GetServiceDataForUUID(BluetoothUUID(kTestUUIDHeartRate)));
+  EXPECT_EQ(std::vector<uint8_t>({}),
+            *device2->GetManufacturerDataForID(kTestManufacturerId));
   EXPECT_EQ(
       std::vector<uint8_t>({0, 2}),
       *device2->GetServiceDataForUUID(BluetoothUUID(kTestUUIDImmediateAlert)));
@@ -260,7 +265,8 @@
                          BluetoothUUID(kTestUUIDImmediateAlert)));
 #endif  // !defined(OS_LINUX) && !defined(OS_CHROMEOS)
 }
-#endif  // defined(OS_MACOSX) || defined(OS_CHROMEOS) || defined(OS_LINUX)
+#endif  // defined(OS_MACOSX) || defined(OS_CHROMEOS) || defined(OS_LINUX) ||
+        // defined(OS_ANDROID)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
 // Tests that the Advertisement Data fields are correctly updated during
@@ -288,12 +294,10 @@
   EXPECT_EQ(UUIDSet({BluetoothUUID(kTestUUIDGenericAccess),
                      BluetoothUUID(kTestUUIDGenericAttribute)}),
             device->GetUUIDs());
-#if defined(OS_MACOSX)
-  // TODO(ortuno): Enable on Android once it supports Service Data.
-  // http://crbug.com/639408
   EXPECT_EQ(ServiceDataMap({{BluetoothUUID(kTestUUIDHeartRate), {1}}}),
             device->GetServiceData());
-#endif  // defined(OS_MACOSX)
+  EXPECT_EQ(ManufacturerDataMap({{kTestManufacturerId, {1, 2, 3, 4}}}),
+            device->GetManufacturerData());
   EXPECT_EQ(ToInt8(TestTxPower::LOWEST), device->GetInquiryTxPower().value());
 
   // Receive Advertisement with no UUIDs, Service Data, or Tx Power, should
@@ -307,11 +311,8 @@
 
   EXPECT_EQ(ToInt8(TestRSSI::LOW), device->GetInquiryRSSI().value());
   EXPECT_TRUE(device->GetUUIDs().empty());
-#if defined(OS_MACOSX)
-  // TODO(ortuno): Enable on Android once it supports Service Data.
-  // http://crbug.com/639408
   EXPECT_TRUE(device->GetServiceData().empty());
-#endif  // defined(OS_MACOSX)
+  EXPECT_TRUE(device->GetManufacturerData().empty());
   EXPECT_FALSE(device->GetInquiryTxPower());
 
   // Receive Advertisement with different UUIDs, Service Data, and Tx Power,
@@ -327,14 +328,13 @@
   EXPECT_EQ(UUIDSet({BluetoothUUID(kTestUUIDImmediateAlert),
                      BluetoothUUID(kTestUUIDLinkLoss)}),
             device->GetUUIDs());
-#if defined(OS_MACOSX)
-  // TODO(ortuno): Enable on Android once it supports Service Data.
-  // http://crbug.com/639408
+
   EXPECT_EQ(ServiceDataMap(
                 {{BluetoothUUID(kTestUUIDHeartRate), std::vector<uint8_t>({})},
                  {BluetoothUUID(kTestUUIDImmediateAlert), {0, 2}}}),
             device->GetServiceData());
-#endif  // defined(OS_MACOSX)
+  EXPECT_EQ(ManufacturerDataMap({{kTestManufacturerId, {}}}),
+            device->GetManufacturerData());
   EXPECT_EQ(ToInt8(TestTxPower::LOWER), device->GetInquiryTxPower().value());
 
   // Stop discovery session, should notify of device changed.
@@ -342,6 +342,7 @@
   //    discovering.
   //  - GetUUIDs: Should not return any UUIDs.
   //  - GetServiceData: Should return empty map.
+  //  - GetMAnufacturerData: Should return empty map.
   //  - GetInquiryTxPower: Should return nullopt because we are no longer
   //    discovering.
   discovery_sessions_[0]->Stop(GetCallback(Call::EXPECTED),
@@ -353,11 +354,8 @@
 
   EXPECT_FALSE(device->GetInquiryRSSI());
   EXPECT_TRUE(device->GetUUIDs().empty());
-#if defined(OS_MACOSX)
-  // TODO(ortuno): Enable on Android once it supports Service Data.
-  // http://crbug.com/639408
   EXPECT_TRUE(device->GetServiceData().empty());
-#endif  // defined(OS_MACOSX)
+  EXPECT_TRUE(device->GetManufacturerData().empty());
   EXPECT_FALSE(device->GetInquiryTxPower());
 
   // Discover the device again with different UUIDs, should notify of device
@@ -375,12 +373,10 @@
   EXPECT_EQ(UUIDSet({BluetoothUUID(kTestUUIDGenericAccess),
                      BluetoothUUID(kTestUUIDGenericAttribute)}),
             device->GetUUIDs());
-#if defined(OS_MACOSX)
-  // TODO(ortuno): Enable on Android once it supports Service Data.
-  // http://crbug.com/639408
   EXPECT_EQ(ServiceDataMap({{BluetoothUUID(kTestUUIDHeartRate), {1}}}),
             device->GetServiceData());
-#endif  // defined(OS_MACOSX)
+  EXPECT_EQ(ManufacturerDataMap({{kTestManufacturerId, {1, 2, 3, 4}}}),
+            device->GetManufacturerData());
   EXPECT_EQ(ToInt8(TestTxPower::LOWEST), device->GetInquiryTxPower().value());
 }
 #endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
@@ -622,12 +618,10 @@
   EXPECT_EQ(UUIDSet({BluetoothUUID(kTestUUIDGenericAccess),
                      BluetoothUUID(kTestUUIDGenericAttribute)}),
             device->GetUUIDs());
-#if defined(OS_MACOSX)
-  // TODO(ortuno): Enable on Android once it supports Service Data.
-  // http://crbug.com/639408
+#if defined(OS_MACOSX) || defined(OS_ANDROID)
   EXPECT_EQ(ServiceDataMap({{BluetoothUUID(kTestUUIDHeartRate), {1}}}),
             device->GetServiceData());
-#endif  // defined(OS_MACOSX)
+#endif  // defined(OS_MACOSX) || defined(OS_ANDROID)
   EXPECT_EQ(ToInt8(TestTxPower::LOWEST), device->GetInquiryTxPower().value());
 
   // Discover services, should notify of device changed.
@@ -657,14 +651,12 @@
                      BluetoothUUID(kTestUUIDImmediateAlert),
                      BluetoothUUID(kTestUUIDHeartRate)}),
             device->GetUUIDs());
-#if defined(OS_MACOSX)
-  // TODO(ortuno): Enable on Android once it supports Service Data.
-  // http://crbug.com/639408
+#if defined(OS_MACOSX) || defined(OS_ANDROID)
   EXPECT_EQ(ServiceDataMap(
                 {{BluetoothUUID(kTestUUIDHeartRate), std::vector<uint8_t>({})},
                  {BluetoothUUID(kTestUUIDImmediateAlert), {0, 2}}}),
             device->GetServiceData());
-#endif  // defined(OS_MACOSX)
+#endif  // defined(OS_MACOSX) || defined(OS_ANDROID)
   EXPECT_EQ(ToInt8(TestTxPower::LOWER), device->GetInquiryTxPower().value());
 
   // Stop discovery session, should notify of device changed.
@@ -683,11 +675,9 @@
   EXPECT_EQ(4, observer.device_changed_count());
   EXPECT_FALSE(device->GetInquiryRSSI());
   EXPECT_EQ(UUIDSet({BluetoothUUID(kTestUUIDHeartRate)}), device->GetUUIDs());
-#if defined(OS_MACOSX)
-  // TODO(ortuno): Enable on Android once it supports Service Data.
-  // http://crbug.com/639408
+#if defined(OS_MACOSX) || defined(OS_ANDROID)
   EXPECT_EQ(ServiceDataMap(), device->GetServiceData());
-#endif  // defined(OS_MACOSX)
+#endif  // defined(OS_MACOSX) || defined(OS_ANDROID)
   EXPECT_FALSE(device->GetInquiryTxPower());
 
   // Disconnect device, should notify of device changed.
@@ -731,9 +721,7 @@
   EXPECT_EQ(UUIDSet({BluetoothUUID(kTestUUIDGenericAccess),
                      BluetoothUUID(kTestUUIDGenericAttribute)}),
             device->GetUUIDs());
-#if defined(OS_MACOSX)
-  // TODO(ortuno): Enable on Android once it supports Service Data.
-  // http://crbug.com/639408
+#if defined(OS_MACOSX) || defined(OS_ANDROID)
   EXPECT_EQ(ServiceDataMap({{BluetoothUUID(kTestUUIDHeartRate), {1}}}),
             device->GetServiceData());
 #endif  // defined(OS_MACOSX)
@@ -764,14 +752,12 @@
   EXPECT_EQ(UUIDSet({BluetoothUUID(kTestUUIDLinkLoss),
                      BluetoothUUID(kTestUUIDImmediateAlert)}),
             device->GetUUIDs());
-#if defined(OS_MACOSX)
-  // TODO(ortuno): Enable on Android once it supports Service Data.
-  // http://crbug.com/639408
+#if defined(OS_MACOSX) || defined(OS_ANDROID)
   EXPECT_EQ(ServiceDataMap(
                 {{BluetoothUUID(kTestUUIDHeartRate), std::vector<uint8_t>({})},
                  {BluetoothUUID(kTestUUIDImmediateAlert), {0, 2}}}),
             device->GetServiceData());
-#endif  // defined(OS_MACOSX)
+#endif  // defined(OS_MACOSX) || defined(OS_ANDROID)
   EXPECT_EQ(ToInt8(TestTxPower::LOWER), device->GetInquiryTxPower().value());
 
   // Discover Services, should notify of device changed.
@@ -804,9 +790,7 @@
                      BluetoothUUID(kTestUUIDImmediateAlert)}),
             device->GetUUIDs());
 
-#if defined(OS_MACOSX)
-  // TODO(ortuno): Enable on Android once it supports Service Data.
-  // http://crbug.com/639408
+#if defined(OS_MACOSX) || defined(OS_ANDROID)
   EXPECT_EQ(ServiceDataMap(
                 {{BluetoothUUID(kTestUUIDHeartRate), std::vector<uint8_t>({})},
                  {BluetoothUUID(kTestUUIDImmediateAlert), {0, 2}}}),
@@ -826,12 +810,10 @@
   EXPECT_EQ(UUIDSet({BluetoothUUID(kTestUUIDGenericAccess),
                      BluetoothUUID(kTestUUIDGenericAttribute)}),
             device->GetUUIDs());
-#if defined(OS_MACOSX)
-  // TODO(ortuno): Enable on Android once it supports Service Data.
-  // http://crbug.com/639408
+#if defined(OS_MACOSX) || defined(OS_ANDROID)
   EXPECT_EQ(ServiceDataMap({{BluetoothUUID(kTestUUIDHeartRate), {1}}}),
             device->GetServiceData());
-#endif  // defined(OS_MACOSX)
+#endif  // defined(OS_MACOSX) || defined(OS_ANDROID)
   EXPECT_EQ(ToInt8(TestTxPower::LOWEST), device->GetInquiryTxPower().value());
 
   // Stop discovery session, should notify of device changed.
@@ -848,11 +830,9 @@
 
   EXPECT_FALSE(device->GetInquiryRSSI());
   EXPECT_TRUE(device->GetUUIDs().empty());
-#if defined(OS_MACOSX)
-  // TODO(ortuno): Enable on Android once it supports Service Data.
-  // http://crbug.com/639408
+#if defined(OS_MACOSX) || defined(OS_ANDROID)
   EXPECT_TRUE(device->GetServiceData().empty());
-#endif  // defined(OS_MACOSX)
+#endif  // defined(OS_MACOSX)  || defined(OS_ANDROID)
   EXPECT_FALSE(device->GetInquiryTxPower());
 }
 #endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
diff --git a/device/bluetooth/dbus/fake_bluetooth_device_client.cc b/device/bluetooth/dbus/fake_bluetooth_device_client.cc
index b5810ab..4b11c3f 100644
--- a/device/bluetooth/dbus/fake_bluetooth_device_client.cc
+++ b/device/bluetooth/dbus/fake_bluetooth_device_client.cc
@@ -1563,9 +1563,11 @@
   properties->rssi.ReplaceValue(rssi);
 }
 
-void FakeBluetoothDeviceClient::UpdateServiceData(
+void FakeBluetoothDeviceClient::UpdateServiceAndManufacturerData(
     const dbus::ObjectPath& object_path,
-    const std::unordered_map<std::string, std::vector<uint8_t>>& service_data) {
+    const std::unordered_map<std::string, std::vector<uint8_t>>& service_data,
+    const std::unordered_map<uint16_t, std::vector<uint8_t>>&
+        manufacturer_data) {
   PropertiesMap::const_iterator iter = properties_map_.find(object_path);
   if (iter == properties_map_.end()) {
     VLOG(2) << "Fake device does not exist: " << object_path.value();
@@ -1574,16 +1576,23 @@
   Properties* properties = iter->second.get();
   DCHECK(properties);
   properties->service_data.set_valid(true);
+  properties->manufacturer_data.set_valid(true);
 
   // BlueZ caches all the previously received advertisements. To mimic BlueZ
   // caching behavior, merge the new data here with the existing data.
   // TODO(crbug.com/707039): once the BlueZ caching behavior is changed, this
   // needs to be updated as well.
-  std::unordered_map<std::string, std::vector<uint8_t>> merged_data =
+  std::unordered_map<std::string, std::vector<uint8_t>> merged_service_data =
       service_data;
-  merged_data.insert(properties->service_data.value().begin(),
-                     properties->service_data.value().end());
-  properties->service_data.ReplaceValue(merged_data);
+  merged_service_data.insert(properties->service_data.value().begin(),
+                             properties->service_data.value().end());
+  properties->service_data.ReplaceValue(merged_service_data);
+
+  std::unordered_map<uint16_t, std::vector<uint8_t>> merged_manufacturer_data =
+      manufacturer_data;
+  merged_manufacturer_data.insert(properties->manufacturer_data.value().begin(),
+                                  properties->manufacturer_data.value().end());
+  properties->manufacturer_data.ReplaceValue(merged_manufacturer_data);
 }
 
 void FakeBluetoothDeviceClient::UpdateConnectionInfo(
@@ -1811,7 +1820,9 @@
     const std::string device_address,
     const std::vector<std::string>& service_uuids,
     device::BluetoothTransport type,
-    const std::unordered_map<std::string, std::vector<uint8_t>>& service_data) {
+    const std::unordered_map<std::string, std::vector<uint8_t>>& service_data,
+    const std::unordered_map<uint16_t, std::vector<uint8_t>>&
+        manufacturer_data) {
   // Create a random device path.
   dbus::ObjectPath device_path;
   std::string id;
@@ -1858,6 +1869,11 @@
     properties->service_data.set_valid(true);
   }
 
+  if (!manufacturer_data.empty()) {
+    properties->manufacturer_data.ReplaceValue(manufacturer_data);
+    properties->manufacturer_data.set_valid(true);
+  }
+
   properties_map_.insert(std::make_pair(device_path, std::move(properties)));
   device_list_.push_back(device_path);
   for (auto& observer : observers_)
diff --git a/device/bluetooth/dbus/fake_bluetooth_device_client.h b/device/bluetooth/dbus/fake_bluetooth_device_client.h
index 37446d2..2a91399c 100644
--- a/device/bluetooth/dbus/fake_bluetooth_device_client.h
+++ b/device/bluetooth/dbus/fake_bluetooth_device_client.h
@@ -167,8 +167,9 @@
       const std::string device_address,
       const std::vector<std::string>& service_uuids,
       device::BluetoothTransport type,
-      const std::unordered_map<std::string, std::vector<uint8_t>>&
-          service_data);
+      const std::unordered_map<std::string, std::vector<uint8_t>>& service_data,
+      const std::unordered_map<uint16_t, std::vector<uint8_t>>&
+          manufacturer_data);
 
   void set_delay_start_discovery(bool value) { delay_start_discovery_ = value; }
 
@@ -176,13 +177,14 @@
   // |object_path| to |rssi|, if the fake device exists.
   void UpdateDeviceRSSI(const dbus::ObjectPath& object_path, int16_t rssi);
 
-  // Updates the service data property of fake device with object path
-  // |object_path| to merge |service_data| into the existing data,
+  // Updates the service and manufacturer data property of fake device with
+  // object path |object_path| to merge |service_data| into the existing data,
   // if the fake device exists.
-  void UpdateServiceData(
+  void UpdateServiceAndManufacturerData(
       const dbus::ObjectPath& object_path,
-      const std::unordered_map<std::string, std::vector<uint8_t>>&
-          service_data);
+      const std::unordered_map<std::string, std::vector<uint8_t>>& service_data,
+      const std::unordered_map<uint16_t, std::vector<uint8_t>>&
+          manufacturer_data);
 
   static const char kTestPinCode[];
   static const int kTestPassKey;
diff --git a/device/bluetooth/test/android/java/src/org/chromium/device/bluetooth/Fakes.java b/device/bluetooth/test/android/java/src/org/chromium/device/bluetooth/Fakes.java
index b045395..805cc91 100644
--- a/device/bluetooth/test/android/java/src/org/chromium/device/bluetooth/Fakes.java
+++ b/device/bluetooth/test/android/java/src/org/chromium/device/bluetooth/Fakes.java
@@ -15,6 +15,7 @@
 import android.os.Build;
 import android.os.ParcelUuid;
 import android.test.mock.MockContext;
+import android.util.SparseArray;
 
 import org.chromium.base.Log;
 import org.chromium.base.annotations.CalledByNative;
@@ -26,6 +27,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 /**
@@ -140,10 +142,18 @@
                     uuids.add(ParcelUuid.fromString("00001800-0000-1000-8000-00805f9b34fb"));
                     uuids.add(ParcelUuid.fromString("00001801-0000-1000-8000-00805f9b34fb"));
 
+                    HashMap<ParcelUuid, byte[]> serviceData = new HashMap<>();
+                    serviceData.put(ParcelUuid.fromString("0000180d-0000-1000-8000-00805f9b34fb"),
+                            new byte[] {1});
+
+                    SparseArray<byte[]> manufacturerData = new SparseArray<>();
+                    manufacturerData.put(0x00E0, new byte[] {0x01, 0x02, 0x03, 0x04});
+
                     mFakeScanner.mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES,
                             new FakeScanResult(new FakeBluetoothDevice(this, "01:00:00:90:1E:BE",
                                                        "FakeBluetoothDevice"),
-                                    TestRSSI.LOWEST, uuids, TestTxPower.LOWEST));
+                                    TestRSSI.LOWEST, uuids, TestTxPower.LOWEST, serviceData,
+                                    manufacturerData));
                     break;
                 }
                 case 2: {
@@ -151,10 +161,20 @@
                     uuids.add(ParcelUuid.fromString("00001802-0000-1000-8000-00805f9b34fb"));
                     uuids.add(ParcelUuid.fromString("00001803-0000-1000-8000-00805f9b34fb"));
 
+                    HashMap<ParcelUuid, byte[]> serviceData = new HashMap<>();
+                    serviceData.put(ParcelUuid.fromString("0000180d-0000-1000-8000-00805f9b34fb"),
+                            new byte[] {});
+                    serviceData.put(ParcelUuid.fromString("00001802-0000-1000-8000-00805f9b34fb"),
+                            new byte[] {0, 2});
+
+                    SparseArray<byte[]> manufacturerData = new SparseArray<>();
+                    manufacturerData.put(0x00E0, new byte[] {});
+
                     mFakeScanner.mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES,
                             new FakeScanResult(new FakeBluetoothDevice(this, "01:00:00:90:1E:BE",
                                                        "FakeBluetoothDevice"),
-                                    TestRSSI.LOWER, uuids, TestTxPower.LOWER));
+                                    TestRSSI.LOWER, uuids, TestTxPower.LOWER, serviceData,
+                                    manufacturerData));
                     break;
                 }
                 case 3: {
@@ -162,7 +182,7 @@
                     mFakeScanner.mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES,
                             new FakeScanResult(
                                     new FakeBluetoothDevice(this, "01:00:00:90:1E:BE", ""),
-                                    TestRSSI.LOW, uuids, NO_TX_POWER));
+                                    TestRSSI.LOW, uuids, NO_TX_POWER, null, null));
 
                     break;
                 }
@@ -171,7 +191,7 @@
                     mFakeScanner.mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES,
                             new FakeScanResult(
                                     new FakeBluetoothDevice(this, "02:00:00:8B:74:63", ""),
-                                    TestRSSI.MEDIUM, uuids, NO_TX_POWER));
+                                    TestRSSI.MEDIUM, uuids, NO_TX_POWER, null, null));
 
                     break;
                 }
@@ -180,7 +200,29 @@
                     mFakeScanner.mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES,
                             new FakeScanResult(
                                     new FakeBluetoothDevice(this, "01:00:00:90:1E:BE", null),
-                                    TestRSSI.HIGH, uuids, NO_TX_POWER));
+                                    TestRSSI.HIGH, uuids, NO_TX_POWER, null, null));
+                    break;
+                }
+                case 6: {
+                    ArrayList<ParcelUuid> uuids = null;
+                    mFakeScanner.mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES,
+                            new FakeScanResult(
+                                    new FakeBluetoothDevice(this, "02:00:00:8B:74:63", null),
+                                    TestRSSI.LOWEST, uuids, NO_TX_POWER, null, null));
+                    break;
+                }
+                case 7: {
+                    ArrayList<ParcelUuid> uuids = new ArrayList<ParcelUuid>(2);
+                    uuids.add(ParcelUuid.fromString("f1d0fff3-deaa-ecee-b42f-c9ba7ed623bb"));
+
+                    HashMap<ParcelUuid, byte[]> serviceData = new HashMap<>();
+                    serviceData.put(ParcelUuid.fromString("f1d0fff3-deaa-ecee-b42f-c9ba7ed623bb"),
+                            new byte[] {0, 20});
+
+                    mFakeScanner.mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES,
+                            new FakeScanResult(new FakeBluetoothDevice(
+                                                       this, "01:00:00:90:1E:BE", "U2F FakeDevice"),
+                                    TestRSSI.LOWEST, uuids, NO_TX_POWER, serviceData, null));
                     break;
                 }
             }
@@ -309,14 +351,19 @@
         private final int mRssi;
         private final int mTxPower;
         private final ArrayList<ParcelUuid> mUuids;
+        private final Map<ParcelUuid, byte[]> mServiceData;
+        private final SparseArray<byte[]> mManufacturerData;
 
-        FakeScanResult(
-                FakeBluetoothDevice device, int rssi, ArrayList<ParcelUuid> uuids, int txPower) {
+        FakeScanResult(FakeBluetoothDevice device, int rssi, ArrayList<ParcelUuid> uuids,
+                int txPower, Map<ParcelUuid, byte[]> serviceData,
+                SparseArray<byte[]> manufacturerData) {
             super(null);
             mDevice = device;
             mRssi = rssi;
             mUuids = uuids;
             mTxPower = txPower;
+            mServiceData = serviceData;
+            mManufacturerData = manufacturerData;
         }
 
         @Override
@@ -338,6 +385,16 @@
         public int getScanRecord_getTxPowerLevel() {
             return mTxPower;
         }
+
+        @Override
+        public Map<ParcelUuid, byte[]> getScanRecord_getServiceData() {
+            return mServiceData;
+        }
+
+        @Override
+        public SparseArray<byte[]> getScanRecord_getManufacturerSpecificData() {
+            return mManufacturerData;
+        }
     }
 
     /**
diff --git a/device/bluetooth/test/bluetooth_test.cc b/device/bluetooth/test/bluetooth_test.cc
index 64437f27..3d9b41c 100644
--- a/device/bluetooth/test/bluetooth_test.cc
+++ b/device/bluetooth/test/bluetooth_test.cc
@@ -59,6 +59,8 @@
     "00002903-0000-1000-8000-00805f9b34fb";
 const char BluetoothTestBase::kTestUUIDCharacteristicPresentationFormat[] =
     "00002904-0000-1000-8000-00805f9b34fb";
+// Manufacturer kTestAdapterAddress
+const unsigned short BluetoothTestBase::kTestManufacturerId = 0x00E0;
 
 BluetoothTestBase::BluetoothTestBase() : weak_factory_(this) {}
 
diff --git a/device/bluetooth/test/bluetooth_test.h b/device/bluetooth/test/bluetooth_test.h
index 562a528..7a68780 100644
--- a/device/bluetooth/test/bluetooth_test.h
+++ b/device/bluetooth/test/bluetooth_test.h
@@ -109,6 +109,8 @@
   static const char kTestUUIDClientCharacteristicConfiguration[];
   static const char kTestUUIDServerCharacteristicConfiguration[];
   static const char kTestUUIDCharacteristicPresentationFormat[];
+  // Manufacturer data
+  static const unsigned short kTestManufacturerId;
 
   BluetoothTestBase();
   ~BluetoothTestBase() override;
@@ -164,6 +166,7 @@
   //      RSSI:              kTestRSSI1
   //      Advertised UUIDs: {kTestUUIDGenericAccess, kTestUUIDGenericAttribute}
   //      Service Data:     {kTestUUIDHeartRate: [1]}
+  //      ManufacturerData: {kTestManufacturerId: [1, 2, 3, 4]}
   //      Tx Power:          kTestTxPower1
   //   2: Name: kTestDeviceName
   //      Address:           kTestDeviceAddress1
@@ -171,18 +174,21 @@
   //      Advertised UUIDs: {kTestUUIDImmediateAlert, kTestUUIDLinkLoss}
   //      Service Data:     {kTestUUIDHeartRate: [],
   //                         kTestUUIDImmediateAlert: [0, 2]}
+  //      ManufacturerData: {kTestManufacturerId: []}
   //      Tx Power:          kTestTxPower2
   //   3: Name:    kTestDeviceNameEmpty
   //      Address: kTestDeviceAddress1
   //      RSSI:    kTestRSSI3
   //      No Advertised UUIDs
   //      No Service Data
+  //      No Manufacturer Data
   //      No Tx Power
   //   4: Name:    kTestDeviceNameEmpty
   //      Address: kTestDeviceAddress2
   //      RSSI:    kTestRSSI4
   //      No Advertised UUIDs
   //      No Service Data
+  //      No Manufacturer Data
   //      No Tx Power
   //   5: No name device
   //      Address: kTestDeviceAddress1
@@ -195,6 +201,7 @@
   //      RSSI:    kTestRSSI1,
   //      No Advertised UUIDs
   //      No Service Data
+  //      No Manufacturer Data
   //      No Tx Power
   //      Supports BR/EDR and LE.
   //   7: Name:    kTestDeviceNameU2f
@@ -202,6 +209,7 @@
   //      RSSI:    kTestRSSI1,
   //      Advertised UUIDs: {kTestUUIDU2fControlPointLength}
   //      Service Data:     {kTestUUIDU2fControlPointLength: [0, 20]}
+  //      No Manufacturer Data
   //      No Tx Power
   //      Supports LE.
   virtual BluetoothDevice* SimulateLowEnergyDevice(int device_ordinal);
diff --git a/device/bluetooth/test/bluetooth_test_bluez.cc b/device/bluetooth/test/bluetooth_test_bluez.cc
index 9fe32b4..32134dd 100644
--- a/device/bluetooth/test/bluetooth_test_bluez.cc
+++ b/device/bluetooth/test/bluetooth_test_bluez.cc
@@ -123,18 +123,21 @@
   std::vector<std::string> service_uuids;
   BluetoothTransport device_type = BLUETOOTH_TRANSPORT_LE;
   std::unordered_map<std::string, std::vector<uint8_t>> service_data;
+  std::unordered_map<uint16_t, std::vector<uint8_t>> manufacturer_data;
 
   switch (device_ordinal) {
     case 1:
       service_uuids.push_back(kTestUUIDGenericAccess);
       service_uuids.push_back(kTestUUIDGenericAttribute);
       service_data[kTestUUIDHeartRate] = {0x01};
+      manufacturer_data[kTestManufacturerId] = {1, 2, 3, 4};
       break;
     case 2:
       service_uuids.push_back(kTestUUIDImmediateAlert);
       service_uuids.push_back(kTestUUIDLinkLoss);
       service_data[kTestUUIDHeartRate] = {};
       service_data[kTestUUIDImmediateAlert] = {0x00, 0x02};
+      manufacturer_data[kTestManufacturerId] = {};
       break;
     case 3:
       device_name = std::string(kTestDeviceNameEmpty);
@@ -158,8 +161,8 @@
 
   BluetoothDevice* device = adapter_->GetDevice(device_address);
   if (device) {
-    fake_bluetooth_device_client_->UpdateServiceData(GetDevicePath(device),
-                                                     service_data);
+    fake_bluetooth_device_client_->UpdateServiceAndManufacturerData(
+        GetDevicePath(device), service_data, manufacturer_data);
     return device;
   }
 
@@ -167,7 +170,7 @@
       dbus::ObjectPath(bluez::FakeBluetoothAdapterClient::kAdapterPath),
       /* name */ device_name,
       /* alias */ device_name.value_or("") + "(alias)", device_address,
-      service_uuids, device_type, service_data);
+      service_uuids, device_type, service_data, manufacturer_data);
 
   return adapter_->GetDevice(device_address);
 }
@@ -177,12 +180,14 @@
   std::string device_address = kTestDeviceAddress3;
   std::vector<std::string> service_uuids;
   std::unordered_map<std::string, std::vector<uint8_t>> service_data;
+  std::unordered_map<uint16_t, std::vector<uint8_t>> manufacturer_data;
 
   if (!adapter_->GetDevice(device_address)) {
     fake_bluetooth_device_client_->CreateTestDevice(
         dbus::ObjectPath(bluez::FakeBluetoothAdapterClient::kAdapterPath),
         device_name /* name */, device_name /* alias */, device_address,
-        service_uuids, BLUETOOTH_TRANSPORT_CLASSIC, service_data);
+        service_uuids, BLUETOOTH_TRANSPORT_CLASSIC, service_data,
+        manufacturer_data);
   }
   return adapter_->GetDevice(device_address);
 }
diff --git a/device/bluetooth/test/bluetooth_test_mac.mm b/device/bluetooth/test/bluetooth_test_mac.mm
index 463b93d..dd52798 100644
--- a/device/bluetooth/test/bluetooth_test_mac.mm
+++ b/device/bluetooth/test/bluetooth_test_mac.mm
@@ -51,6 +51,7 @@
     NSString* name,
     NSArray* uuids,
     NSDictionary* service_data,
+    NSData* manufacturer_data,
     NSNumber* tx_power) {
   NSMutableDictionary* advertisement_data(
       [NSMutableDictionary dictionaryWithDictionary:@{
@@ -71,6 +72,11 @@
                            forKey:CBAdvertisementDataServiceDataKey];
   }
 
+  if (service_data) {
+    [advertisement_data setObject:manufacturer_data
+                           forKey:CBAdvertisementDataManufacturerDataKey];
+  }
+
   if (tx_power) {
     [advertisement_data setObject:tx_power
                            forKey:CBAdvertisementDataTxPowerLevelKey];
@@ -164,6 +170,7 @@
   NSArray* uuids;
   NSNumber* rssi;
   NSDictionary* service_data;
+  NSData* manufacturer_data;
   NSNumber* tx_power;
 
   switch (device_ordinal) {
@@ -179,6 +186,9 @@
         [CBUUID UUIDWithString:@(kTestUUIDHeartRate)] :
             [NSData dataWithBytes:(unsigned char[]){1} length:1]
       };
+      manufacturer_data =
+          [NSData dataWithBytes:(unsigned char[]){0xE0, 0x00, 1, 2, 3, 4}
+                         length:6];
       tx_power = @(static_cast<int8_t>(TestTxPower::LOWEST));
       break;
     case 2:
@@ -195,6 +205,8 @@
         [CBUUID UUIDWithString:@(kTestUUIDImmediateAlert)] :
             [NSData dataWithBytes:(unsigned char[]){0, 2} length:2]
       };
+      manufacturer_data =
+          [NSData dataWithBytes:(unsigned char[]){0xE0, 0x00} length:2];
       tx_power = @(static_cast<int8_t>(TestTxPower::LOWER));
       break;
     case 3:
@@ -203,6 +215,7 @@
       rssi = @(static_cast<int8_t>(TestRSSI::LOW));
       uuids = nil;
       service_data = nil;
+      manufacturer_data = nil;
       tx_power = nil;
       break;
     case 4:
@@ -211,6 +224,7 @@
       rssi = @(static_cast<int8_t>(TestRSSI::MEDIUM));
       uuids = nil;
       service_data = nil;
+      manufacturer_data = nil;
       tx_power = nil;
       break;
     case 5:
@@ -219,6 +233,7 @@
       rssi = @(static_cast<int8_t>(TestRSSI::HIGH));
       uuids = nil;
       service_data = nil;
+      manufacturer_data = nil;
       tx_power = nil;
       break;
     default:
@@ -229,6 +244,7 @@
       rssi = nil;
       uuids = nil;
       service_data = nil;
+      manufacturer_data = nil;
       tx_power = nil;
   }
   scoped_nsobject<MockCBPeripheral> mock_peripheral([[MockCBPeripheral alloc]
@@ -239,7 +255,7 @@
              centralManager:central_manager
       didDiscoverPeripheral:[mock_peripheral peripheral]
           advertisementData:CreateAdvertisementData(name, uuids, service_data,
-                                                    tx_power)
+                                                    manufacturer_data, tx_power)
                        RSSI:rssi];
   return observer.last_device();
 }
diff --git a/docs/README.md b/docs/README.md
index 37e256bb..dc5b5826 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -102,6 +102,8 @@
 *   [Code Reviews](code_reviews.md) - Code review requirements and guidelines
 *   [Respectful Code Reviews](cr_respect.md) - A guide for code reviewers
 *   [Respectful Changes](cl_respect.md) - A guide for code authors
+*   [Tour of Continuous Integration UI](tour_of_luci_ui.md) - A tour of our
+    the user interface for LUCI, our continuous integration system.
 *   [Closure Compilation](closure_compilation.md) - The _Closure_ JavaScript
     compiler
 *   [Threading and Tasks in Chrome](threading_and_tasks.md) - How to run tasks
diff --git a/docs/images/LUCI-Build.png b/docs/images/LUCI-Build.png
new file mode 100644
index 0000000..92e9a82
--- /dev/null
+++ b/docs/images/LUCI-Build.png
Binary files differ
diff --git a/docs/images/LUCI-Builder.png b/docs/images/LUCI-Builder.png
new file mode 100644
index 0000000..4fedca1
--- /dev/null
+++ b/docs/images/LUCI-Builder.png
Binary files differ
diff --git a/docs/images/LUCI-Builders-View.png b/docs/images/LUCI-Builders-View.png
new file mode 100644
index 0000000..f4410c2
--- /dev/null
+++ b/docs/images/LUCI-Builders-View.png
Binary files differ
diff --git a/docs/images/LUCI-Console-View.png b/docs/images/LUCI-Console-View.png
new file mode 100644
index 0000000..185d505c
--- /dev/null
+++ b/docs/images/LUCI-Console-View.png
Binary files differ
diff --git a/docs/images/LUCI-Home-Hierarchy.png b/docs/images/LUCI-Home-Hierarchy.png
new file mode 100644
index 0000000..4f6b582
--- /dev/null
+++ b/docs/images/LUCI-Home-Hierarchy.png
Binary files differ
diff --git a/docs/images/LUCI-Home.png b/docs/images/LUCI-Home.png
new file mode 100644
index 0000000..2d817a7
--- /dev/null
+++ b/docs/images/LUCI-Home.png
Binary files differ
diff --git a/docs/images/LUCI-Project-Builders.png b/docs/images/LUCI-Project-Builders.png
new file mode 100644
index 0000000..77b6d46
--- /dev/null
+++ b/docs/images/LUCI-Project-Builders.png
Binary files differ
diff --git a/docs/images/LUCI-Project-Diagram.png b/docs/images/LUCI-Project-Diagram.png
new file mode 100644
index 0000000..6165eee
--- /dev/null
+++ b/docs/images/LUCI-Project-Diagram.png
Binary files differ
diff --git a/docs/images/LUCI-Project.png b/docs/images/LUCI-Project.png
new file mode 100644
index 0000000..ee87b49
--- /dev/null
+++ b/docs/images/LUCI-Project.png
Binary files differ
diff --git a/docs/images/LUCI-Search.png b/docs/images/LUCI-Search.png
new file mode 100644
index 0000000..99c42c0
--- /dev/null
+++ b/docs/images/LUCI-Search.png
Binary files differ
diff --git a/docs/images/LUCI-Site-Project.png b/docs/images/LUCI-Site-Project.png
new file mode 100644
index 0000000..02b008f
--- /dev/null
+++ b/docs/images/LUCI-Site-Project.png
Binary files differ
diff --git a/docs/tour_of_luci_ui.md b/docs/tour_of_luci_ui.md
new file mode 100644
index 0000000..e4e8fae
--- /dev/null
+++ b/docs/tour_of_luci_ui.md
@@ -0,0 +1,371 @@
+# A Tour of Continuous Integration UI
+
+This document details a tour of page layouts and site hierarchy for [LUCI
+UI](https://ci.chromium.org/p/chromium), Chromium's continuous integration user
+interface. Currently, LUCI shows both Buildbot and LUCI builds. In the near
+future, LUCI will replace Buildbot as the default continuous integration system.
+Read this document to learn how to navigate LUCI and to better understand the
+new UX concepts LUCI introduces. Refer to the [FAQ](#FAQ) to quickly jump to
+sections.
+
+[TOC]
+
+## FAQ
+
+**Where can I see the console view for my builders?**
+
+*Using this URL schema, `ci.chromium.org/p/<project_id>/g/<group_id>/console`,
+replace `<project_id>` with the project ID and `<group_id>` with the group ID.
+For example, [Chromium Main](http://ci.chromium.org/p/chromium/g/main/console).
+See [console view page](#Console-view-page) section below for details on the
+page.*
+
+**Why are we replacing Buildbot with LUCI?**
+
+*Buildbot is slow and doesn't scale well. LUCI fixes this and over time, we have
+already been swapping out Buildbot responsibilities with LUCI services.  See
+[Background section](#Background) for more details.*
+
+**What are the LUCI pages I should expect to see?**
+
+*[Jump](#Tour-of-LUCI-Pages) to the Tour of LUCI Pages section to see details on
+each page.*
+
+**Is LUCI only for the Chromium project?**
+
+_The overall goal is to have LUCI serve all projects (wherever possible) that
+Buildbot currently serves. The 11/30 UI rollout is limited to chromium.* masters
+and tryserver.chromium.* + tryserver.blink masters. Other projects/masters will
+switch to LUCI UI at later notice._
+
+**Is there a list of Known Issues?**
+
+_A list of known user interface issues can be viewed in [Chromium
+bugs](https://bugs.chromium.org/p/chromium/issues/list?q=label%3Aluci-knownissues-ui)._
+
+**What happened to Masters?**
+
+_LUCI no longer uses Masters and distributes the responsibility of scheduling,
+distributing, collecting, archiving and logging builds into separate independent
+services. As a UI concept, "masters" are replaced with "groups" and "views". See
+[Background section](#Background) for more details._
+
+**What is a Group?**
+
+_Builders of each project are organized into groups of ordered builders (A
+single builder can be referenced by multiple groups). See [Site Hierarchy
+section](#Site-Hierarchy) for more details._
+
+**What is a View?**
+
+*A group of builders and their builds can be visualized in a couple ways; we
+call these views. See [Site Hierarchy section](#Site-Hierarchy) for more
+details.*
+
+**Are URLs final?**
+
+*No. URLs and pages are subject to change. Our initial goal is to provide
+Buildbot functionality parity, but we are committed to building additional
+enhanced user experiences on top of the new LUCI UI.*
+
+**How do I provide feedback?**
+
+*LUCI UI is still in development and we would love to get feedback, please see
+[feedback section](#Feedback) for links.*
+
+
+## Tour of LUCI Pages
+
+**Please note** that URLs and pages are subject to change. Our initial goal is
+to provide Buildbot functionality, but we are committed to building additional
+enhanced user experiences on top of the new LUCI UI.
+
+
+### High level pages
+
+
+#### Home
+
+**URL:** [ci.chromium.org](http://ci.chromium.org/)
+
+This is the "Home" page for LUCI. It contains a listing of all of the projects
+configured in LUCI.
+
+![Home page for LUCI](images/LUCI-Home.png "LUCI Home Page")
+
+
+#### Search
+
+**URL:** [ci.chromium.org/search](http://ci.chromium.org/search)
+
+This is the builder search page for LUCI. Find a specific builder serviced by
+LUCI by name. Search results are sorted by bucket and groups. This can also be
+accessed by typing "ci.<tab>" in the Chrome Omnibox.
+
+![Search page for LUCI](images/LUCI-Search.png "LUCI Search Page")
+
+#### Project page
+
+**URL:** `ci.chromium.org/p/<project_id>`
+
+Example: [ci.chromium.org/p/chromium](http://ci.chromium.org/p/chromium)
+
+A list of the groups defined for the project. A group is an ordered list of
+builders (Builders can be referenced by multiple groups). This page contains
+links to the default view defined for each group and the
+last-completed-build-status for each builder.
+
+Refer to the build results [color key](#Color-Key) on how to interpret the build
+status.
+
+**Note:** Initially, we have defined "groups" of builders corresponding to a
+Buildbot master and included builders that used to be attached to it. 
+
+![An example project page on LUCI](images/LUCI-Project.png "LUCI Project Page")
+
+
+#### Builders list page per project
+
+**URL:** `ci.chromium.org/p/<project_id>/builders`
+
+Example:
+[ci.chromium.org/p/chromium/builders](http://ci.chromium.org/p/chromium/builders)
+
+Shows a listing of all builders belonging to the *`<project_id>`*. Each builder
+shows number of builds pending, in-progress and the build statuses of the last
+30 recently completed builds by default (option available to show more).
+
+Refer to the build results [color key](#Color-Key) on how to interpret the build
+status.
+
+![An example builders per project page](images/LUCI-Project-Builders.png "LUCI
+builders page per project")
+
+
+### Project Resource pages
+
+These pages display a singular resource that *belongs to* the project
+(currently, Builders and Build Results).
+
+
+#### Builder page
+
+**Buildbot Builder URL:** `ci.chromium.org/buildbot/<group_id>/<builder_name>`
+
+**LUCI Builder URL:**
+`ci.chromium.org/p/<project_id>/builders/<bucket>/<builder_name>`
+
+This is the page describing the builder and lists machine pool, current builds,
+pending builds and recent builds completed. The layout is equivalent to Buildbot
+layout of builder pages.
+
+![An example builder page](images/LUCI-Builder.png "LUCI Builder Page")
+
+
+#### Build Results page
+
+**Buildbot Build URL:** `
+ci.chromium.org/buildbot/<group_id>/<builder_name>/<build_id>`
+
+**LUCI Build URL:**
+`ci.chromium.org/p/<project_id>/builders/<bucket>/<builder_name>/<build_id>`
+
+This is the page describing the build and results. Contains build info,
+properties, result status, blame-list, steps and links to log files. The layout
+is equivalent to Buildbot layout of build result pages 
+
+![An example build result page](images/LUCI-Build.png "LUCI Build Page")
+
+
+### View pages
+
+Each group has multiple views (currently "console" and "builders" views).
+Later, we may add additional views (e.g. "stats"). Views are the primary reason
+to create a group; For example, you put builders into the "main" group so that
+they show up on main/console.
+
+
+#### Console view page
+
+**URL:** `ci.chromium.org/p/<project_id>/g/<group_id>/console`
+
+Example:
+[ci.chromium.org/p/chromium/g/main/console](http://ci.chromium.org/p/chromium/g/main/console)
+
+A high-level overview of the recently completed builds. Contains most relevant
+information on the Group, including tree status, on-call information, important
+links, sub groups, and builds ordered by latest commits by users over all
+platform builders. Refer to the build results [color key](#Color-Key) on how to
+interpret the build status.
+
+![An example console view page for a group](images/LUCI-Console-View.png "LUCI
+console view page")
+
+
+**Tree Status**
+
+The "tree" represents the various source repositories used to build the project,
+e.g.  chromium/src.git plus its
+[DEPS](http://src.chromium.org/viewvc/chrome/trunk/src/DEPS?view=markup) file.
+**Tree status **displays the state of the tree corresponding to the project and
+determines whether or not developers are allowed to commit to the repositories.
+The tree can be "open", "closed" or "throttled". The normal state is open. When
+vital builders fail or tests break, the tree is closed by putting the word
+"closed" in the tree status;
+[PRESUBMIT.py](http://src.chromium.org/viewvc/chrome/trunk/src/PRESUBMIT.py?view=markup)
+checks the status and will block commits, and the build sheriff will act to fix
+the tree. When the tree is throttled, commits are only allowed with specific
+permission from the build sheriff, generally because the sheriff wants to make
+sure the tree is stable before opening it up to unlimited commits. 
+
+
+**On-call Info**
+
+This is the list of list the current build sheriffs and troopers. The sheriffs
+have overall responsibility in case someone else is away or not paying
+attention.
+
+
+**Commits/CLs**
+
+Each time someone lands a change, the scheduler gathers changes and schedules
+builds and tests on all relevant builders. Each row in the table represents a
+commit and resulting builds across the various builders. The columns are sorted
+by build configuration and platform. A build can span multiple commits, in the
+event that commits land faster than the builder can cycle. At the start of each
+build, a yellow box is displayed. Clicking on the box shows more information
+about the build, including the "blamelist" of changes that went into it and
+detailed step/log information for the build (See [build
+results](#Build-Results-page) page for more details). The times shown in the
+table are in U.S. Pacific time.
+
+Refer to the build results [color key](#Color-Key) on how to interpret the build
+status.
+
+
+#### Builders view page
+
+**URL:** `ci.chromium.org/p/<project_id>/g/<group_id>/builders`
+
+Example:
+[ci.chromium.org/p/chromium/g/main/builders](http://ci.chromium.org/p/chromium/g/main/builders)
+
+Builders view page for the group_id under project_id. Shows all builders of this
+group. Each builder shows number of builds pending, in-progress and the build
+statuses of the last 30 recently completed builds by default (option available
+to show more).
+
+![An example builders view page for a group](images/LUCI-Builders-View.png "LUCI
+builders view Page")
+
+
+### Color Key 
+
+Throughout LUCI, we visualize a build results using colored boxes, where the
+color signifies the build result status. Below is the color key mapping.
+
+*   <span style="color:yellow">Yellow</span> = in progress
+*   <span style="color:green">Green</span> = finished successfully
+*   <span style="color:red">Red</span> = finished with errors
+*   <span style="color:purple">Purple</span> = internal error.
+
+## Background
+
+LUCI which stands for Layered Universal Continuous Integration is a replacement
+for Buildbot (our existing single-threaded monolithic continuous integration
+system). LUCI is made up of a number of independent services that work together,
+each service dealing with one part of the continuous integration stack. Over
+time, we have been swapping out Buildbot responsibilities with LUCI services. As
+such, most builds today on Buildbot are already running using LUCI services.
+
+LUCI tries to separate each CI concern into a separate service. Swarming, for
+example, is one such service in LUCI, and handles job distribution. One of the
+technical limitations of Buildbot is that each set of builders needed to be
+owned by a single "master" process. This master would be responsible for
+scheduling, distributing, collecting, archiving and logging builds. LUCI
+distributes the responsibility of this work into separate independent services,
+negating the need for a master. For ease of migration from Buildbot masters to
+LUCI builders, we have defined "groups" of builders that each correspond to a
+single Buildbot master, and we have kept the LUCI Builder names the same as they
+were in Buildbot.
+
+For more an overview of the services that make up LUCI, take a look at the [LUCI
+Overview
+presentation](https://docs.google.com/presentation/d/1dhUecmBf7IZ3moy_SflNT7yBYeiJbvj7m9kAxqeN9-A/preview?slide=id.g6571e2aaf_0_95).
+
+
+## Site Hierarchy
+
+On the highest level, LUCI is organized by **projects**. A project contains all
+the configuration necessary to do development on a given repo, for example,
+Chromium is one of these projects (corresponding to chromium/src.git). Each
+project contains **builders**, which describe how a given builder works (i.e.
+recipe to run, gn args to use, etc.). Each **builder** has **builds**, which
+contain build information, build properties details, success or failure status,
+blamelists, steps and links to log files.
+
+![Project layout diagram](images/LUCI-Site-Project.png)
+
+
+For the LUCI UI, to organize builders in each project, we introduce the idea of
+**groups**. Builders of each project are organized into **groups **of ordered
+builders (A single builder can be referenced by multiple groups). For example,
+"main" might contain all of the main builders, and "gpu" might contain all of
+the "gpu" builders but since groups can overlap, (so "main" can contain some
+subset of the "gpu" builders). Notably, groups don't own any project resources,
+they're simply a mechanism to group them for display purposes. Projects may have
+as many groups as they like, for whatever purpose they need them.
+
+A group of builders and their builds can be visualized in a couple ways; we call
+these **views**. We have currently implemented 2 views; a **console view** and a
+**builders view** for each group.
+
+![Project hierarchy diagram](images/LUCI-Project-Diagram.png)
+
+
+To get started with LUCI, go to [ci.chromium.org](http://ci.chromium.org). Drill
+down to build results from the list of projects. The search page is available to
+find a specific builder by name.
+
+![LUCI Home page site layout](images/LUCI-Home-Hierarchy.png "LUCI Home")
+
+
+## Known Issues
+
+A list of [known
+issues](https://bugs.chromium.org/p/chromium/issues/list?q=label%3Aluci-knownissues-ui)
+for the user interface of LUCI is available under Chromium bugs.
+
+Note: URLs and pages are subject to change. Our initial goal is to provide
+Buildbot user functionality parity, but we are committed to building additional
+enhanced user experiences on top of the new LUCI UI.
+
+## Help
+
+The Chrome Operations Foundation team is responsible for the design and
+development of LUCI. If you have any questions or need help on usage, feel free
+to reach out to the Chrome Operations team by emailing us at
+[infra-dev@chromium.org](mailto:infra-dev@chromium.org)
+
+
+## Feedback
+
+LUCI UI is still in development. Our initial goal is to provide Buildbot
+functionality parity, but we are committed to building additional enhanced user
+experiences on top of the new LUCI UI once we no longer need to support
+Buildbot.
+
+If you have specific feedback you would like to share with us, we would love to
+hear it and incorporate it into our ongoing UI improvements.
+
+Use the **feedback button** on a LUCI page.
+
+For **feature requests or bugs**, please file a crbug using the following
+[template](https://bugs.chromium.org/p/chromium/issues/entry?labels=LUCI-M0-Backlog&summary=[LUCI-Feedback-UI]%20Enter%20an%20one-line%20summary&components=Infra>Platform>Milo&cc=efoo@chromium.org,estaab@chromium.org,nodir@chromium.org&description=Please%20use%20this%20to%20template%20to%20file%20a%20feature%20request%20into%20LUCI%20backlog.%20%20%0A%0AReminder%20to%20include%20the%20following%3A%0A-%20Description%0A-%20Priority%0A-%20Why%20this%20feature%20is%20needed).
+
+To **share your feedback**, please fill out this [short
+survey](https://goo.gl/forms/YPO6XCQ3q47r00iw2).
+
+**Contact us** directly by emailing us at
+[infra-dev@chromium.org](mailto:infra-dev@chromium.org).
+
diff --git a/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc b/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc
index b0e095a3..3fe9764 100644
--- a/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc
+++ b/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc
@@ -221,7 +221,7 @@
     device_test_->SetDeviceProperty(kCellularDevicePath, shill::kMinProperty,
                                     base::Value("test_min"));
     device_test_->SetDeviceProperty(kCellularDevicePath,
-                                    shill::kModelIDProperty,
+                                    shill::kModelIdProperty,
                                     base::Value("test_model_id"));
     std::unique_ptr<base::DictionaryValue> apn =
         DictionaryBuilder()
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index 9d3aa023..93c5a483 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1269,6 +1269,7 @@
   DEVELOPERPRIVATE_NOTIFYDRAGINSTALLINPROGRESS,
   AUTOTESTPRIVATE_GETPRINTERLIST,
   DEVELOPERPRIVATE_GETEXTENSIONSIZE,
+  CRYPTOTOKENPRIVATE_ISAPPIDHASHINENTERPRISECONTEXT,
   // Last entry: Add new entries above, then run:
   // python tools/metrics/histograms/update_extension_histograms.py
   ENUM_BOUNDARY
diff --git a/ios/chrome/app/chrome_exe_main.mm b/ios/chrome/app/chrome_exe_main.mm
index 5c49215..d6cbbb0f 100644
--- a/ios/chrome/app/chrome_exe_main.mm
+++ b/ios/chrome/app/chrome_exe_main.mm
@@ -6,6 +6,7 @@
 
 #include "base/at_exit.h"
 #include "base/debug/crash_logging.h"
+#include "base/strings/sys_string_conversions.h"
 #include "components/crash/core/common/crash_keys.h"
 #include "ios/chrome/app/startup/ios_chrome_main.h"
 #include "ios/chrome/browser/crash_report/breakpad_helper.h"
@@ -24,9 +25,9 @@
 void StartCrashController() {
   @autoreleasepool {
     std::string channel_string = GetChannelString();
-
     RegisterChromeIOSCrashKeys();
-    base::debug::SetCrashKeyValue(crash_keys::kChannel, channel_string);
+    breakpad_helper::AddReportParameter(
+        @"channel", base::SysUTF8ToNSString(channel_string), false);
     breakpad_helper::Start(channel_string);
   }
 }
diff --git a/ios/chrome/browser/crash_report/crash_keys.cc b/ios/chrome/browser/crash_report/crash_keys.cc
index e2886be..9294980 100644
--- a/ios/chrome/browser/crash_report/crash_keys.cc
+++ b/ios/chrome/browser/crash_report/crash_keys.cc
@@ -14,8 +14,6 @@
   // The following keys may be chunked by the underlying crash logging system,
   // but ultimately constitute a single key-value pair.
   base::debug::CrashKey fixed_keys[] = {
-      {crash_keys::kChannel, crash_keys::kSmallSize},
-      {crash_keys::kMetricsClientId, crash_keys::kSmallSize},
       {crash_keys::kNumVariations, crash_keys::kSmallSize},
       {crash_keys::kVariations, crash_keys::kHugeSize},
   };
diff --git a/ios/web/download/download_task_impl.h b/ios/web/download/download_task_impl.h
index 3faea62..8a5948e 100644
--- a/ios/web/download/download_task_impl.h
+++ b/ios/web/download/download_task_impl.h
@@ -55,6 +55,7 @@
   void ShutDown();
 
   // DownloadTask overrides:
+  DownloadTask::State GetState() const override;
   void Start(std::unique_ptr<net::URLFetcherResponseWriter> writer) override;
   net::URLFetcherResponseWriter* GetResponseWriter() const override;
   NSString* GetIndentifier() const override;
@@ -99,9 +100,9 @@
   base::ObserverList<DownloadTaskObserver, true> observers_;
 
   // Back up corresponding public methods of DownloadTask interface.
+  State state_ = State::kNotStarted;
   std::unique_ptr<net::URLFetcherResponseWriter> writer_;
   GURL original_url_;
-  bool is_done_ = false;
   int error_code_ = 0;
   int http_code_ = -1;
   int64_t total_bytes_ = -1;
diff --git a/ios/web/download/download_task_impl.mm b/ios/web/download/download_task_impl.mm
index a80eb07e..7bee9bc 100644
--- a/ios/web/download/download_task_impl.mm
+++ b/ios/web/download/download_task_impl.mm
@@ -161,11 +161,18 @@
   delegate_ = nullptr;
 }
 
+DownloadTask::State DownloadTaskImpl::GetState() const {
+  DCHECK_CURRENTLY_ON(web::WebThread::UI);
+  return state_;
+}
+
 void DownloadTaskImpl::Start(
     std::unique_ptr<net::URLFetcherResponseWriter> writer) {
   DCHECK_CURRENTLY_ON(web::WebThread::UI);
   DCHECK(!writer_);
+  DCHECK_EQ(state_, State::kNotStarted);
   writer_ = std::move(writer);
+  state_ = State::kInProgress;
   GetCookies(base::Bind(&DownloadTaskImpl::StartWithCookies,
                         weak_factory_.GetWeakPtr()));
 }
@@ -187,7 +194,7 @@
 
 bool DownloadTaskImpl::IsDone() const {
   DCHECK_CURRENTLY_ON(web::WebThread::UI);
-  return is_done_;
+  return state_ == State::kComplete;
 }
 
 int DownloadTaskImpl::GetErrorCode() const {
@@ -329,7 +336,7 @@
 }
 
 void DownloadTaskImpl::OnDownloadFinished(int error_code) {
-  is_done_ = true;
+  state_ = State::kComplete;
   session_task_ = nil;
   OnDownloadUpdated();
 }
diff --git a/ios/web/download/download_task_impl_unittest.mm b/ios/web/download/download_task_impl_unittest.mm
index 1c815e0..52a9b3e 100644
--- a/ios/web/download/download_task_impl_unittest.mm
+++ b/ios/web/download/download_task_impl_unittest.mm
@@ -191,6 +191,7 @@
 
 // Tests DownloadTaskImpl default state after construction.
 TEST_F(DownloadTaskImplTest, DefaultState) {
+  EXPECT_EQ(DownloadTask::State::kNotStarted, task_->GetState());
   EXPECT_FALSE(task_->GetResponseWriter());
   EXPECT_NSEQ(task_delegate_.configuration().identifier,
               task_->GetIndentifier());
@@ -222,6 +223,7 @@
   ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForDownloadTimeout, ^{
     return task_->IsDone();
   }));
+  EXPECT_EQ(DownloadTask::State::kComplete, task_->GetState());
   EXPECT_EQ(0, task_->GetErrorCode());
   EXPECT_EQ(0, task_->GetTotalBytes());
   EXPECT_EQ(100, task_->GetPercentComplete());
@@ -244,6 +246,7 @@
   session_task.countOfBytesExpectedToReceive = kDataSize;
   SimulateDataDownload(session_task, kData);
   testing::Mock::VerifyAndClearExpectations(&task_observer_);
+  EXPECT_EQ(DownloadTask::State::kInProgress, task_->GetState());
   EXPECT_FALSE(task_->IsDone());
   EXPECT_EQ(0, task_->GetErrorCode());
   EXPECT_EQ(kDataSize, task_->GetTotalBytes());
@@ -257,6 +260,7 @@
   ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForDownloadTimeout, ^{
     return task_->IsDone();
   }));
+  EXPECT_EQ(DownloadTask::State::kComplete, task_->GetState());
   EXPECT_EQ(0, task_->GetErrorCode());
   EXPECT_EQ(kDataSize, task_->GetTotalBytes());
   EXPECT_EQ(100, task_->GetPercentComplete());
@@ -282,6 +286,7 @@
   session_task.countOfBytesExpectedToReceive = kData1Size + kData2Size;
   SimulateDataDownload(session_task, kData1);
   testing::Mock::VerifyAndClearExpectations(&task_observer_);
+  EXPECT_EQ(DownloadTask::State::kInProgress, task_->GetState());
   EXPECT_FALSE(task_->IsDone());
   EXPECT_EQ(0, task_->GetErrorCode());
   EXPECT_EQ(kData1Size + kData2Size, task_->GetTotalBytes());
@@ -294,6 +299,7 @@
   EXPECT_CALL(task_observer_, OnDownloadUpdated(task_.get()));
   SimulateDataDownload(session_task, kData2);
   testing::Mock::VerifyAndClearExpectations(&task_observer_);
+  EXPECT_EQ(DownloadTask::State::kInProgress, task_->GetState());
   EXPECT_FALSE(task_->IsDone());
   EXPECT_EQ(0, task_->GetErrorCode());
   EXPECT_EQ(kData1Size + kData2Size, task_->GetTotalBytes());
@@ -307,6 +313,7 @@
   ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForDownloadTimeout, ^{
     return task_->IsDone();
   }));
+  EXPECT_EQ(DownloadTask::State::kComplete, task_->GetState());
   EXPECT_EQ(0, task_->GetErrorCode());
   EXPECT_EQ(kData1Size + kData2Size, task_->GetTotalBytes());
   EXPECT_EQ(100, task_->GetPercentComplete());
@@ -332,6 +339,7 @@
   ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForDownloadTimeout, ^{
     return task_->IsDone();
   }));
+  EXPECT_EQ(DownloadTask::State::kComplete, task_->GetState());
   EXPECT_TRUE(task_->GetErrorCode() == net::ERR_INTERNET_DISCONNECTED);
   EXPECT_EQ(0, task_->GetTotalBytes());
   EXPECT_EQ(100, task_->GetPercentComplete());
@@ -355,6 +363,7 @@
   session_task.countOfBytesExpectedToReceive = kExpectedDataSize;
   SimulateDataDownload(session_task, kReceivedData);
   testing::Mock::VerifyAndClearExpectations(&task_observer_);
+  EXPECT_EQ(DownloadTask::State::kInProgress, task_->GetState());
   EXPECT_FALSE(task_->IsDone());
   EXPECT_EQ(0, task_->GetErrorCode());
   EXPECT_EQ(kExpectedDataSize, task_->GetTotalBytes());
@@ -372,6 +381,7 @@
   ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForDownloadTimeout, ^{
     return task_->IsDone();
   }));
+  EXPECT_EQ(DownloadTask::State::kComplete, task_->GetState());
   EXPECT_TRUE(task_->GetErrorCode() == net::ERR_INTERNET_DISCONNECTED);
   EXPECT_EQ(kExpectedDataSize, task_->GetTotalBytes());
   EXPECT_EQ(23, task_->GetPercentComplete());
diff --git a/ios/web/public/download/download_task.h b/ios/web/public/download/download_task.h
index 6cd5f66..6472ce0 100644
--- a/ios/web/public/download/download_task.h
+++ b/ios/web/public/download/download_task.h
@@ -27,6 +27,20 @@
 // stores all the state for a download. Must be used on the UI thread.
 class DownloadTask {
  public:
+  enum class State {
+    // Download has not started yet.
+    kNotStarted = 0,
+
+    // Download is actively progressing.
+    kInProgress,
+
+    // Download is completely finished.
+    kComplete,
+  };
+
+  // Returns the download task state.
+  virtual State GetState() const = 0;
+
   // Starts the download. |writer| allows clients to perform in-memory or
   // in-file downloads and must not be null. Start() can only be called once.
   virtual void Start(std::unique_ptr<net::URLFetcherResponseWriter> writer) = 0;
diff --git a/ios/web/public/test/fakes/fake_download_task.h b/ios/web/public/test/fakes/fake_download_task.h
index 0f24728..30f94dff 100644
--- a/ios/web/public/test/fakes/fake_download_task.h
+++ b/ios/web/public/test/fakes/fake_download_task.h
@@ -21,6 +21,7 @@
   ~FakeDownloadTask() override;
 
   // DownloadTask overrides:
+  DownloadTask::State GetState() const override;
   void Start(std::unique_ptr<net::URLFetcherResponseWriter> writer) override;
   net::URLFetcherResponseWriter* GetResponseWriter() const override;
   NSString* GetIndentifier() const override;
@@ -52,9 +53,9 @@
 
   base::ObserverList<DownloadTaskObserver, true> observers_;
 
+  State state_ = State::kNotStarted;
   std::unique_ptr<net::URLFetcherResponseWriter> writer_;
   GURL original_url_;
-  bool is_done_ = false;
   int error_code_ = 0;
   int http_code_ = -1;
   std::string content_disposition_;
diff --git a/ios/web/public/test/fakes/fake_download_task.mm b/ios/web/public/test/fakes/fake_download_task.mm
index 3eb5213..57082b6 100644
--- a/ios/web/public/test/fakes/fake_download_task.mm
+++ b/ios/web/public/test/fakes/fake_download_task.mm
@@ -19,6 +19,10 @@
 
 FakeDownloadTask::~FakeDownloadTask() = default;
 
+DownloadTask::State FakeDownloadTask::GetState() const {
+  return state_;
+}
+
 void FakeDownloadTask::Start(
     std::unique_ptr<net::URLFetcherResponseWriter> writer) {
   writer_ = std::move(writer);
@@ -38,7 +42,7 @@
 }
 
 bool FakeDownloadTask::IsDone() const {
-  return is_done_;
+  return state_ == State::kComplete;
 }
 
 int FakeDownloadTask::GetErrorCode() const {
@@ -80,7 +84,7 @@
 }
 
 void FakeDownloadTask::SetDone(bool done) {
-  is_done_ = done;
+  state_ = State::kComplete;
   OnDownloadUpdated();
 }
 
diff --git a/services/preferences/public/cpp/pref_service_factory.cc b/services/preferences/public/cpp/pref_service_factory.cc
index 0198885..38a65f58 100644
--- a/services/preferences/public/cpp/pref_service_factory.cc
+++ b/services/preferences/public/cpp/pref_service_factory.cc
@@ -123,9 +123,9 @@
       base::Bind(&DoNothingHandleReadError), true);
   switch (pref_service->GetAllPrefStoresInitializationStatus()) {
     case PrefService::INITIALIZATION_STATUS_WAITING:
-      pref_service->AddPrefInitObserver(base::Bind(&OnPrefServiceInit,
-                                                   base::Passed(&pref_service),
-                                                   base::Passed(&callback)));
+      pref_service->AddPrefInitObserver(
+          base::BindOnce(&OnPrefServiceInit, base::Passed(&pref_service),
+                         base::Passed(&callback)));
       break;
     case PrefService::INITIALIZATION_STATUS_SUCCESS:
     case PrefService::INITIALIZATION_STATUS_CREATED_NEW_PREF_STORE:
diff --git a/third_party/WebKit/LayoutTests/http/tests/devtools/console-cd-completions.js b/third_party/WebKit/LayoutTests/http/tests/devtools/console-cd-completions.js
index 9dfcec7d..f19a8eb 100644
--- a/third_party/WebKit/LayoutTests/http/tests/devtools/console-cd-completions.js
+++ b/third_party/WebKit/LayoutTests/http/tests/devtools/console-cd-completions.js
@@ -99,7 +99,7 @@
 
   ConsoleTestRunner.changeExecutionContext('myIFrame');
 
-  ObjectUI.JavaScriptAutocomplete.completionsForExpression('', 'myGlob').then(checkCompletions.bind(this));
+  ObjectUI.javaScriptAutocomplete._completionsForExpression('', 'myGlob').then(checkCompletions.bind(this));
   function checkCompletions(completions) {
     TestRunner.addResult('myGlob completions:');
     dumpCompletions(completions, ['myGlobalVar', 'myGlobalFunction']);
@@ -108,7 +108,7 @@
 
   function requestIFrameCompletions() {
     ConsoleTestRunner.changeExecutionContext('top');
-    ObjectUI.JavaScriptAutocomplete.completionsForExpression('myIFrame.', '').then(checkIframeCompletions.bind(this));
+    ObjectUI.javaScriptAutocomplete._completionsForExpression('myIFrame.', '').then(checkIframeCompletions.bind(this));
   }
 
   function checkIframeCompletions(completions) {
@@ -120,7 +120,7 @@
 
   function requestProxyCompletions() {
     ConsoleTestRunner.changeExecutionContext('top');
-    ObjectUI.JavaScriptAutocomplete.completionsForExpression('window.proxy2.', '')
+    ObjectUI.javaScriptAutocomplete._completionsForExpression('window.proxy2.', '')
         .then(checkProxyCompletions.bind(this));
   }
 
@@ -138,7 +138,7 @@
 
   function requestMyClassWithMixinCompletions() {
     ConsoleTestRunner.changeExecutionContext('top');
-    ObjectUI.JavaScriptAutocomplete.completionsForExpression('window.x.', '')
+    ObjectUI.javaScriptAutocomplete._completionsForExpression('window.x.', '')
         .then(checkMyClassWithMixinCompletions.bind(this));
   }
 
@@ -151,7 +151,7 @@
 
   function requestObjectCompletions() {
     ConsoleTestRunner.changeExecutionContext('top');
-    ObjectUI.JavaScriptAutocomplete.completionsForExpression('Object.', '').then(checkObjectCompletions.bind(this));
+    ObjectUI.javaScriptAutocomplete._completionsForExpression('Object.', '').then(checkObjectCompletions.bind(this));
   }
 
   function checkObjectCompletions(completions) {
diff --git a/third_party/WebKit/LayoutTests/http/tests/devtools/console-completions.js b/third_party/WebKit/LayoutTests/http/tests/devtools/console-completions.js
index b5d1d5ba..3e570918 100644
--- a/third_party/WebKit/LayoutTests/http/tests/devtools/console-completions.js
+++ b/third_party/WebKit/LayoutTests/http/tests/devtools/console-completions.js
@@ -43,12 +43,12 @@
   `);
 
   TestRunner.addResult('Completions for objectC.:');
-  let completions = await ObjectUI.JavaScriptAutocomplete.completionsForExpression('objectC.', 'e');
+  let completions = await ObjectUI.javaScriptAutocomplete._completionsForExpression('objectC.', 'e');
   for (var completion of completions)
     TestRunner.addObject(completion);
 
   TestRunner.addResult('Completions for prefix:');
-  completions = await ObjectUI.JavaScriptAutocomplete.completionsForExpression('', 'prefix');
+  completions = await ObjectUI.javaScriptAutocomplete._completionsForExpression('', 'prefix');
   for (var completion of completions)
     TestRunner.addObject(completion);
 
diff --git a/third_party/WebKit/LayoutTests/http/tests/devtools/sources/debugger/debugger-completions-on-call-frame.js b/third_party/WebKit/LayoutTests/http/tests/devtools/sources/debugger/debugger-completions-on-call-frame.js
index a80dd3a..43d34c3 100644
--- a/third_party/WebKit/LayoutTests/http/tests/devtools/sources/debugger/debugger-completions-on-call-frame.js
+++ b/third_party/WebKit/LayoutTests/http/tests/devtools/sources/debugger/debugger-completions-on-call-frame.js
@@ -32,72 +32,72 @@
     },
 
     function step2(next) {
-      ObjectUI.JavaScriptAutocomplete.completionsForExpression('', 'var').then(
+      ObjectUI.javaScriptAutocomplete._completionsForExpression('', 'var').then(
           checkAgainstGolden.bind(this, ['var1', 'var2'], [], next));
     },
 
     function step3(next) {
-      ObjectUI.JavaScriptAutocomplete.completionsForExpression('', 'di').then(
+      ObjectUI.javaScriptAutocomplete._completionsForExpression('', 'di').then(
           checkAgainstGolden.bind(this, ['dir', 'dirxml'], [], next));
     },
 
     function step4(next) {
-      ObjectUI.JavaScriptAutocomplete.completionsForExpression('', 'win').then(
+      ObjectUI.javaScriptAutocomplete._completionsForExpression('', 'win').then(
           checkAgainstGolden.bind(this, ['window'], [], next));
     },
 
     function step5(next) {
-      ObjectUI.JavaScriptAutocomplete.completionsForExpression('', 't').then(
+      ObjectUI.javaScriptAutocomplete._completionsForExpression('', 't').then(
           checkAgainstGolden.bind(this, ['this'], [], next));
     },
 
     function step6(next) {
-      ObjectUI.JavaScriptAutocomplete.completionsForExpression('var1.', 'toExp')
+      ObjectUI.javaScriptAutocomplete._completionsForExpression('var1.', 'toExp')
           .then(checkAgainstGolden.bind(this, ['toExponential'], [], next));
     },
 
     function step7(next) {
-      ObjectUI.JavaScriptAutocomplete.completionsForExpression('123.', 'toExp')
+      ObjectUI.javaScriptAutocomplete._completionsForExpression('123.', 'toExp')
           .then(checkAgainstGolden.bind(this, [], ['toExponential'], next));
     },
 
     function step8(next) {
-      ObjectUI.JavaScriptAutocomplete.completionsForExpression('', '').then(
+      ObjectUI.javaScriptAutocomplete._completionsForExpression('', '').then(
           checkAgainstGolden.bind(this, [], ['$'], next));
     },
 
     function step9(next) {
-      ObjectUI.JavaScriptAutocomplete.completionsForExpression('', '', true)
+      ObjectUI.javaScriptAutocomplete._completionsForExpression('', '', true)
           .then(checkAgainstGolden.bind(this, ['$', 'window'], [], next));
     },
 
     function step10(next) {
-      ObjectUI.JavaScriptAutocomplete.completionsForExpression('console.', 'log(\'bar\');')
+      ObjectUI.javaScriptAutocomplete._completionsForExpression('console.', 'log(\'bar\');')
           .then(checkAgainstGolden.bind(this, [], ['$'], next));
     },
 
     function step11(next) {
-      ObjectUI.JavaScriptAutocomplete.completionsForExpression('arr1.', '')
+      ObjectUI.javaScriptAutocomplete._completionsForExpression('arr1.', '')
           .then(checkAgainstGolden.bind(this, ['length'], ['1', '2', '3'], next));
     },
 
     function step12(next) {
-      ObjectUI.JavaScriptAutocomplete.completionsForExpression('arr1[', '')
+      ObjectUI.javaScriptAutocomplete._completionsForExpression('arr1[', '')
           .then(checkAgainstGolden.bind(this, ['"length"]'], ['3]'], next));
     },
 
     function step13_ShouldNotCrash(next) {
-      ObjectUI.JavaScriptAutocomplete.completionsForExpression('arr2.', '')
+      ObjectUI.javaScriptAutocomplete._completionsForExpression('arr2.', '')
           .then(checkAgainstGolden.bind(this, ['length'], ['1', '2', '3'], next));
     },
 
     function step14(next) {
-      ObjectUI.JavaScriptAutocomplete.completionsForExpression('document\n', 'E')
+      ObjectUI.javaScriptAutocomplete._completionsForExpression('document\n', 'E')
           .then(checkAgainstGolden.bind(this, ['Element'], ['ELEMENT_NODE'], next));
     },
 
     function step15_ShouldNotCrash(next) {
-      ObjectUI.JavaScriptAutocomplete.completionsForExpression('arr3.', '')
+      ObjectUI.javaScriptAutocomplete._completionsForExpression('arr3.', '')
           .then(checkAgainstGolden.bind(this, ['length'], ['1', '2', '3'], next));
     }
   ]);
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector-protocol/resources/inspector-protocol-test.js b/third_party/WebKit/LayoutTests/http/tests/inspector-protocol/resources/inspector-protocol-test.js
index 8099817..a827c81 100644
--- a/third_party/WebKit/LayoutTests/http/tests/inspector-protocol/resources/inspector-protocol-test.js
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector-protocol/resources/inspector-protocol-test.js
@@ -492,25 +492,6 @@
   });
 };
 
-function debugTest(testFunction) {
-  var dispatch = DevToolsAPI.dispatchMessage;
-  var messages = [];
-  DevToolsAPI.dispatchMessage = message => {
-    if (!messages.length) {
-      setTimeout(() => {
-        for (var message of messages.splice(0))
-          dispatch(message);
-      }, 0);
-    }
-    messages.push(message);
-  };
-  return testRunner => {
-    testRunner.log = console.log;
-    testRunner.completeTest = () => console.log('Test completed');
-    window.test = () => testFunction(testRunner);
-  };
-};
-
 DevToolsAPI._fetch = function(url) {
   return new Promise(fulfill => {
     var xhr = new XMLHttpRequest();
@@ -532,11 +513,29 @@
 window.testRunner.setCanOpenWindows(true);
 
 window.addEventListener('load', () => {
-  var testScriptURL = window.location.search.substring(1);
+  var params = new URLSearchParams(window.location.search);
+  var testScriptURL = params.get('test');
   var baseURL = testScriptURL.substring(0, testScriptURL.lastIndexOf('/') + 1);
   DevToolsAPI._fetch(testScriptURL).then(testScript => {
     var testRunner = new TestRunner(baseURL, DevToolsAPI._log, DevToolsAPI._completeTest, DevToolsAPI._fetch);
     var testFunction = eval(`${testScript}\n//# sourceURL=${testScriptURL}`);
+    if (params.get('debug')) {
+      var dispatch = DevToolsAPI.dispatchMessage;
+      var messages = [];
+      DevToolsAPI.dispatchMessage = message => {
+        if (!messages.length) {
+          setTimeout(() => {
+            for (var message of messages.splice(0))
+              dispatch(message);
+          }, 0);
+        }
+        messages.push(message);
+      };
+      testRunner.log = console.log;
+      testRunner.completeTest = () => console.log('Test completed');
+      window.test = () => testFunction(testRunner);
+      return;
+    }
     return testFunction(testRunner);
   }).catch(reason => {
     DevToolsAPI._log(`Error while executing test script: ${reason}\n${reason.stack}`);
diff --git a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed-expected.txt b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/computed-expected.txt
similarity index 100%
rename from third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed-expected.txt
rename to third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/computed-expected.txt
diff --git a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed.html b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/computed.html
similarity index 93%
rename from third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed.html
rename to third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/computed.html
index 241a0b8..8e085f1 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/computed.html
@@ -2,9 +2,9 @@
 <meta charset="utf-8">
 <title>Computed StylePropertyMap tests</title>
 <link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#computed-stylepropertymapreadonly-objects">
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script src="../resources/testhelper.js"></script>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
 <style>#target { height: 10px; --foo: auto; }</style>
 <div style="width: 50px">
   <div id="target" style="top: 5px; --bar: 5; width: 50%;"></div>
diff --git a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/get.html b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/get.html
similarity index 78%
copy from third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/get.html
copy to third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/get.html
index a835b872c..48fbc570 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/get.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/get.html
@@ -2,24 +2,20 @@
 <meta charset="utf-8">
 <title>StylePropertyMap.get tests</title>
 <link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#get-a-value-from-a-stylepropertymap">
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script src="../resources/testhelper.js"></script>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
 <div id="target" style="width: 10px; --foo: auto; transition-duration: 1s, 2s;"></div>
 <script>
 'use strict';
 
-const styleMap = document.getElementById('target').attributeStyleMap;
+const styleMap = document.getElementById('target').computedStyleMap();
 
 test(() => {
   assert_throws(new TypeError(), () => styleMap.get('lemon'));
 }, 'Calling StylePropertyMap.get with an unsupported property throws a TypeError');
 
 test(() => {
-  assert_equals(styleMap.get('height'), null);
-}, 'Calling StylePropertyMap.get with a property not in the property model returns null');
-
-test(() => {
   assert_equals(styleMap.get('--Foo'), null);
 }, 'Calling StylePropertyMap.get with a custom property not in the property model returns null');
 
diff --git a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/getAll.html b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/getAll.html
similarity index 77%
copy from third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/getAll.html
copy to third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/getAll.html
index 6cfbe75b1..35f80a3 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/getAll.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/getAll.html
@@ -2,25 +2,20 @@
 <meta charset="utf-8">
 <title>StylePropertyMap.getAll tests</title>
 <link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#stylepropertymap">
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script src="../resources/testhelper.js"></script>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
 <div id="target" style="width: 10px; --foo: auto; transition-duration: 1s, 2s;"></div>
 <script>
 'use strict';
 
-const styleMap = document.getElementById('target').attributeStyleMap;
+const styleMap = document.getElementById('target').computedStyleMap();
 
 test(() => {
   assert_throws(new TypeError(), () => styleMap.getAll('lemon'));
 }, 'Calling StylePropertyMap.getAll with an unsupported property throws a TypeError');
 
 test(() => {
-  const result = styleMap.getAll('height');
-  assert_equals(result.length, 0);
-}, 'Calling StylePropertyMap.getAll with a property not in the property model returns an empty list');
-
-test(() => {
   const result = styleMap.getAll('--Foo');
   assert_equals(result.length, 0);
 }, 'Calling StylePropertyMap.getAll with a custom property not in the property model returns an empty list');
diff --git a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/getProperties-expected.txt b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/getProperties-expected.txt
new file mode 100644
index 0000000..3ee9bd2881
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/getProperties-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL StylePropertyMap.getProperties returns property names in correct order assert_array_equals: lengths differ, expected 297 got 294
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/getProperties.html b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/getProperties.html
new file mode 100644
index 0000000..87e9f85c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/getProperties.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>StylePropertyMap.getProperties tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-getproperties">
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<div id="target" style="--A: A; width: 0px; --C: C; transition-duration: 1s, 2s; color: red; --B: B;"></div>
+<script>
+'use strict';
+
+const target = document.getElementById('target');
+
+test(() => {
+  const styleMap = target.computedStyleMap();
+  const expectedProperties = [...getComputedStyle(target)].sort().concat('--A', '--B', '--C');
+  assert_array_equals(styleMap.getProperties(), expectedProperties);
+}, 'StylePropertyMap.getProperties returns property names in correct order');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/has.html b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/has.html
similarity index 80%
copy from third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/has.html
copy to third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/has.html
index 0578094..53d6f6e 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/has.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/has.html
@@ -2,20 +2,19 @@
 <meta charset="utf-8">
 <title>StylePropertyMap.has tests</title>
 <link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#check-if-stylepropertymap-has-a-property">
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
 <div id="target" style="width: 10px; --foo: auto; background-image: url('A'), url('B')"></div>
 <script>
 'use strict';
 
-const styleMap = document.getElementById('target').attributeStyleMap;
+const styleMap = document.getElementById('target').computedStyleMap();
 
 test(() => {
   assert_throws(new TypeError(), () => styleMap.has('lemon'));
 }, 'Calling StylePropertyMap.has with an unsupported property throws a TypeError');
 
 const gTestCases = [
-  { property: 'height', expected: false, desc: 'a property not in the property model' },
   { property: '--Foo', expected: false, desc: 'a custom property not in the property model' },
   { property: 'width', expected: true, desc: 'a valid property' },
   { property: 'wIdTh', expected: true, desc: 'a valid property in mixed case' },
diff --git a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/iterable-expected.txt b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/iterable-expected.txt
new file mode 100644
index 0000000..28bb1a4a
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/iterable-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+FAIL StylePropertyMap iterates properties in correct order assert_array_equals: lengths differ, expected 297 got 294
+PASS StylePropertyMap iterator returns CSS properties with the correct CSSStyleValue
+PASS StylePropertyMap iterator returns list-valued properties with the correct CSSStyleValue
+FAIL StylePropertyMap iterator returns custom properties with the correct CSSStyleValue assert_equals: expected object "--A" but got null
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/iterable.html b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/iterable.html
new file mode 100644
index 0000000..498d2ec
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/computed/iterable.html
@@ -0,0 +1,41 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>StylePropertyMap iterable tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#the-stylepropertymap">
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<div id="target" style="--A: A; width: 10px; --C: C; transition-duration: 1s, 2s; color: red; --B: B;"></div>
+<script>
+'use strict';
+
+function findInStyleMap(styleMap, property) {
+  const index = [...styleMap.keys()].indexOf(property);
+  if (index == -1)
+    return null;
+  return [...styleMap.values()][index];
+}
+
+const target = document.getElementById('target');
+const styleMap = target.computedStyleMap();
+
+test(() => {
+  const expectedKeys = [...getComputedStyle(target)].sort().concat('--A', '--B', '--C');
+  assert_array_equals([...styleMap], expectedKeys);
+}, 'StylePropertyMap iterates properties in correct order');
+
+test(() => {
+  assert_style_value_equals(findInStyleMap(styleMap, 'width'), CSS.px(10));
+}, 'StylePropertyMap iterator returns CSS properties with the correct CSSStyleValue');
+
+test(() => {
+  assert_style_value_array_equals(findInStyleMap(styleMap, 'transition-duration'), [CSS.s(1), CSS.s(2)]);
+}, 'StylePropertyMap iterator returns list-valued properties with the correct CSSStyleValue');
+
+test(() => {
+  assert_style_value_equals(findInStyleMap(styleMap, '--A'), new CSSUnparsedValue('--A'));
+  assert_style_value_equals(findInStyleMap(styleMap, '--B'), new CSSUnparsedValue('--B'));
+  assert_style_value_equals(findInStyleMap(styleMap, '--C'), new CSSUnparsedValue('--C'));
+}, 'StylePropertyMap iterator returns custom properties with the correct CSSStyleValue');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/append.html b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/append.html
similarity index 91%
rename from third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/append.html
rename to third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/append.html
index 68c8360..f06f394 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/append.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/append.html
@@ -2,9 +2,9 @@
 <meta charset="utf-8">
 <title>StylePropertyMap.append tests</title>
 <link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#append-to-a-stylepropertymap">
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script src="../resources/testhelper.js"></script>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
 <script>
 'use strict';
 
diff --git a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/delete-expected.txt b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/delete-expected.txt
similarity index 100%
rename from third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/delete-expected.txt
rename to third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/delete-expected.txt
diff --git a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/delete.html b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/delete.html
similarity index 88%
rename from third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/delete.html
rename to third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/delete.html
index 55cbf6f2..0f65357 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/delete.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/delete.html
@@ -2,9 +2,9 @@
 <meta charset="utf-8">
 <title>StylePropertyMap.delete tests</title>
 <link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#delete-a-stylepropertymap">
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script src="../resources/testhelper.js"></script>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
 <script>
 'use strict';
 
diff --git a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/get.html b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/get.html
similarity index 90%
rename from third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/get.html
rename to third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/get.html
index a835b872c..089c5ae5 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/get.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/get.html
@@ -2,9 +2,9 @@
 <meta charset="utf-8">
 <title>StylePropertyMap.get tests</title>
 <link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#get-a-value-from-a-stylepropertymap">
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script src="../resources/testhelper.js"></script>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
 <div id="target" style="width: 10px; --foo: auto; transition-duration: 1s, 2s;"></div>
 <script>
 'use strict';
diff --git a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/getAll.html b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/getAll.html
similarity index 90%
rename from third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/getAll.html
rename to third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/getAll.html
index 6cfbe75b1..5c72c22 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/getAll.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/getAll.html
@@ -2,9 +2,9 @@
 <meta charset="utf-8">
 <title>StylePropertyMap.getAll tests</title>
 <link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#stylepropertymap">
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script src="../resources/testhelper.js"></script>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
 <div id="target" style="width: 10px; --foo: auto; transition-duration: 1s, 2s;"></div>
 <script>
 'use strict';
diff --git a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/getProperties.html b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/getProperties.html
similarity index 88%
rename from third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/getProperties.html
rename to third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/getProperties.html
index 8f7b2ca..95bde5a 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/getProperties.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/getProperties.html
@@ -2,8 +2,8 @@
 <meta charset="utf-8">
 <title>StylePropertyMap.getProperties tests</title>
 <link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-getproperties">
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
 <div id="target-empty"></div>
 <div id="target-multiple" style="--A: A; width: 0px; --C: C; transition-duration: 1s, 2s; color: red; --B: B;"></div>
 <script>
diff --git a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/has.html b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/has.html
similarity index 91%
rename from third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/has.html
rename to third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/has.html
index 0578094..71ee151a 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/has.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/has.html
@@ -2,8 +2,8 @@
 <meta charset="utf-8">
 <title>StylePropertyMap.has tests</title>
 <link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#check-if-stylepropertymap-has-a-property">
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
 <div id="target" style="width: 10px; --foo: auto; background-image: url('A'), url('B')"></div>
 <script>
 'use strict';
diff --git a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/iterable.html b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/iterable.html
similarity index 92%
rename from third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/iterable.html
rename to third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/iterable.html
index 59a8e5b0..3face508 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/iterable.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/iterable.html
@@ -2,9 +2,9 @@
 <meta charset="utf-8">
 <title>StylePropertyMap iterable tests</title>
 <link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#the-stylepropertymap">
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script src="../resources/testhelper.js"></script>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
 <div id="target-empty"></div>
 <div id="target-multiple" style="--A: A; width: 10px; --C: C; transition-duration: 1s, 2s; color: red; --B: B;"></div>
 <script>
diff --git a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/set-expected.txt b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/set-expected.txt
similarity index 100%
rename from third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/set-expected.txt
rename to third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/set-expected.txt
diff --git a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/set.html b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/set.html
similarity index 93%
rename from third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/set.html
rename to third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/set.html
index 7c04c5e..d0ba3f1 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/set.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/set.html
@@ -2,9 +2,9 @@
 <meta charset="utf-8">
 <title>StylePropertyMap.set</title>
 <link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#set-a-value-on-a-stylepropertymap">
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script src="../resources/testhelper.js"></script>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
 <script>
 'use strict';
 
diff --git a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/update-expected.txt b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/update-expected.txt
similarity index 100%
rename from third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/update-expected.txt
rename to third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/update-expected.txt
diff --git a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/update.html b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/update.html
similarity index 94%
rename from third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/update.html
rename to third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/update.html
index ccb9c74..49760bc 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/update.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/the-stylepropertymap/inline/update.html
@@ -2,9 +2,9 @@
 <meta charset="utf-8">
 <title>StylePropertyMap.update tests</title>
 <link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#update-a-value-in-a-stylepropertymap">
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script src="../resources/testhelper.js"></script>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
 <script>
 'use strict';
 
diff --git a/third_party/WebKit/LayoutTests/vr/VRDisplay_rAF_fires_with_window_rAF.html b/third_party/WebKit/LayoutTests/vr/VRDisplay_rAF_fires_with_window_rAF.html
index 1a01af6..9a8a286 100644
--- a/third_party/WebKit/LayoutTests/vr/VRDisplay_rAF_fires_with_window_rAF.html
+++ b/third_party/WebKit/LayoutTests/vr/VRDisplay_rAF_fires_with_window_rAF.html
@@ -5,10 +5,11 @@
 <script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
 <script src="file:///gen/device/vr/vr_service.mojom.js"></script>
 <script src="resources/mock-vr-service.js"></script>
+<script src="resources/test-constants.js"></script>
 <script>
 let fakeDisplays = fakeVRDisplays();
 
-vr_test( (t) => {
+vr_test( (t, mock_service) => {
   return navigator.getVRDisplays().then( (displays) => {
     let display = displays[0];
     let counter = 0;
@@ -53,10 +54,18 @@
       counter++;
     }
 
-    window.requestAnimationFrame(onWindowAnimationFrame1);
-    display.requestAnimationFrame(onDisplayAnimationFrame1);
-    display.requestAnimationFrame(onDisplayAnimationFrame2);
-    window.requestAnimationFrame(onWindowAnimationFrame2);
+    function onFirstAnimationFrame() {
+      window.requestAnimationFrame(onWindowAnimationFrame1);
+      display.requestAnimationFrame(onDisplayAnimationFrame1);
+      display.requestAnimationFrame(onDisplayAnimationFrame2);
+      window.requestAnimationFrame(onWindowAnimationFrame2);
+    }
+
+    // Ensure that there's a non-null pose available for the mock framework to
+    // return, and wait for one rAF callback to let it propagate. This is a
+    // workaround for delayed promise execution in the mocking framework.
+    mock_service.mockVRDisplays_[0].setPose(VALID_POSE);
+    display.requestAnimationFrame(onFirstAnimationFrame);
   }, (err) => {
     t.step( () => {
       assert_unreached("getVRDisplays rejected");
diff --git a/third_party/WebKit/LayoutTests/vr/getFrameData_oneframeupdate.html b/third_party/WebKit/LayoutTests/vr/getFrameData_oneframeupdate.html
index 8e370e8d..442bb12 100644
--- a/third_party/WebKit/LayoutTests/vr/getFrameData_oneframeupdate.html
+++ b/third_party/WebKit/LayoutTests/vr/getFrameData_oneframeupdate.html
@@ -17,7 +17,6 @@
     var counter = 0;
 
     function onFrame() {
-      display.requestAnimationFrame(onFrame);
       if (counter == 0) {
         t.step( () => {
           assert_false(display.getFrameData(fd));
@@ -27,14 +26,6 @@
           assert_false(display.getFrameData(fd));
         }, "Does not update within the same frame");
       } else {
-        // TODO(mthiesse): This is a workaround for crbug.com/787196. Either
-        // remove the workaround once fixed or remove this todo.
-        // Let several animation frames get triggered so we're sure to have a
-        // pose
-        if (counter <= 2) {
-          counter++;
-          return;
-        }
         t.step( () => {
           assert_true(display.getFrameData(fd));
           assert_not_equals(fd.pose, null);
@@ -49,6 +40,10 @@
         t.done();
       }
       counter++;
+      // Use rAF late so that the mock's setPose above can supply its data
+      // before RequestVSync calls GetPose. This is an artifact of the mocking
+      // framework, the real implementation doesn't have this restriction.
+      display.requestAnimationFrame(onFrame);
     }
 
     display.requestAnimationFrame(onFrame);
diff --git a/third_party/WebKit/LayoutTests/vr/getFrameData_samewithinframe.html b/third_party/WebKit/LayoutTests/vr/getFrameData_samewithinframe.html
index 559981d..b5bf29ac 100644
--- a/third_party/WebKit/LayoutTests/vr/getFrameData_samewithinframe.html
+++ b/third_party/WebKit/LayoutTests/vr/getFrameData_samewithinframe.html
@@ -16,16 +16,9 @@
     var fd1 = new VRFrameData();
     var fd2 = new VRFrameData();
     mock_service.mockVRDisplays_[0].setPose(expected_pose);
-    var numFrames = 0;
 
     function onFrame() {
       display.requestAnimationFrame(onFrame);
-      numFrames++;
-      // Let one rAF run before checking in order to ensure we actually get
-      // frame data
-      if (numFrames == 1) {
-        return;
-      }
 
       t.step( () => {
         assert_true(display.getFrameData(fd1));
diff --git a/third_party/WebKit/Source/core/dom/Document.cpp b/third_party/WebKit/Source/core/dom/Document.cpp
index 6819999..3ce6b91b 100644
--- a/third_party/WebKit/Source/core/dom/Document.cpp
+++ b/third_party/WebKit/Source/core/dom/Document.cpp
@@ -608,7 +608,6 @@
       visually_ordered_(false),
       ready_state_(kComplete),
       parsing_state_(kFinishedParsing),
-      goto_anchor_needed_after_stylesheets_load_(false),
       contains_validity_style_rules_(false),
       contains_plugins_(false),
       ignore_destructive_write_count_(0),
@@ -2387,9 +2386,6 @@
   if (frame_view->NeedsLayout())
     frame_view->UpdateLayout();
 
-  if (goto_anchor_needed_after_stylesheets_load_)
-    frame_view->ProcessUrlFragment(url_);
-
   if (Lifecycle().GetState() < DocumentLifecycle::kLayoutClean)
     Lifecycle().AdvanceTo(DocumentLifecycle::kLayoutClean);
 
@@ -3269,12 +3265,6 @@
         (!GetLayoutViewItem().FirstChild() ||
          GetLayoutViewItem().NeedsLayout()))
       View()->UpdateLayout();
-
-    // TODO(bokan): This is a temporary fix to https://crbug.com/788486.
-    // There's some better cleanups that should be done to follow-up:
-    // https://crbug.com/795381.
-    if (View() && goto_anchor_needed_after_stylesheets_load_)
-      View()->ProcessUrlFragment(url_);
   }
 
   load_event_progress_ = kLoadEventCompleted;
diff --git a/third_party/WebKit/Source/core/dom/Document.h b/third_party/WebKit/Source/core/dom/Document.h
index 2629fb7..93965f1 100644
--- a/third_party/WebKit/Source/core/dom/Document.h
+++ b/third_party/WebKit/Source/core/dom/Document.h
@@ -479,13 +479,6 @@
     return *style_engine_.Get();
   }
 
-  bool GotoAnchorNeededAfterStylesheetsLoad() {
-    return goto_anchor_needed_after_stylesheets_load_;
-  }
-  void SetGotoAnchorNeededAfterStylesheetsLoad(bool b) {
-    goto_anchor_needed_after_stylesheets_load_ = b;
-  }
-
   void ScheduleUseShadowTreeUpdate(SVGUseElement&);
   void UnscheduleUseShadowTreeUpdate(SVGUseElement&);
 
@@ -1642,7 +1635,6 @@
   DocumentReadyState ready_state_;
   ParsingState parsing_state_;
 
-  bool goto_anchor_needed_after_stylesheets_load_;
   bool is_dns_prefetch_enabled_;
   bool have_explicitly_disabled_dns_prefetch_;
   bool contains_validity_style_rules_;
diff --git a/third_party/WebKit/Source/core/frame/Frame.h b/third_party/WebKit/Source/core/frame/Frame.h
index 09e4d9c..4bc6821b 100644
--- a/third_party/WebKit/Source/core/frame/Frame.h
+++ b/third_party/WebKit/Source/core/frame/Frame.h
@@ -123,11 +123,6 @@
   // otherwise.
   virtual bool PrepareForCommit() = 0;
 
-  // TODO(japhet): These should all move to LocalFrame.
-  virtual void PrintNavigationErrorMessage(const Frame&,
-                                           const char* reason) = 0;
-  virtual void PrintNavigationWarning(const String&) = 0;
-
   // TODO(pilgrim): Replace all instances of ownerLayoutObject() with
   // ownerLayoutItem(), https://crbug.com/499321
   LayoutEmbeddedContent* OwnerLayoutObject()
diff --git a/third_party/WebKit/Source/core/frame/FrameClient.h b/third_party/WebKit/Source/core/frame/FrameClient.h
index 478940ef..8e64265 100644
--- a/third_party/WebKit/Source/core/frame/FrameClient.h
+++ b/third_party/WebKit/Source/core/frame/FrameClient.h
@@ -18,9 +18,6 @@
  public:
   virtual bool InShadowTree() const = 0;
 
-  // TODO(dcheng): Move this into LocalFrameClient, since remote frames don't
-  // need this.
-  virtual void WillBeDetached() = 0;
   virtual void Detached(FrameDetachType) = 0;
 
   virtual Frame* Opener() const = 0;
diff --git a/third_party/WebKit/Source/core/frame/LocalFrame.h b/third_party/WebKit/Source/core/frame/LocalFrame.h
index 94398c3a..0a7818c 100644
--- a/third_party/WebKit/Source/core/frame/LocalFrame.h
+++ b/third_party/WebKit/Source/core/frame/LocalFrame.h
@@ -119,8 +119,8 @@
   void Detach(FrameDetachType) override;
   bool ShouldClose() override;
   SecurityContext* GetSecurityContext() const override;
-  void PrintNavigationErrorMessage(const Frame&, const char* reason) override;
-  void PrintNavigationWarning(const String&) override;
+  void PrintNavigationErrorMessage(const Frame&, const char* reason);
+  void PrintNavigationWarning(const String&);
   bool PrepareForCommit() override;
   void DidChangeVisibilityState() override;
   void DidFreeze() override;
diff --git a/third_party/WebKit/Source/core/frame/LocalFrameClient.h b/third_party/WebKit/Source/core/frame/LocalFrameClient.h
index 3b9d725d..1b1a697 100644
--- a/third_party/WebKit/Source/core/frame/LocalFrameClient.h
+++ b/third_party/WebKit/Source/core/frame/LocalFrameClient.h
@@ -110,6 +110,7 @@
 
   virtual bool HasWebView() const = 0;  // mainly for assertions
 
+  virtual void WillBeDetached() = 0;
   virtual void DispatchWillSendRequest(ResourceRequest&) = 0;
   virtual void DispatchDidReceiveResponse(const ResourceResponse&) = 0;
   virtual void DispatchDidLoadResourceFromMemoryCache(
diff --git a/third_party/WebKit/Source/core/frame/LocalFrameView.cpp b/third_party/WebKit/Source/core/frame/LocalFrameView.cpp
index 2ce6766..411a4d0 100644
--- a/third_party/WebKit/Source/core/frame/LocalFrameView.cpp
+++ b/third_party/WebKit/Source/core/frame/LocalFrameView.cpp
@@ -240,6 +240,7 @@
       needs_intersection_observation_(false),
       needs_forced_compositing_update_(false),
       scroll_gesture_region_is_dirty_(false),
+      needs_focus_on_fragment_(false),
       main_thread_scrolling_reasons_(0),
       paint_frame_count_(0),
       unique_id_(NewUniqueObjectId()) {
@@ -1917,14 +1918,6 @@
                                               UrlFragmentBehavior behavior) {
   DCHECK(frame_->GetDocument());
 
-  if (behavior == kUrlFragmentScroll &&
-      !frame_->GetDocument()->IsRenderingReady()) {
-    frame_->GetDocument()->SetGotoAnchorNeededAfterStylesheetsLoad(true);
-    return false;
-  }
-
-  frame_->GetDocument()->SetGotoAnchorNeededAfterStylesheetsLoad(false);
-
   Element* anchor_node = frame_->GetDocument()->FindAnchor(name);
 
   // Setting to null will clear the current target.
@@ -1935,12 +1928,12 @@
             ToSVGSVGElementOrNull(frame_->GetDocument()->documentElement())) {
       svg->SetupInitialView(name, anchor_node);
       if (!anchor_node)
-        return true;
+        return false;
     }
     // If this is not the top-level frame, then don't scroll to the
     // anchor position.
     if (!frame_->IsMainFrame())
-      behavior = kUrlFragmentDontScroll;
+      return false;
   }
 
   // Implement the rule that "" and "top" both mean top of page as in other
@@ -1949,46 +1942,33 @@
       !(name.IsEmpty() || DeprecatedEqualIgnoringCase(name, "top")))
     return false;
 
-  if (behavior == kUrlFragmentScroll) {
-    SetFragmentAnchor(anchor_node ? static_cast<Node*>(anchor_node)
-                                  : frame_->GetDocument());
+  if (behavior == kUrlFragmentDontScroll)
+    return true;
+
+  if (!anchor_node) {
+    fragment_anchor_ = frame_->GetDocument();
+    needs_focus_on_fragment_ = false;
+  } else {
+    fragment_anchor_ = anchor_node;
+    needs_focus_on_fragment_ = true;
   }
 
-  // If the anchor accepts keyboard focus and fragment scrolling is allowed,
-  // move focus there to aid users relying on keyboard navigation.
-  // If anchorNode is not focusable or fragment scrolling is not allowed,
-  // clear focus, which matches the behavior of other browsers.
-  if (anchor_node) {
-    frame_->GetDocument()->UpdateStyleAndLayoutIgnorePendingStylesheets();
-    if (behavior == kUrlFragmentScroll && anchor_node->IsFocusable()) {
-      anchor_node->focus();
-    } else {
-      if (behavior == kUrlFragmentScroll) {
-        frame_->GetDocument()->SetSequentialFocusNavigationStartingPoint(
-            anchor_node);
-      }
-      frame_->GetDocument()->ClearFocusedElement();
-    }
+  // If rendering is blocked, we'll necessarily have a layout to kick off the
+  // scroll and focus.
+  if (frame_->GetDocument()->IsRenderingReady()) {
+    frame_->GetDocument()->UpdateStyleAndLayoutTree();
+
+    // If layout is needed, we will scroll in performPostLayoutTasks. Otherwise,
+    // scroll and focus immediately.
+    if (NeedsLayout())
+      UpdateLayout();
+    else
+      ScrollAndFocusFragmentAnchor();
   }
+
   return true;
 }
 
-void LocalFrameView::SetFragmentAnchor(Node* anchor_node) {
-  DCHECK(anchor_node);
-  fragment_anchor_ = anchor_node;
-
-  // We need to update the layout tree before scrolling.
-  frame_->GetDocument()->UpdateStyleAndLayoutTree();
-
-  // If layout is needed, we will scroll in performPostLayoutTasks. Otherwise,
-  // scroll immediately.
-  LayoutViewItem layout_view_item = this->GetLayoutViewItem();
-  if (!layout_view_item.IsNull() && layout_view_item.NeedsLayout())
-    UpdateLayout();
-  else
-    ScrollToFragmentAnchor();
-}
-
 void LocalFrameView::ClearFragmentAnchor() {
   fragment_anchor_ = nullptr;
 }
@@ -2463,7 +2443,7 @@
       });
 }
 
-void LocalFrameView::ScrollToFragmentAnchor() {
+void LocalFrameView::ScrollAndFocusFragmentAnchor() {
   Node* anchor_node = fragment_anchor_;
   if (!anchor_node)
     return;
@@ -2506,6 +2486,22 @@
 
     if (AXObjectCache* cache = frame_->GetDocument()->ExistingAXObjectCache())
       cache->HandleScrolledToAnchor(anchor_node);
+
+    // If the anchor accepts keyboard focus and fragment scrolling is allowed,
+    // move focus there to aid users relying on keyboard navigation.
+    // If anchorNode is not focusable or fragment scrolling is not allowed,
+    // clear focus, which matches the behavior of other browsers.
+    if (needs_focus_on_fragment_) {
+      if (anchor_node->IsElementNode() &&
+          ToElement(anchor_node)->IsFocusable()) {
+        ToElement(anchor_node)->focus();
+      } else {
+        frame_->GetDocument()->SetSequentialFocusNavigationStartingPoint(
+            anchor_node);
+        frame_->GetDocument()->ClearFocusedElement();
+      }
+      needs_focus_on_fragment_ = false;
+    }
   }
 
   // The fragment anchor should only be maintained while the frame is still
@@ -2632,7 +2628,7 @@
 
   // If we're restoring a scroll position from history, that takes precedence
   // over scrolling to the anchor in the URL.
-  ScrollToFragmentAnchor();
+  ScrollAndFocusFragmentAnchor();
   GetFrame().Loader().RestoreScrollPositionAndViewState();
   SendResizeEventIfNeeded();
 }
@@ -4267,8 +4263,8 @@
   Document* document = frame_->GetDocument();
   document->EnqueueScrollEventForNode(document);
 
-  frame_->GetEventHandler().DispatchFakeMouseMoveEventSoon(
-      MouseEventManager::FakeMouseMoveReason::kDuringScroll);
+  GetLayoutView()->DispatchFakeMouseMoveEventSoon(GetFrame().GetEventHandler());
+
   if (scroll_type == kUserScroll || scroll_type == kCompositorScroll) {
     Page* page = GetFrame().GetPage();
     if (page)
diff --git a/third_party/WebKit/Source/core/frame/LocalFrameView.h b/third_party/WebKit/Source/core/frame/LocalFrameView.h
index 204cc74..e52d3b5 100644
--- a/third_party/WebKit/Source/core/frame/LocalFrameView.h
+++ b/third_party/WebKit/Source/core/frame/LocalFrameView.h
@@ -352,8 +352,8 @@
   enum UrlFragmentBehavior { kUrlFragmentScroll, kUrlFragmentDontScroll };
   // Updates the fragment anchor element based on URL's fragment identifier.
   // Updates corresponding ':target' CSS pseudo class on the anchor element.
-  // If |UrlFragmentScroll| is passed in then makes the anchor element
-  // focused and also visible by scrolling to it. The scroll offset is
+  // If |UrlFragmentScroll| is passed in sets the the anchor element so that it
+  // will be focused and scrolled into view during layout. The scroll offset is
   // maintained during the frame loading process.
   void ProcessUrlFragment(const KURL&,
                           UrlFragmentBehavior = kUrlFragmentScroll);
@@ -1103,7 +1103,7 @@
 
   bool ProcessUrlFragmentHelper(const String&, UrlFragmentBehavior);
   void SetFragmentAnchor(Node*);
-  void ScrollToFragmentAnchor();
+  void ScrollAndFocusFragmentAnchor();
   void DidScrollTimerFired(TimerBase*);
 
   void UpdateLayersAndCompositingAfterScrollIfNeeded();
@@ -1323,6 +1323,8 @@
   bool needs_forced_compositing_update_;
   bool scroll_gesture_region_is_dirty_;
 
+  bool needs_focus_on_fragment_;
+
   Member<ElementVisibilityObserver> visibility_observer_;
 
   IntRect remote_viewport_intersection_;
diff --git a/third_party/WebKit/Source/core/frame/RemoteFrame.cpp b/third_party/WebKit/Source/core/frame/RemoteFrame.cpp
index 1c14d43..e8c12aac 100644
--- a/third_party/WebKit/Source/core/frame/RemoteFrame.cpp
+++ b/third_party/WebKit/Source/core/frame/RemoteFrame.cpp
@@ -97,7 +97,6 @@
   // the parent is a local frame.
   if (view_)
     view_->Dispose();
-  Client()->WillBeDetached();
   GetWindowProxyManager()->ClearForClose();
   SetView(nullptr);
   // ... the RemoteDOMWindow will need to be informed of detachment,
diff --git a/third_party/WebKit/Source/core/frame/RemoteFrame.h b/third_party/WebKit/Source/core/frame/RemoteFrame.h
index 3038478..472803d 100644
--- a/third_party/WebKit/Source/core/frame/RemoteFrame.h
+++ b/third_party/WebKit/Source/core/frame/RemoteFrame.h
@@ -35,8 +35,6 @@
   void AddResourceTiming(const ResourceTimingInfo&) override;
   void Detach(FrameDetachType) override;
   RemoteSecurityContext* GetSecurityContext() const override;
-  void PrintNavigationErrorMessage(const Frame&, const char* reason) override {}
-  void PrintNavigationWarning(const String&) override {}
   bool PrepareForCommit() override;
   bool ShouldClose() override;
   void DidFreeze() override;
diff --git a/third_party/WebKit/Source/core/frame/RemoteFrameClientImpl.cpp b/third_party/WebKit/Source/core/frame/RemoteFrameClientImpl.cpp
index fce0de9df..2e74eff8 100644
--- a/third_party/WebKit/Source/core/frame/RemoteFrameClientImpl.cpp
+++ b/third_party/WebKit/Source/core/frame/RemoteFrameClientImpl.cpp
@@ -55,8 +55,6 @@
   return web_frame_->InShadowTree();
 }
 
-void RemoteFrameClientImpl::WillBeDetached() {}
-
 void RemoteFrameClientImpl::Detached(FrameDetachType type) {
   // Alert the client that the frame is being detached.
   WebRemoteFrameClient* client = web_frame_->Client();
diff --git a/third_party/WebKit/Source/core/frame/RemoteFrameClientImpl.h b/third_party/WebKit/Source/core/frame/RemoteFrameClientImpl.h
index 823a00bd..a3b9e1f 100644
--- a/third_party/WebKit/Source/core/frame/RemoteFrameClientImpl.h
+++ b/third_party/WebKit/Source/core/frame/RemoteFrameClientImpl.h
@@ -18,7 +18,6 @@
 
   // FrameClient overrides:
   bool InShadowTree() const override;
-  void WillBeDetached() override;
   void Detached(FrameDetachType) override;
   Frame* Opener() const override;
   void SetOpener(Frame*) override;
diff --git a/third_party/WebKit/Source/core/layout/LayoutBox.cpp b/third_party/WebKit/Source/core/layout/LayoutBox.cpp
index 235bc6fe..21f23f8 100644
--- a/third_party/WebKit/Source/core/layout/LayoutBox.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutBox.cpp
@@ -1165,6 +1165,14 @@
                                                  : nullptr;
 }
 
+void LayoutBox::DispatchFakeMouseMoveEventSoon(EventHandler& event_handler) {
+  const LayoutBoxModelObject& container = ContainerForPaintInvalidation();
+  FloatQuad quad =
+      FloatQuad(FloatRect(VisualRectIncludingCompositedScrolling(container)));
+  quad = container.LocalToAbsoluteQuad(quad);
+  event_handler.DispatchFakeMouseMoveEventSoonInQuad(quad);
+}
+
 void LayoutBox::ScrollByRecursively(const ScrollOffset& delta) {
   if (delta.IsZero())
     return;
diff --git a/third_party/WebKit/Source/core/layout/LayoutBox.h b/third_party/WebKit/Source/core/layout/LayoutBox.h
index b0c5387..ae03b92 100644
--- a/third_party/WebKit/Source/core/layout/LayoutBox.h
+++ b/third_party/WebKit/Source/core/layout/LayoutBox.h
@@ -34,6 +34,7 @@
 
 namespace blink {
 
+class EventHandler;
 class LayoutBlockFlow;
 class LayoutMultiColumnSpannerPlaceholder;
 struct NGPhysicalBoxStrut;
@@ -1002,6 +1003,7 @@
       const IntPoint& point_in_root_frame) const;
   static LayoutBox* FindAutoscrollable(LayoutObject*);
   virtual void StopAutoscroll() {}
+  virtual void DispatchFakeMouseMoveEventSoon(EventHandler&);
 
   DISABLE_CFI_PERF bool HasAutoVerticalScrollbar() const {
     return HasOverflowClip() && Style()->HasAutoVerticalScroll();
diff --git a/third_party/WebKit/Source/core/layout/LayoutView.cpp b/third_party/WebKit/Source/core/layout/LayoutView.cpp
index b063cba..8dbda507 100644
--- a/third_party/WebKit/Source/core/layout/LayoutView.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutView.cpp
@@ -29,6 +29,7 @@
 #include "core/frame/LocalFrameView.h"
 #include "core/frame/Settings.h"
 #include "core/html/HTMLIFrameElement.h"
+#include "core/input/EventHandler.h"
 #include "core/layout/HitTestResult.h"
 #include "core/layout/LayoutCounter.h"
 #include "core/layout/LayoutEmbeddedContent.h"
@@ -733,6 +734,11 @@
 #undef RETURN_SCROLLBAR_MODE
 }
 
+void LayoutView::DispatchFakeMouseMoveEventSoon(EventHandler& event_handler) {
+  event_handler.DispatchFakeMouseMoveEventSoon(
+      MouseEventManager::FakeMouseMoveReason::kDuringScroll);
+}
+
 IntRect LayoutView::DocumentRect() const {
   LayoutRect overflow_rect(LayoutOverflowRect());
   FlipForWritingMode(overflow_rect);
diff --git a/third_party/WebKit/Source/core/layout/LayoutView.h b/third_party/WebKit/Source/core/layout/LayoutView.h
index f0148d44..2600b60 100644
--- a/third_party/WebKit/Source/core/layout/LayoutView.h
+++ b/third_party/WebKit/Source/core/layout/LayoutView.h
@@ -161,6 +161,8 @@
   void CalculateScrollbarModes(ScrollbarMode& h_mode,
                                ScrollbarMode& v_mode) const;
 
+  void DispatchFakeMouseMoveEventSoon(EventHandler&) override;
+
   LayoutState* GetLayoutState() const { return layout_state_; }
 
   void UpdateHitTestResult(HitTestResult&, const LayoutPoint&) override;
diff --git a/third_party/WebKit/Source/core/loader/EmptyClients.h b/third_party/WebKit/Source/core/loader/EmptyClients.h
index e911dcde..ab86c73 100644
--- a/third_party/WebKit/Source/core/loader/EmptyClients.h
+++ b/third_party/WebKit/Source/core/loader/EmptyClients.h
@@ -439,7 +439,6 @@
 
   // FrameClient implementation.
   bool InShadowTree() const override { return false; }
-  void WillBeDetached() override {}
   void Detached(FrameDetachType) override {}
   Frame* Opener() const override { return nullptr; }
   void SetOpener(Frame*) override {}
diff --git a/third_party/WebKit/Source/core/paint/PaintLayerScrollableArea.cpp b/third_party/WebKit/Source/core/paint/PaintLayerScrollableArea.cpp
index 104dabd..e53b5f3f 100644
--- a/third_party/WebKit/Source/core/paint/PaintLayerScrollableArea.cpp
+++ b/third_party/WebKit/Source/core/paint/PaintLayerScrollableArea.cpp
@@ -433,18 +433,7 @@
     UpdateCompositingLayersAfterScroll();
   }
 
-  const LayoutBoxModelObject& paint_invalidation_container =
-      Box().ContainerForPaintInvalidation();
-
-  FloatQuad quad_for_fake_mouse_move_event = FloatQuad(FloatRect(
-      Layer()->GetLayoutObject().VisualRectIncludingCompositedScrolling(
-          paint_invalidation_container)));
-
-  quad_for_fake_mouse_move_event =
-      paint_invalidation_container.LocalToAbsoluteQuad(
-          quad_for_fake_mouse_move_event);
-  frame->GetEventHandler().DispatchFakeMouseMoveEventSoonInQuad(
-      quad_for_fake_mouse_move_event);
+  Box().DispatchFakeMouseMoveEventSoon(frame->GetEventHandler());
 
   if (scroll_type == kUserScroll || scroll_type == kCompositorScroll) {
     Page* page = frame->GetPage();
diff --git a/third_party/WebKit/Source/devtools/front_end/console/ConsolePrompt.js b/third_party/WebKit/Source/devtools/front_end/console/ConsolePrompt.js
index 96629b5a..fcaac7b 100644
--- a/third_party/WebKit/Source/devtools/front_end/console/ConsolePrompt.js
+++ b/third_party/WebKit/Source/devtools/front_end/console/ConsolePrompt.js
@@ -286,7 +286,7 @@
       if (excludedTokens.has(token.type))
         return Promise.resolve(historyWords);
     }
-    return ObjectUI.JavaScriptAutocomplete.completionsForTextInCurrentContext(before, query, force)
+    return ObjectUI.javaScriptAutocomplete.completionsForTextInCurrentContext(before, query, force)
         .then(words => words.concat(historyWords));
   }
 
diff --git a/third_party/WebKit/Source/devtools/front_end/object_ui/JavaScriptAutocomplete.js b/third_party/WebKit/Source/devtools/front_end/object_ui/JavaScriptAutocomplete.js
index c8871af2..355a312 100644
--- a/third_party/WebKit/Source/devtools/front_end/object_ui/JavaScriptAutocomplete.js
+++ b/third_party/WebKit/Source/devtools/front_end/object_ui/JavaScriptAutocomplete.js
@@ -2,185 +2,86 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-ObjectUI.JavaScriptAutocomplete = {};
-
-/** @typedef {{title:(string|undefined), items:Array<string>}} */
-ObjectUI.JavaScriptAutocomplete.CompletionGroup;
-
-/**
- * @param {string} text
- * @param {string} query
- * @param {boolean=} force
- * @return {!Promise<!UI.SuggestBox.Suggestions>}
- */
-ObjectUI.JavaScriptAutocomplete.completionsForTextInCurrentContext = function(text, query, force) {
-  var clippedExpression = ObjectUI.JavaScriptAutocomplete._clipExpression(text, true);
-  var mapCompletionsPromise = ObjectUI.JavaScriptAutocomplete._mapCompletions(text, query);
-  return ObjectUI.JavaScriptAutocomplete.completionsForExpression(clippedExpression, query, force)
-      .then(completions => mapCompletionsPromise.then(mapCompletions => mapCompletions.concat(completions)));
-};
-
-/**
- * @param {string} text
- * @param {boolean=} allowEndingBracket
- * @return {string}
- */
-ObjectUI.JavaScriptAutocomplete._clipExpression = function(text, allowEndingBracket) {
-  var index;
-  var stopChars = new Set('=:({;,!+-*/&|^<>`'.split(''));
-  var whiteSpaceChars = new Set(' \r\n\t'.split(''));
-  var continueChars = new Set('[. \r\n\t'.split(''));
-
-  for (index = text.length - 1; index >= 0; index--) {
-    if (stopChars.has(text.charAt(index)))
-      break;
-    if (whiteSpaceChars.has(text.charAt(index)) && !continueChars.has(text.charAt(index - 1)))
-      break;
+ObjectUI.JavaScriptAutocomplete = class {
+  constructor() {
+    /** @type {!Map<string, {date: number, value: !Promise<?Object>}>} */
+    this._expressionCache = new Map();
+    ConsoleModel.consoleModel.addEventListener(
+        ConsoleModel.ConsoleModel.Events.CommandEvaluated, this._clearCache, this);
+    SDK.targetManager.addModelListener(
+        SDK.RuntimeModel, SDK.RuntimeModel.Events.ExecutionContextChanged, this._clearCache, this);
+    SDK.targetManager.addModelListener(
+        SDK.DebuggerModel, SDK.DebuggerModel.Events.DebuggerPaused, this._clearCache, this);
   }
-  var clippedExpression = text.substring(index + 1).trim();
-  var bracketCount = 0;
 
-  index = clippedExpression.length - 1;
-  while (index >= 0) {
-    var character = clippedExpression.charAt(index);
-    if (character === ']')
-      bracketCount++;
-    // Allow an open bracket at the end for property completion.
-    if (character === '[' && (index < clippedExpression.length - 1 || !allowEndingBracket)) {
-      bracketCount--;
-      if (bracketCount < 0)
+  _clearCache() {
+    this._expressionCache.clear();
+  }
+
+  /**
+   * @param {string} text
+   * @param {string} query
+   * @param {boolean=} force
+   * @return {!Promise<!UI.SuggestBox.Suggestions>}
+   */
+  completionsForTextInCurrentContext(text, query, force) {
+    var clippedExpression = this._clipExpression(text, true);
+    var mapCompletionsPromise = this._mapCompletions(text, query);
+    return this._completionsForExpression(clippedExpression, query, force)
+        .then(completions => mapCompletionsPromise.then(mapCompletions => mapCompletions.concat(completions)));
+  }
+
+  /**
+   * @param {string} text
+   * @param {boolean=} allowEndingBracket
+   * @return {string}
+   */
+  _clipExpression(text, allowEndingBracket) {
+    var index;
+    var stopChars = new Set('=:({;,!+-*/&|^<>`'.split(''));
+    var whiteSpaceChars = new Set(' \r\n\t'.split(''));
+    var continueChars = new Set('[. \r\n\t'.split(''));
+
+    for (index = text.length - 1; index >= 0; index--) {
+      if (stopChars.has(text.charAt(index)))
+        break;
+      if (whiteSpaceChars.has(text.charAt(index)) && !continueChars.has(text.charAt(index - 1)))
         break;
     }
-    index--;
-  }
-  return clippedExpression.substring(index + 1).trim();
-};
+    var clippedExpression = text.substring(index + 1).trim();
+    var bracketCount = 0;
 
-/**
- * @param {string} text
- * @param {string} query
- * @return {!Promise<!UI.SuggestBox.Suggestions>}
- */
-ObjectUI.JavaScriptAutocomplete._mapCompletions = async function(text, query) {
-  var mapMatch = text.match(/\.\s*(get|set|delete)\s*\(\s*$/);
-  var executionContext = UI.context.flavor(SDK.ExecutionContext);
-  if (!executionContext || !mapMatch)
-    return [];
-
-  var clippedExpression = ObjectUI.JavaScriptAutocomplete._clipExpression(text.substring(0, mapMatch.index));
-  var result = await executionContext.evaluate(
-      {
-        expression: clippedExpression,
-        objectGroup: 'completion',
-        includeCommandLineAPI: true,
-        silent: true,
-        returnByValue: false,
-        generatePreview: false
-      },
-      /* userGesture */ false, /* awaitPromise */ false);
-  if (result.error || !!result.exceptionDetails || result.object.subtype !== 'map')
-    return [];
-  var properties = await result.object.getOwnPropertiesPromise(false);
-  var internalProperties = properties.internalProperties || [];
-  var entriesProperty = internalProperties.find(property => property.name === '[[Entries]]');
-  if (!entriesProperty)
-    return [];
-  var keysObj = await entriesProperty.value.callFunctionJSONPromise(getEntries);
-  return gotKeys(Object.keys(keysObj));
-
-  /**
-   * @suppressReceiverCheck
-   * @this {!Array<{key:?, value:?}>}
-   * @return {!Object}
-   */
-  function getEntries() {
-    var result = {__proto__: null};
-    for (var i = 0; i < this.length; i++) {
-      if (typeof this[i].key === 'string')
-        result[this[i].key] = true;
+    index = clippedExpression.length - 1;
+    while (index >= 0) {
+      var character = clippedExpression.charAt(index);
+      if (character === ']')
+        bracketCount++;
+      // Allow an open bracket at the end for property completion.
+      if (character === '[' && (index < clippedExpression.length - 1 || !allowEndingBracket)) {
+        bracketCount--;
+        if (bracketCount < 0)
+          break;
+      }
+      index--;
     }
-    return result;
+    return clippedExpression.substring(index + 1).trim();
   }
 
   /**
-   * @param {!Array<string>} rawKeys
-   * @return {!UI.SuggestBox.Suggestions}
+   * @param {string} text
+   * @param {string} query
+   * @return {!Promise<!UI.SuggestBox.Suggestions>}
    */
-  function gotKeys(rawKeys) {
-    var caseSensitivePrefix = [];
-    var caseInsensitivePrefix = [];
-    var caseSensitiveAnywhere = [];
-    var caseInsensitiveAnywhere = [];
-    var quoteChar = '"';
-    if (query.startsWith('\''))
-      quoteChar = '\'';
-    var endChar = ')';
-    if (mapMatch[0].indexOf('set') !== -1)
-      endChar = ', ';
+  async _mapCompletions(text, query) {
+    var mapMatch = text.match(/\.\s*(get|set|delete)\s*\(\s*$/);
+    var executionContext = UI.context.flavor(SDK.ExecutionContext);
+    if (!executionContext || !mapMatch)
+      return [];
 
-    var sorter = rawKeys.length < 1000 ? String.naturalOrderComparator : undefined;
-    var keys = rawKeys.sort(sorter).map(key => quoteChar + key + quoteChar);
-
-    for (var key of keys) {
-      if (key.length < query.length)
-        continue;
-      if (query.length && key.toLowerCase().indexOf(query.toLowerCase()) === -1)
-        continue;
-      // Substitute actual newlines with newline characters. @see crbug.com/498421
-      var title = key.split('\n').join('\\n');
-      var text = title + endChar;
-
-      if (key.startsWith(query))
-        caseSensitivePrefix.push({text: text, title: title, priority: 4});
-      else if (key.toLowerCase().startsWith(query.toLowerCase()))
-        caseInsensitivePrefix.push({text: text, title: title, priority: 3});
-      else if (key.indexOf(query) !== -1)
-        caseSensitiveAnywhere.push({text: text, title: title, priority: 2});
-      else
-        caseInsensitiveAnywhere.push({text: text, title: title, priority: 1});
-    }
-    var suggestions = caseSensitivePrefix.concat(caseInsensitivePrefix, caseSensitiveAnywhere, caseInsensitiveAnywhere);
-    if (suggestions.length)
-      suggestions[0].subtitle = Common.UIString('Keys');
-    return suggestions;
-  }
-};
-
-/**
- * @param {string} expressionString
- * @param {string} query
- * @param {boolean=} force
- * @return {!Promise<!UI.SuggestBox.Suggestions>}
- */
-ObjectUI.JavaScriptAutocomplete.completionsForExpression = async function(expressionString, query, force) {
-  var executionContext = UI.context.flavor(SDK.ExecutionContext);
-  if (!executionContext)
-    return [];
-
-  var lastIndex = expressionString.length - 1;
-
-  var dotNotation = (expressionString[lastIndex] === '.');
-  var bracketNotation = (expressionString.length > 1 && expressionString[lastIndex] === '[');
-
-  if (dotNotation || bracketNotation)
-    expressionString = expressionString.substr(0, lastIndex);
-  else
-    expressionString = '';
-
-  // User is entering float value, do not suggest anything.
-  if ((expressionString && !isNaN(expressionString)) || (!expressionString && query && !isNaN(query)))
-    return [];
-
-
-  if (!query && !expressionString && !force)
-    return [];
-  var selectedFrame = executionContext.debuggerModel.selectedCallFrame();
-  if (!expressionString && selectedFrame) {
-    return completionsOnPause(selectedFrame);
-  } else {
+    var clippedExpression = this._clipExpression(text.substring(0, mapMatch.index));
     var result = await executionContext.evaluate(
         {
-          expression: expressionString,
+          expression: clippedExpression,
           objectGroup: 'completion',
           includeCommandLineAPI: true,
           silent: true,
@@ -188,132 +89,266 @@
           generatePreview: false
         },
         /* userGesture */ false, /* awaitPromise */ false);
-    var completionGroups = await completionsOnGlobal(result);
-    return receivedPropertyNames(completionGroups);
+    if (result.error || !!result.exceptionDetails || result.object.subtype !== 'map')
+      return [];
+    var properties = await result.object.getOwnPropertiesPromise(false);
+    var internalProperties = properties.internalProperties || [];
+    var entriesProperty = internalProperties.find(property => property.name === '[[Entries]]');
+    if (!entriesProperty)
+      return [];
+    var keysObj = await entriesProperty.value.callFunctionJSONPromise(getEntries);
+    return gotKeys(Object.keys(keysObj));
+
+    /**
+     * @suppressReceiverCheck
+     * @this {!Array<{key:?, value:?}>}
+     * @return {!Object}
+     */
+    function getEntries() {
+      var result = {__proto__: null};
+      for (var i = 0; i < this.length; i++) {
+        if (typeof this[i].key === 'string')
+          result[this[i].key] = true;
+      }
+      return result;
+    }
+
+    /**
+     * @param {!Array<string>} rawKeys
+     * @return {!UI.SuggestBox.Suggestions}
+     */
+    function gotKeys(rawKeys) {
+      var caseSensitivePrefix = [];
+      var caseInsensitivePrefix = [];
+      var caseSensitiveAnywhere = [];
+      var caseInsensitiveAnywhere = [];
+      var quoteChar = '"';
+      if (query.startsWith('\''))
+        quoteChar = '\'';
+      var endChar = ')';
+      if (mapMatch[0].indexOf('set') !== -1)
+        endChar = ', ';
+
+      var sorter = rawKeys.length < 1000 ? String.naturalOrderComparator : undefined;
+      var keys = rawKeys.sort(sorter).map(key => quoteChar + key + quoteChar);
+
+      for (var key of keys) {
+        if (key.length < query.length)
+          continue;
+        if (query.length && key.toLowerCase().indexOf(query.toLowerCase()) === -1)
+          continue;
+        // Substitute actual newlines with newline characters. @see crbug.com/498421
+        var title = key.split('\n').join('\\n');
+        var text = title + endChar;
+
+        if (key.startsWith(query))
+          caseSensitivePrefix.push({text: text, title: title, priority: 4});
+        else if (key.toLowerCase().startsWith(query.toLowerCase()))
+          caseInsensitivePrefix.push({text: text, title: title, priority: 3});
+        else if (key.indexOf(query) !== -1)
+          caseSensitiveAnywhere.push({text: text, title: title, priority: 2});
+        else
+          caseInsensitiveAnywhere.push({text: text, title: title, priority: 1});
+      }
+      var suggestions =
+          caseSensitivePrefix.concat(caseInsensitivePrefix, caseSensitiveAnywhere, caseInsensitiveAnywhere);
+      if (suggestions.length)
+        suggestions[0].subtitle = Common.UIString('Keys');
+      return suggestions;
+    }
   }
 
   /**
-   * @param {!SDK.RuntimeModel.EvaluationResult} result
-   * @return {!Promise<!Array<!ObjectUI.JavaScriptAutocomplete.CompletionGroup>>}
+   * @param {string} expressionString
+   * @param {string} query
+   * @param {boolean=} force
+   * @return {!Promise<!UI.SuggestBox.Suggestions>}
    */
-  async function completionsOnGlobal(result) {
-    if (result.error || !!result.exceptionDetails || !result.object)
+  async _completionsForExpression(expressionString, query, force) {
+    var executionContext = UI.context.flavor(SDK.ExecutionContext);
+    if (!executionContext)
       return [];
 
-    var object = result.object;
-    while (object && object.type === 'object' && object.subtype === 'proxy') {
-      var properties = await object.getOwnPropertiesPromise(false /* generatePreview */);
-      var internalProperties = properties.internalProperties || [];
-      var target = internalProperties.find(property => property.name === '[[Target]]');
-      object = target ? target.value : null;
-    }
-    if (!object)
+    var lastIndex = expressionString.length - 1;
+
+    var dotNotation = (expressionString[lastIndex] === '.');
+    var bracketNotation = (expressionString.length > 1 && expressionString[lastIndex] === '[');
+
+    if (dotNotation || bracketNotation)
+      expressionString = expressionString.substr(0, lastIndex);
+    else
+      expressionString = '';
+
+    // User is entering float value, do not suggest anything.
+    if ((expressionString && !isNaN(expressionString)) || (!expressionString && query && !isNaN(query)))
       return [];
-    var completions = [];
-    if (object.type === 'object' || object.type === 'function') {
-      completions =
-          await object.callFunctionJSONPromise(getCompletions, [SDK.RemoteObject.toCallArgument(object.subtype)]);
-    } else if (object.type === 'string' || object.type === 'number' || object.type === 'boolean') {
-      var evaluateResult = await executionContext.evaluate(
+
+
+    if (!query && !expressionString && !force)
+      return [];
+    var selectedFrame = executionContext.debuggerModel.selectedCallFrame();
+    var completionGroups;
+    var TEN_SECONDS = 10000;
+    var cache = this._expressionCache.get(expressionString);
+    if (cache && cache.date + TEN_SECONDS > Date.now()) {
+      completionGroups = await cache.value;
+    } else if (!expressionString && selectedFrame) {
+      cache = {date: Date.now(), value: completionsOnPause(selectedFrame)};
+      this._expressionCache.set(expressionString, cache);
+      completionGroups = await cache.value;
+    } else {
+      var resultPromise = executionContext.evaluate(
           {
-            expression: '(' + getCompletions + ')("' + object.type + '")',
+            expression: expressionString,
             objectGroup: 'completion',
-            includeCommandLineAPI: false,
+            includeCommandLineAPI: true,
             silent: true,
-            returnByValue: true,
+            returnByValue: false,
             generatePreview: false
           },
-          /* userGesture */ false,
-          /* awaitPromise */ false);
-      if (evaluateResult.object && !evaluateResult.exceptionDetails)
-        completions = evaluateResult.object.value;
+          /* userGesture */ false, /* awaitPromise */ false);
+      cache = {date: Date.now(), value: resultPromise.then(result => completionsOnGlobal.call(this, result))};
+      this._expressionCache.set(expressionString, cache);
+      completionGroups = await cache.value;
     }
-    executionContext.runtimeModel.releaseObjectGroup('completion');
-
-    if (!expressionString) {
-      var globalNames = await executionContext.globalLexicalScopeNames();
-      // Merge lexical scope names with first completion group on global object: var a and let b should be in the same group.
-      if (completions.length)
-        completions[0].items = completions[0].items.concat(globalNames);
-      else
-        completions.push({items: globalNames, title: Common.UIString('Lexical scope variables')});
-    }
-    return completions;
+    return this._receivedPropertyNames(completionGroups, dotNotation, bracketNotation, expressionString, query);
 
     /**
-     * @param {string=} type
-     * @return {!Object}
-     * @suppressReceiverCheck
-     * @this {Object}
+     * @this {ObjectUI.JavaScriptAutocomplete}
+     * @param {!SDK.RuntimeModel.EvaluationResult} result
+     * @return {!Promise<!Array<!ObjectUI.JavaScriptAutocomplete.CompletionGroup>>}
      */
-    function getCompletions(type) {
-      var object;
-      if (type === 'string')
-        object = new String('');
-      else if (type === 'number')
-        object = new Number(0);
-      else if (type === 'boolean')
-        object = new Boolean(false);
-      else
-        object = this;
+    async function completionsOnGlobal(result) {
+      if (result.error || !!result.exceptionDetails || !result.object)
+        return [];
 
-      var result = [];
-      try {
-        for (var o = object; o; o = Object.getPrototypeOf(o)) {
-          if ((type === 'array' || type === 'typedarray') && o === object && o.length > 9999)
-            continue;
-
-          var group = {items: [], __proto__: null};
-          try {
-            if (typeof o === 'object' && o.constructor && o.constructor.name)
-              group.title = o.constructor.name;
-          } catch (ee) {
-            // we could break upon cross origin check.
-          }
-          result[result.length] = group;
-          var names = Object.getOwnPropertyNames(o);
-          var isArray = Array.isArray(o);
-          for (var i = 0; i < names.length; ++i) {
-            // Skip array elements indexes.
-            if (isArray && /^[0-9]/.test(names[i]))
-              continue;
-            group.items[group.items.length] = names[i];
-          }
-        }
-      } catch (e) {
+      var object = result.object;
+      while (object && object.type === 'object' && object.subtype === 'proxy') {
+        var properties = await object.getOwnPropertiesPromise(false /* generatePreview */);
+        var internalProperties = properties.internalProperties || [];
+        var target = internalProperties.find(property => property.name === '[[Target]]');
+        object = target ? target.value : null;
       }
+      if (!object)
+        return [];
+      var completions = [];
+      if (object.type === 'object' || object.type === 'function') {
+        completions =
+            await object.callFunctionJSONPromise(getCompletions, [SDK.RemoteObject.toCallArgument(object.subtype)]) ||
+            [];
+      } else if (object.type === 'string' || object.type === 'number' || object.type === 'boolean') {
+        var evaluateResult = await executionContext.evaluate(
+            {
+              expression: '(' + getCompletions + ')("' + object.type + '")',
+              objectGroup: 'completion',
+              includeCommandLineAPI: false,
+              silent: true,
+              returnByValue: true,
+              generatePreview: false
+            },
+            /* userGesture */ false,
+            /* awaitPromise */ false);
+        if (evaluateResult.object && !evaluateResult.exceptionDetails)
+          completions = evaluateResult.object.value || [];
+      }
+      executionContext.runtimeModel.releaseObjectGroup('completion');
+
+      if (!expressionString) {
+        var globalNames = await executionContext.globalLexicalScopeNames();
+        // Merge lexical scope names with first completion group on global object: var a and let b should be in the same group.
+        if (completions.length)
+          completions[0].items = completions[0].items.concat(globalNames);
+        else
+          completions.push({items: globalNames.sort(), title: Common.UIString('Lexical scope variables')});
+      }
+
+      for (var group of completions) {
+        for (var i = 0; i < group.items.length; i++)
+          group.items[i] = group.items[i].replace(/\n/g, '\\n');
+
+        group.items.sort(group.items.length < 1000 ? this._itemComparator : undefined);
+      }
+
+      return completions;
+
+      /**
+       * @param {string=} type
+       * @return {!Object}
+       * @suppressReceiverCheck
+       * @this {Object}
+       */
+      function getCompletions(type) {
+        var object;
+        if (type === 'string')
+          object = new String('');
+        else if (type === 'number')
+          object = new Number(0);
+        else if (type === 'boolean')
+          object = new Boolean(false);
+        else
+          object = this;
+
+        var result = [];
+        try {
+          for (var o = object; o; o = Object.getPrototypeOf(o)) {
+            if ((type === 'array' || type === 'typedarray') && o === object && o.length > 9999)
+              continue;
+
+            var group = {items: [], __proto__: null};
+            try {
+              if (typeof o === 'object' && o.constructor && o.constructor.name)
+                group.title = o.constructor.name;
+            } catch (ee) {
+              // we could break upon cross origin check.
+            }
+            result[result.length] = group;
+            var names = Object.getOwnPropertyNames(o);
+            var isArray = Array.isArray(o);
+            for (var i = 0; i < names.length; ++i) {
+              // Skip array elements indexes.
+              if (isArray && /^[0-9]/.test(names[i]))
+                continue;
+              group.items[group.items.length] = names[i];
+            }
+          }
+        } catch (e) {
+        }
+        return result;
+      }
+    }
+
+    /**
+     * @param {!SDK.DebuggerModel.CallFrame} callFrame
+     * @return {!Promise<?Object>}
+     */
+    async function completionsOnPause(callFrame) {
+      var result = [{items: ['this']}];
+      var scopeChain = callFrame.scopeChain();
+      var groupPromises = [];
+      for (var scope of scopeChain) {
+        groupPromises.push(scope.object()
+                               .getAllPropertiesPromise(false /* accessorPropertiesOnly */, false /* generatePreview */)
+                               .then(result => ({properties: result.properties, name: scope.name()})));
+      }
+      var fullScopes = await Promise.all(groupPromises);
+      executionContext.runtimeModel.releaseObjectGroup('completion');
+      for (var scope of fullScopes)
+        result.push({title: scope.name, items: scope.properties.map(property => property.name).sort()});
       return result;
     }
   }
 
   /**
-   * @param {!SDK.DebuggerModel.CallFrame} callFrame
-   * @return {!Promise<!UI.SuggestBox.Suggestions>}
-   */
-  async function completionsOnPause(callFrame) {
-    var result = [{items: ['this']}];
-    var scopeChain = callFrame.scopeChain();
-    var groupPromises = [];
-    for (var scope of scopeChain) {
-      groupPromises.push(scope.object()
-                             .getAllPropertiesPromise(false /* accessorPropertiesOnly */, false /* generatePreview */)
-                             .then(result => ({properties: result.properties, name: scope.name()})));
-    }
-    var fullScopes = await Promise.all(groupPromises);
-    executionContext.runtimeModel.releaseObjectGroup('completion');
-    for (var scope of fullScopes)
-      result.push({title: scope.name, items: scope.properties.map(property => property.name)});
-    return receivedPropertyNames(result);
-  }
-
-  /**
-   * @param {?Object} completions
+   * @param {?Array<!ObjectUI.JavaScriptAutocomplete.CompletionGroup>} propertyGroups
+   * @param {boolean} dotNotation
+   * @param {boolean} bracketNotation
+   * @param {string} expressionString
+   * @param {string} query
    * @return {!UI.SuggestBox.Suggestions}
    */
-  function receivedPropertyNames(completions) {
-    if (!completions)
+  _receivedPropertyNames(propertyGroups, dotNotation, bracketNotation, expressionString, query) {
+    if (!propertyGroups)
       return [];
-    var propertyGroups = /** @type {!Array<!ObjectUI.JavaScriptAutocomplete.CompletionGroup>} */ (completions);
     var includeCommandLineAPI = (!dotNotation && !bracketNotation);
     if (includeCommandLineAPI) {
       const commandLineAPI = [
@@ -341,100 +376,98 @@
       ];
       propertyGroups.push({items: commandLineAPI});
     }
-    return ObjectUI.JavaScriptAutocomplete._completionsForQuery(
-        dotNotation, bracketNotation, expressionString, query, propertyGroups);
+    return this._completionsForQuery(dotNotation, bracketNotation, expressionString, query, propertyGroups);
   }
-};
-
-/**
-   * @param {boolean} dotNotation
-   * @param {boolean} bracketNotation
-   * @param {string} expressionString
-   * @param {string} query
-   * @param {!Array<!ObjectUI.JavaScriptAutocomplete.CompletionGroup>} propertyGroups
-   * @return {!UI.SuggestBox.Suggestions}
-   */
-ObjectUI.JavaScriptAutocomplete._completionsForQuery = function(
-    dotNotation, bracketNotation, expressionString, query, propertyGroups) {
-  if (bracketNotation) {
-    if (query.length && query[0] === '\'')
-      var quoteUsed = '\'';
-    else
-      var quoteUsed = '"';
-  }
-
-  if (!expressionString) {
-    const keywords = [
-      'break', 'case',     'catch',  'continue', 'default',    'delete', 'do',     'else',   'finally',
-      'for',   'function', 'if',     'in',       'instanceof', 'new',    'return', 'switch', 'this',
-      'throw', 'try',      'typeof', 'var',      'void',       'while',  'with'
-    ];
-    propertyGroups.push({title: Common.UIString('keywords'), items: keywords});
-  }
-
-  var result = [];
-  var lastGroupTitle;
-  for (var group of propertyGroups) {
-    group.items.sort(itemComparator.bind(null, group.items.length > 1000));
-    var caseSensitivePrefix = [];
-    var caseInsensitivePrefix = [];
-    var caseSensitiveAnywhere = [];
-    var caseInsensitiveAnywhere = [];
-
-    for (var property of group.items) {
-      // Assume that all non-ASCII characters are letters and thus can be used as part of identifier.
-      if (!bracketNotation && !/^[a-zA-Z_$\u008F-\uFFFF][a-zA-Z0-9_$\u008F-\uFFFF]*$/.test(property))
-        continue;
-
-      if (bracketNotation) {
-        if (!/^[0-9]+$/.test(property))
-          property = quoteUsed + property.escapeCharacters(quoteUsed + '\\') + quoteUsed;
-        property += ']';
-      }
-
-      if (property.length < query.length)
-        continue;
-      if (query.length && property.toLowerCase().indexOf(query.toLowerCase()) === -1)
-        continue;
-      // Substitute actual newlines with newline characters. @see crbug.com/498421
-      var prop = property.split('\n').join('\\n');
-
-      if (property.startsWith(query))
-        caseSensitivePrefix.push({text: prop, priority: 4});
-      else if (property.toLowerCase().startsWith(query.toLowerCase()))
-        caseInsensitivePrefix.push({text: prop, priority: 3});
-      else if (property.indexOf(query) !== -1)
-        caseSensitiveAnywhere.push({text: prop, priority: 2});
-      else
-        caseInsensitiveAnywhere.push({text: prop, priority: 1});
-    }
-    var structuredGroup =
-        caseSensitivePrefix.concat(caseInsensitivePrefix, caseSensitiveAnywhere, caseInsensitiveAnywhere);
-    if (structuredGroup.length && group.title !== lastGroupTitle) {
-      structuredGroup[0].subtitle = group.title;
-      lastGroupTitle = group.title;
-    }
-    result = result.concat(structuredGroup);
-    result.forEach(item => {
-      if (item.text.endsWith(']'))
-        item.title = item.text.substring(0, item.text.length - 1);
-    });
-  }
-  return result;
 
   /**
-   * @param {boolean} naturalOrder
+     * @param {boolean} dotNotation
+     * @param {boolean} bracketNotation
+     * @param {string} expressionString
+     * @param {string} query
+     * @param {!Array<!ObjectUI.JavaScriptAutocomplete.CompletionGroup>} propertyGroups
+     * @return {!UI.SuggestBox.Suggestions}
+     */
+  _completionsForQuery(dotNotation, bracketNotation, expressionString, query, propertyGroups) {
+    var quoteUsed = (bracketNotation && query.startsWith('\'')) ? '\'' : '"';
+
+    if (!expressionString) {
+      const keywords = [
+        'break', 'case',     'catch',  'continue', 'default',    'delete', 'do',     'else',   'finally',
+        'for',   'function', 'if',     'in',       'instanceof', 'new',    'return', 'switch', 'this',
+        'throw', 'try',      'typeof', 'var',      'void',       'while',  'with'
+      ];
+      propertyGroups.push({title: Common.UIString('keywords'), items: keywords});
+    }
+
+    var result = [];
+    var lastGroupTitle;
+    var regex = /^[a-zA-Z_$\u008F-\uFFFF][a-zA-Z0-9_$\u008F-\uFFFF]*$/;
+    var lowerCaseQuery = query.toLowerCase();
+    for (var group of propertyGroups) {
+      var caseSensitivePrefix = [];
+      var caseInsensitivePrefix = [];
+      var caseSensitiveAnywhere = [];
+      var caseInsensitiveAnywhere = [];
+
+      for (var i = 0; i < group.items.length; i++) {
+        var property = group.items[i];
+        // Assume that all non-ASCII characters are letters and thus can be used as part of identifier.
+        if (!bracketNotation && !regex.test(property))
+          continue;
+
+        if (bracketNotation) {
+          if (!/^[0-9]+$/.test(property))
+            property = quoteUsed + property.escapeCharacters(quoteUsed + '\\') + quoteUsed;
+          property += ']';
+        }
+
+        if (property.length < query.length)
+          continue;
+        var lowerCaseProperty = property.toLowerCase();
+        if (query.length && lowerCaseProperty.indexOf(lowerCaseQuery) === -1)
+          continue;
+
+        if (property.startsWith(query))
+          caseSensitivePrefix.push({text: property, priority: 4});
+        else if (lowerCaseProperty.startsWith(lowerCaseQuery))
+          caseInsensitivePrefix.push({text: property, priority: 3});
+        else if (property.indexOf(query) !== -1)
+          caseSensitiveAnywhere.push({text: property, priority: 2});
+        else
+          caseInsensitiveAnywhere.push({text: property, priority: 1});
+      }
+      var structuredGroup =
+          caseSensitivePrefix.concat(caseInsensitivePrefix, caseSensitiveAnywhere, caseInsensitiveAnywhere);
+      if (structuredGroup.length && group.title !== lastGroupTitle) {
+        structuredGroup[0].subtitle = group.title;
+        lastGroupTitle = group.title;
+      }
+      result = result.concat(structuredGroup);
+      result.forEach(item => {
+        if (item.text.endsWith(']'))
+          item.title = item.text.substring(0, item.text.length - 1);
+      });
+    }
+    return result;
+  }
+
+  /**
    * @param {string} a
    * @param {string} b
    * @return {number}
    */
-  function itemComparator(naturalOrder, a, b) {
+  _itemComparator(a, b) {
     var aStartsWithUnderscore = a.startsWith('_');
     var bStartsWithUnderscore = b.startsWith('_');
     if (aStartsWithUnderscore && !bStartsWithUnderscore)
       return 1;
     if (bStartsWithUnderscore && !aStartsWithUnderscore)
       return -1;
-    return naturalOrder ? String.naturalOrderComparator(a, b) : a.localeCompare(b);
+    return String.naturalOrderComparator(a, b);
   }
 };
+
+/** @typedef {{title:(string|undefined), items:Array<string>}} */
+ObjectUI.JavaScriptAutocomplete.CompletionGroup;
+
+ObjectUI.javaScriptAutocomplete = new ObjectUI.JavaScriptAutocomplete();
\ No newline at end of file
diff --git a/third_party/WebKit/Source/devtools/front_end/object_ui/ObjectPropertiesSection.js b/third_party/WebKit/Source/devtools/front_end/object_ui/ObjectPropertiesSection.js
index f12b43cc..a3ab3f6e 100644
--- a/third_party/WebKit/Source/devtools/front_end/object_ui/ObjectPropertiesSection.js
+++ b/third_party/WebKit/Source/devtools/front_end/object_ui/ObjectPropertiesSection.js
@@ -1267,7 +1267,7 @@
 ObjectUI.ObjectPropertyPrompt = class extends UI.TextPrompt {
   constructor() {
     super();
-    this.initialize(ObjectUI.JavaScriptAutocomplete.completionsForTextInCurrentContext);
+    this.initialize(ObjectUI.javaScriptAutocomplete.completionsForTextInCurrentContext);
   }
 };
 
diff --git a/third_party/WebKit/Source/modules/accessibility/AXNodeObject.cpp b/third_party/WebKit/Source/modules/accessibility/AXNodeObject.cpp
index b3670ffb..ad507f6 100644
--- a/third_party/WebKit/Source/modules/accessibility/AXNodeObject.cpp
+++ b/third_party/WebKit/Source/modules/accessibility/AXNodeObject.cpp
@@ -28,6 +28,8 @@
 
 #include "modules/accessibility/AXNodeObject.h"
 
+#include <math.h>
+
 #include "core/dom/AccessibleNode.h"
 #include "core/dom/Element.h"
 #include "core/dom/FlatTreeTraversal.h"
@@ -93,8 +95,8 @@
   DCHECK(!node_);
 }
 
-void AXNodeObject::AlterSliderValue(bool increase) {
-  if (RoleValue() != kSliderRole)
+void AXNodeObject::AlterSliderOrSpinButtonValue(bool increase) {
+  if (!IsSlider() && !IsSpinButton())
     return;
 
   float value;
@@ -957,12 +959,22 @@
   return RoleValue() == kSliderRole;
 }
 
+bool AXNodeObject::IsSpinButton() const {
+  return RoleValue() == kSpinButtonRole;
+}
+
 bool AXNodeObject::IsNativeSlider() const {
   if (auto* input = ToHTMLInputElementOrNull(GetNode()))
     return input->type() == InputTypeNames::range;
   return false;
 }
 
+bool AXNodeObject::IsNativeSpinButton() const {
+  if (auto* input = ToHTMLInputElementOrNull(GetNode()))
+    return input->type() == InputTypeNames::number;
+  return false;
+}
+
 bool AXNodeObject::IsMoveableSplitter() const {
   return RoleValue() == kSplitterRole && CanSetFocusAttribute();
 }
@@ -1545,7 +1557,7 @@
 
   if (IsNativeSlider() || IsNativeSpinButton()) {
     *out_value = ToHTMLInputElement(*GetNode()).valueAsNumber();
-    return true;
+    return isfinite(*out_value);
   }
 
   if (auto* meter = ToHTMLMeterElementOrNull(GetNode())) {
@@ -1588,9 +1600,9 @@
     return true;
   }
 
-  if (IsNativeSlider()) {
-    *out_value = ToHTMLInputElement(*GetNode()).Maximum();
-    return true;
+  if (IsNativeSlider() || IsNativeSpinButton()) {
+    *out_value = static_cast<float>(ToHTMLInputElement(*GetNode()).Maximum());
+    return isfinite(*out_value);
   }
 
   if (auto* meter = ToHTMLMeterElementOrNull(GetNode())) {
@@ -1621,9 +1633,9 @@
     return true;
   }
 
-  if (IsNativeSlider()) {
-    *out_value = ToHTMLInputElement(*GetNode()).Minimum();
-    return true;
+  if (IsNativeSlider() || IsNativeSpinButton()) {
+    *out_value = static_cast<float>(ToHTMLInputElement(*GetNode()).Minimum());
+    return isfinite(*out_value);
   }
 
   if (auto* meter = ToHTMLMeterElementOrNull(GetNode())) {
@@ -1648,11 +1660,11 @@
 }
 
 bool AXNodeObject::StepValueForRange(float* out_value) const {
-  if (IsNativeSlider()) {
+  if (IsNativeSlider() || IsNativeSpinButton()) {
     Decimal step =
         ToHTMLInputElement(*GetNode()).CreateStepRange(kRejectAny).Step();
     *out_value = step.ToString().ToFloat();
-    return true;
+    return isfinite(*out_value);
   }
 
   switch (AriaRoleAttribute()) {
@@ -2342,7 +2354,7 @@
   LocalFrame* frame = GetDocument() ? GetDocument()->GetFrame() : nullptr;
   std::unique_ptr<UserGestureIndicator> gesture_indicator =
       Frame::NotifyUserActivation(frame, UserGestureToken::kNewGesture);
-  AlterSliderValue(true);
+  AlterSliderOrSpinButtonValue(true);
   return true;
 }
 
@@ -2350,7 +2362,7 @@
   LocalFrame* frame = GetDocument() ? GetDocument()->GetFrame() : nullptr;
   std::unique_ptr<UserGestureIndicator> gesture_indicator =
       Frame::NotifyUserActivation(frame, UserGestureToken::kNewGesture);
-  AlterSliderValue(false);
+  AlterSliderOrSpinButtonValue(false);
   return true;
 }
 
diff --git a/third_party/WebKit/Source/modules/accessibility/AXNodeObject.h b/third_party/WebKit/Source/modules/accessibility/AXNodeObject.h
index 3175167..6a900f8 100644
--- a/third_party/WebKit/Source/modules/accessibility/AXNodeObject.h
+++ b/third_party/WebKit/Source/modules/accessibility/AXNodeObject.h
@@ -61,7 +61,7 @@
   virtual AccessibilityRole NativeAccessibilityRoleIgnoringAria() const;
   String AccessibilityDescriptionForElements(
       HeapVector<Member<Element>>& elements) const;
-  void AlterSliderValue(bool increase);
+  void AlterSliderOrSpinButtonValue(bool increase);
   AXObject* ActiveDescendant() override;
   String AriaAccessibilityDescription() const;
   String AriaAutoComplete() const;
@@ -116,7 +116,9 @@
   bool IsProgressIndicator() const override;
   bool IsRichlyEditable() const override;
   bool IsSlider() const override;
+  bool IsSpinButton() const override;
   bool IsNativeSlider() const override;
+  bool IsNativeSpinButton() const override;
   bool IsMoveableSplitter() const override;
 
   // Check object state.
diff --git a/third_party/WebKit/Source/modules/vr/VRDisplay.cpp b/third_party/WebKit/Source/modules/vr/VRDisplay.cpp
index 7db31d0..c8d1813 100644
--- a/third_party/WebKit/Source/modules/vr/VRDisplay.cpp
+++ b/third_party/WebKit/Source/modules/vr/VRDisplay.cpp
@@ -47,6 +47,12 @@
 
 namespace {
 
+// Threshold for rejecting stored magic window poses as being too old.
+// If it's exceeded, defer magic window rAF callback execution until
+// a fresh pose is received.
+constexpr WTF::TimeDelta kMagicWindowPoseAgeThreshold =
+    WTF::TimeDelta::FromMilliseconds(250);
+
 VREye StringToVREye(const String& which_eye) {
   if (which_eye == "left")
     return kVREyeLeft;
@@ -214,6 +220,7 @@
   if (!is_presenting_) {
     if (pending_magic_window_vsync_)
       return;
+    magic_window_vsync_waiting_for_pose_.Reset();
     magic_window_provider_->GetPose(
         WTF::Bind(&VRDisplay::OnMagicWindowPose, WrapWeakPersistent(this)));
     pending_magic_window_vsync_ = true;
@@ -1042,15 +1049,31 @@
   if (is_presenting_)
     return;
   vr_frame_id_ = -1;
-  ProcessScheduledAnimations(timestamp);
+  WTF::TimeDelta pose_age = WTF::CurrentTimeTicks() - magic_window_pose_time_;
+  if (pose_age < kMagicWindowPoseAgeThreshold) {
+    ProcessScheduledAnimations(timestamp);
+  } else {
+    // The VSync got triggered before ever getting a pose, or the pose is
+    // stale. Defer the animation until a pose arrives to avoid passing null
+    // poses to the application.
+    magic_window_vsync_waiting_for_pose_ =
+        WTF::Bind(&VRDisplay::ProcessScheduledAnimations,
+                  WrapWeakPersistent(this), timestamp);
+  }
 }
 
 void VRDisplay::OnMagicWindowPose(device::mojom::blink::VRPosePtr pose) {
+  magic_window_pose_time_ = WTF::CurrentTimeTicks();
   if (!in_animation_frame_) {
     frame_pose_ = std::move(pose);
   } else {
     pending_pose_ = std::move(pose);
   }
+  if (magic_window_vsync_waiting_for_pose_) {
+    // We have a vsync waiting for a pose, run it now.
+    std::move(magic_window_vsync_waiting_for_pose_).Run();
+    magic_window_vsync_waiting_for_pose_.Reset();
+  }
 }
 
 void VRDisplay::OnPresentationProviderConnectionError() {
diff --git a/third_party/WebKit/Source/modules/vr/VRDisplay.h b/third_party/WebKit/Source/modules/vr/VRDisplay.h
index 39d3f86..28d0d67 100644
--- a/third_party/WebKit/Source/modules/vr/VRDisplay.h
+++ b/third_party/WebKit/Source/modules/vr/VRDisplay.h
@@ -16,6 +16,7 @@
 #include "platform/Timer.h"
 #include "platform/heap/Handle.h"
 #include "platform/wtf/Forward.h"
+#include "platform/wtf/Functional.h"
 #include "platform/wtf/text/WTFString.h"
 #include "public/platform/WebGraphicsContext3DProvider.h"
 
@@ -246,6 +247,8 @@
   bool pending_presenting_vsync_ = false;
   bool pending_magic_window_vsync_ = false;
   int pending_magic_window_vsync_id_ = -1;
+  base::OnceClosure magic_window_vsync_waiting_for_pose_;
+  WTF::TimeTicks magic_window_pose_time_;
   bool in_animation_frame_ = false;
   bool did_submit_this_frame_ = false;
   bool display_blurred_ = false;
diff --git a/third_party/WebKit/Source/platform/graphics/CanvasResource.cpp b/third_party/WebKit/Source/platform/graphics/CanvasResource.cpp
index 98e1d9e..a33dc2cc 100644
--- a/third_party/WebKit/Source/platform/graphics/CanvasResource.cpp
+++ b/third_party/WebKit/Source/platform/graphics/CanvasResource.cpp
@@ -272,6 +272,9 @@
   if (gpu_mailbox_.IsZero() && gl) {
     gl->GenMailboxCHROMIUM(gpu_mailbox_.name);
     gl->ProduceTextureDirectCHROMIUM(texture_id_, gpu_mailbox_.name);
+  }
+  if (mailbox_needs_new_sync_token_) {
+    mailbox_needs_new_sync_token_ = false;
     gl->GenUnverifiedSyncTokenCHROMIUM(sync_token_.GetData());
   }
   return gpu_mailbox_;
@@ -295,6 +298,7 @@
       source_texture, 0 /*sourceLevel*/, TextureTarget(), texture_id_,
       0 /*destLevel*/, format, type, false /*unpackFlipY*/,
       false /*unpackPremultiplyAlpha*/, false /*unpackUnmultiplyAlpha*/);
+  mailbox_needs_new_sync_token_ = true;
 }
 
 base::WeakPtr<WebGraphicsContext3DProviderWrapper>
diff --git a/third_party/WebKit/Source/platform/graphics/CanvasResource.h b/third_party/WebKit/Source/platform/graphics/CanvasResource.h
index 64963f7..668b2c1 100644
--- a/third_party/WebKit/Source/platform/graphics/CanvasResource.h
+++ b/third_party/WebKit/Source/platform/graphics/CanvasResource.h
@@ -145,6 +145,7 @@
 
   gpu::Mailbox gpu_mailbox_;
   gpu::SyncToken sync_token_;
+  bool mailbox_needs_new_sync_token_ = true;
   base::WeakPtr<WebGraphicsContext3DProviderWrapper> context_provider_wrapper_;
   std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer_;
   GLuint image_id_ = 0;
diff --git a/third_party/WebKit/Source/platform/runtime_enabled_features.json5 b/third_party/WebKit/Source/platform/runtime_enabled_features.json5
index b23d6796..4284f35 100644
--- a/third_party/WebKit/Source/platform/runtime_enabled_features.json5
+++ b/third_party/WebKit/Source/platform/runtime_enabled_features.json5
@@ -334,7 +334,7 @@
     },
     {
       name: "DisplayNoneIFrameCreatesNoLayoutObject",
-      status: "stable",
+      status: "experimental",
     },
     {
       name: "DocumentCookie",
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index f95e23c..3a2515d 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -2874,6 +2874,7 @@
   <int value="189" label="RFH_INTERFACE_PROVIDER_SUPERFLUOUS"/>
   <int value="190" label="AIRH_UNEXPECTED_BITSTREAM"/>
   <int value="191" label="ARH_UNEXPECTED_BITSTREAM"/>
+  <int value="192" label="RDH_NULL_CLIENT"/>
 </enum>
 
 <enum name="BadMessageReasonExtensions">
@@ -14274,6 +14275,7 @@
   <int value="1206" label="DEVELOPERPRIVATE_NOTIFYDRAGINSTALLINPROGRESS"/>
   <int value="1207" label="AUTOTESTPRIVATE_GETPRINTERLIST"/>
   <int value="1208" label="DEVELOPERPRIVATE_GETEXTENSIONSIZE"/>
+  <int value="1209" label="CRYPTOTOKENPRIVATE_ISAPPIDHASHINENTERPRISECONTEXT"/>
 </enum>
 
 <enum name="ExtensionIconState">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index c96114f..10623c5 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -33366,6 +33366,10 @@
 
 <histogram name="Media.ChromeArcVideoDecodeAccelerator.InitializeResult"
     enum="ArcVideoDecodeAcceleratorResult">
+  <obsolete>
+    Deprecated as of Dec 18, 2017. Replaced by
+    Media.GpuArcVideoDecodeAccelerator.InitializeResult.
+  </obsolete>
   <owner>johnylin@chromium.org</owner>
   <summary>
     Counts of status values returned from calls to
diff --git a/ui/message_center/notification_delegate.cc b/ui/message_center/notification_delegate.cc
index e07e48b..27cc820 100644
--- a/ui/message_center/notification_delegate.cc
+++ b/ui/message_center/notification_delegate.cc
@@ -29,12 +29,13 @@
 // HandleNotificationClickDelegate:
 
 HandleNotificationClickDelegate::HandleNotificationClickDelegate(
-    const base::Closure& callback) {
+    const base::RepeatingClosure& callback) {
   if (!callback.is_null()) {
     // Create a callback that consumes and ignores the button index parameter,
     // and just runs the provided closure.
     callback_ = base::Bind(
-        [](const base::Closure& closure, base::Optional<int> button_index) {
+        [](const base::RepeatingClosure& closure,
+           base::Optional<int> button_index) {
           DCHECK(!button_index);
           closure.Run();
         },
diff --git a/ui/message_center/notification_delegate.h b/ui/message_center/notification_delegate.h
index 399e237..9628854 100644
--- a/ui/message_center/notification_delegate.h
+++ b/ui/message_center/notification_delegate.h
@@ -60,14 +60,16 @@
  public:
   // The parameter is the index of the button that was clicked, or nullopt if
   // the body was clicked.
-  using ButtonClickCallback = base::Callback<void(base::Optional<int>)>;
+  using ButtonClickCallback =
+      base::RepeatingCallback<void(base::Optional<int>)>;
 
   // Creates a delegate that handles clicks on a button or on the body.
   explicit HandleNotificationClickDelegate(const ButtonClickCallback& callback);
 
   // Creates a delegate that only handles clicks on the body of the
   // notification.
-  explicit HandleNotificationClickDelegate(const base::Closure& closure);
+  explicit HandleNotificationClickDelegate(
+      const base::RepeatingClosure& closure);
 
   // message_center::NotificationDelegate overrides:
   void Click() override;