diff --git a/DEPS b/DEPS
index 105fa2d..a7ba98a 100644
--- a/DEPS
+++ b/DEPS
@@ -40,7 +40,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '7cf774573cc5bc7e588ed4489d0a4127e9edf0cc',
+  'skia_revision': 'a4cb388c0fe83de153806cbe42e4b7d47a069c09',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
diff --git a/chrome/VERSION b/chrome/VERSION
index 53b8ef1a..dbc9ac2 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=57
 MINOR=0
-BUILD=2966
+BUILD=2967
 PATCH=0
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index e18d378..4701a6b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -839,34 +839,28 @@
                     openNewTab(url, referer, headers, externalAppId, intent, true);
                     break;
                 case OPEN_NEW_INCOGNITO_TAB:
+                    if (!TextUtils.equals(externalAppId, getPackageName())) {
+                        assert false : "Only Chrome is allowed to open incognito tabs";
+                        Log.e(TAG, "Only Chrome is allowed to open incognito tabs");
+                        return;
+                    }
+
                     if (url == null || url.equals(UrlConstants.NTP_URL)) {
-                        TabLaunchType launchType;
                         if (fromLauncherShortcut) {
                             getTabCreator(true).launchUrl(
                                     UrlConstants.NTP_URL, TabLaunchType.FROM_EXTERNAL_APP);
                             recordLauncherShortcutAction(true);
                             reportNewTabShortcutUsed(true);
-                        } else if (TextUtils.equals(externalAppId, getPackageName())) {
+                        } else {
                             // Used by the Account management screen to open a new incognito tab.
                             // Account management screen collects its metrics separately.
                             getTabCreator(true).launchUrl(
                                     UrlConstants.NTP_URL, TabLaunchType.FROM_CHROME_UI,
                                     intent, mIntentHandlingTimeMs);
-                        } else {
-                            getTabCreator(true).launchUrl(
-                                    UrlConstants.NTP_URL, TabLaunchType.FROM_EXTERNAL_APP,
-                                    intent, mIntentHandlingTimeMs);
-                            RecordUserAction.record("MobileReceivedExternalIntent");
                         }
                     } else {
-                        if (TextUtils.equals(externalAppId, getPackageName())) {
-                            getTabCreator(true).launchUrl(
-                                    url, TabLaunchType.FROM_LINK, intent, mIntentHandlingTimeMs);
-                        } else {
-                            getTabCreator(true).launchUrlFromExternalApp(url, referer, headers,
-                                    externalAppId, true, intent, mIntentHandlingTimeMs);
-                            RecordUserAction.record("MobileReceivedExternalIntent");
-                        }
+                        getTabCreator(true).launchUrl(
+                                url, TabLaunchType.FROM_LINK, intent, mIntentHandlingTimeMs);
                     }
                     break;
                 default:
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
index c05dd117..1ac4ba92 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
@@ -662,9 +662,16 @@
             // Determine if this intent came from a trustworthy source (either Chrome or Google
             // first party applications).
             boolean isInternal = isIntentChromeOrFirstParty(intent, context);
+            boolean isFromChrome = wasIntentSenderChrome(intent, context);
 
-            // "Open new incognito tab" is currently limited to Chrome or first parties.
-            if (!isInternal
+            // "Open new incognito tab" is currently limited to Chrome.
+            //
+            // The pending incognito URL check is to handle the case where the user is shown an
+            // Android intent picker while in incognito and they select the current Chrome instance
+            // from the list.  In this case, we do not apply our Chrome token as the user has the
+            // option to select apps outside of our control, so we rely on this in memory check
+            // instead.
+            if (!isFromChrome
                     && IntentUtils.safeGetBooleanExtra(
                             intent, EXTRA_OPEN_NEW_INCOGNITO_TAB, false)
                     && (getPendingIncognitoUrl() == null
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 11e4ded..d6de7270 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -757,6 +757,8 @@
     "page_load_metrics/metrics_web_contents_observer.h",
     "page_load_metrics/observers/aborts_page_load_metrics_observer.cc",
     "page_load_metrics/observers/aborts_page_load_metrics_observer.h",
+    "page_load_metrics/observers/amp_page_load_metrics_observer.cc",
+    "page_load_metrics/observers/amp_page_load_metrics_observer.h",
     "page_load_metrics/observers/core_page_load_metrics_observer.cc",
     "page_load_metrics/observers/core_page_load_metrics_observer.h",
     "page_load_metrics/observers/css_scanning_page_load_metrics_observer.cc",
diff --git a/chrome/browser/chromeos/accessibility/accessibility_manager.cc b/chrome/browser/chromeos/accessibility/accessibility_manager.cc
index ad71e6d4..263482da 100644
--- a/chrome/browser/chromeos/accessibility/accessibility_manager.cc
+++ b/chrome/browser/chromeos/accessibility/accessibility_manager.cc
@@ -79,6 +79,7 @@
 #include "extensions/common/host_id.h"
 #include "mash/public/interfaces/launchable.mojom.h"
 #include "media/audio/sounds/sounds_manager.h"
+#include "media/base/media_switches.h"
 #include "services/service_manager/public/cpp/connector.h"
 #include "ui/base/ime/chromeos/input_method_manager.h"
 #include "ui/base/resource/resource_bundle.h"
@@ -1344,6 +1345,12 @@
     chromevox_panel_widget_observer_.reset(
         new ChromeVoxPanelWidgetObserver(chromevox_panel_->GetWidget(), this));
   }
+
+  if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kEnableDefaultMediaSession)) {
+    base::CommandLine::ForCurrentProcess()->AppendSwitch(
+        switches::kEnableDefaultMediaSession);
+  }
 }
 
 void AccessibilityManager::PostUnloadChromeVox() {
diff --git a/chrome/browser/devtools/BUILD.gn b/chrome/browser/devtools/BUILD.gn
index 6c5c5ac..88bf81a 100644
--- a/chrome/browser/devtools/BUILD.gn
+++ b/chrome/browser/devtools/BUILD.gn
@@ -130,6 +130,8 @@
       "global_confirm_info_bar.h",
       "remote_debugging_server.cc",
       "remote_debugging_server.h",
+      "url_constants.cc",
+      "url_constants.h",
     ]
     if (enable_service_discovery) {
       sources += [
diff --git a/chrome/browser/devtools/devtools_ui_bindings.cc b/chrome/browser/devtools/devtools_ui_bindings.cc
index 4c20eb07..97fd794 100644
--- a/chrome/browser/devtools/devtools_ui_bindings.cc
+++ b/chrome/browser/devtools/devtools_ui_bindings.cc
@@ -17,6 +17,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
@@ -26,6 +27,7 @@
 #include "chrome/browser/devtools/devtools_file_watcher.h"
 #include "chrome/browser/devtools/devtools_protocol.h"
 #include "chrome/browser/devtools/global_confirm_info_bar.h"
+#include "chrome/browser/devtools/url_constants.h"
 #include "chrome/browser/extensions/chrome_extension_web_contents_observer.h"
 #include "chrome/browser/infobars/infobar_service.h"
 #include "chrome/browser/profiles/profile.h"
@@ -62,8 +64,10 @@
 #include "extensions/common/constants.h"
 #include "extensions/common/permissions/permissions_data.h"
 #include "ipc/ipc_channel.h"
+#include "net/base/escape.h"
 #include "net/base/io_buffer.h"
 #include "net/base/net_errors.h"
+#include "net/base/url_util.h"
 #include "net/cert/x509_certificate.h"
 #include "net/http/http_response_headers.h"
 #include "net/url_request/url_fetcher.h"
@@ -299,6 +303,132 @@
   return net::OK;
 }
 
+GURL SanitizeFrontendURL(
+    const GURL& url,
+    const std::string& scheme,
+    const std::string& host,
+    const std::string& path,
+    bool allow_query);
+
+std::string SanitizeRevision(const std::string& revision) {
+  for (size_t i = 0; i < revision.length(); i++) {
+    if (!(revision[i] == '@' && i == 0)
+        && !(revision[i] >= '0' && revision[i] <= '9')
+        && !(revision[i] >= 'a' && revision[i] <= 'z')
+        && !(revision[i] >= 'A' && revision[i] <= 'Z')) {
+      return std::string();
+    }
+  }
+  return revision;
+}
+
+std::string SanitizeFrontendPath(const std::string& path) {
+  for (size_t i = 0; i < path.length(); i++) {
+    if (path[i] != '/' && path[i] != '-' && path[i] != '_'
+        && path[i] != '.' && path[i] != '@'
+        && !(path[i] >= '0' && path[i] <= '9')
+        && !(path[i] >= 'a' && path[i] <= 'z')
+        && !(path[i] >= 'A' && path[i] <= 'Z')) {
+      return std::string();
+    }
+  }
+  return path;
+}
+
+std::string SanitizeEndpoint(const std::string& value) {
+  if (value.find('&') != std::string::npos
+      || value.find('?') != std::string::npos)
+    return std::string();
+  return value;
+}
+
+std::string SanitizeRemoteBase(const std::string& value) {
+  GURL url(value);
+  std::string path = url.path();
+  std::vector<std::string> parts = base::SplitString(
+      path, "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+  std::string revision = parts.size() > 2 ? parts[2] : "";
+  revision = SanitizeRevision(revision);
+  path = base::StringPrintf("/%s/%s/", kRemoteFrontendPath, revision.c_str());
+  return SanitizeFrontendURL(url, url::kHttpsScheme,
+                             kRemoteFrontendDomain, path, false).spec();
+}
+
+std::string SanitizeRemoteFrontendURL(const std::string& value) {
+  GURL url(net::UnescapeURLComponent(value,
+      net::UnescapeRule::SPACES | net::UnescapeRule::PATH_SEPARATORS |
+      net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS |
+      net::UnescapeRule::REPLACE_PLUS_WITH_SPACE));
+  std::string path = url.path();
+  std::vector<std::string> parts = base::SplitString(
+      path, "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+  std::string revision = parts.size() > 2 ? parts[2] : "";
+  revision = SanitizeRevision(revision);
+  std::string filename = parts.size() ? parts[parts.size() - 1] : "";
+  if (filename != "devtools.html")
+    filename = "inspector.html";
+  path = base::StringPrintf("/serve_rev/%s/%s",
+                            revision.c_str(), filename.c_str());
+  std::string sanitized = SanitizeFrontendURL(url, url::kHttpsScheme,
+      kRemoteFrontendDomain, path, true).spec();
+  return net::EscapeQueryParamValue(sanitized, false);
+}
+
+std::string SanitizeFrontendQueryParam(
+    const std::string& key,
+    const std::string& value) {
+  // Convert boolean flags to true.
+  if (key == "can_dock" || key == "debugFrontend" || key == "experiments" ||
+      key == "isSharedWorker" || key == "v8only" || key == "remoteFrontend")
+    return "true";
+
+  // Pass connection endpoints as is.
+  if (key == "ws" || key == "service-backend")
+    return SanitizeEndpoint(value);
+
+  // Only support undocked for old frontends.
+  if (key == "dockSide" && value == "undocked")
+    return value;
+
+  if (key == "panel" && (value == "elements" || value == "console"))
+    return value;
+
+  if (key == "remoteBase")
+    return SanitizeRemoteBase(value);
+
+  if (key == "remoteFrontendUrl")
+    return SanitizeRemoteFrontendURL(value);
+
+  return std::string();
+}
+
+GURL SanitizeFrontendURL(
+    const GURL& url,
+    const std::string& scheme,
+    const std::string& host,
+    const std::string& path,
+    bool allow_query) {
+  std::vector<std::string> query_parts;
+  if (allow_query) {
+    for (net::QueryIterator it(url); !it.IsAtEnd(); it.Advance()) {
+      std::string value = SanitizeFrontendQueryParam(it.GetKey(),
+          it.GetValue());
+      if (!value.empty()) {
+        query_parts.push_back(
+            base::StringPrintf("%s=%s", it.GetKey().c_str(), value.c_str()));
+      }
+    }
+  }
+  std::string query =
+      query_parts.empty() ? "" : "?" + base::JoinString(query_parts, "&");
+  std::string constructed = base::StringPrintf("%s://%s%s%s",
+      scheme.c_str(), host.c_str(), path.c_str(), query.c_str());
+  GURL result = GURL(constructed);
+  if (!result.is_valid())
+    return GURL();
+  return result;
+}
+
 }  // namespace
 
 // DevToolsUIBindings::FrontendWebContentsObserver ----------------------------
@@ -335,6 +465,12 @@
     ~FrontendWebContentsObserver() {
 }
 
+// static
+GURL DevToolsUIBindings::SanitizeFrontendURL(const GURL& url) {
+  return ::SanitizeFrontendURL(url, content::kChromeDevToolsScheme,
+      chrome::kChromeUIDevToolsHost, SanitizeFrontendPath(url.path()), true);
+}
+
 void DevToolsUIBindings::FrontendWebContentsObserver::RenderProcessGone(
     base::TerminationStatus status) {
   bool crashed = true;
@@ -359,11 +495,7 @@
 void DevToolsUIBindings::FrontendWebContentsObserver::
     DidStartNavigationToPendingEntry(const GURL& url,
                                      content::ReloadType reload_type) {
-  devtools_bindings_->frontend_host_.reset(
-      content::DevToolsFrontendHost::Create(
-          web_contents()->GetMainFrame(),
-          base::Bind(&DevToolsUIBindings::HandleMessageFromDevToolsFrontend,
-                     base::Unretained(devtools_bindings_))));
+  devtools_bindings_->UpdateFrontendHost();
 }
 
 void DevToolsUIBindings::FrontendWebContentsObserver::
@@ -418,11 +550,7 @@
   // Register on-load actions.
   embedder_message_dispatcher_.reset(
       DevToolsEmbedderMessageDispatcher::CreateForDevToolsFrontend(this));
-
-  frontend_host_.reset(content::DevToolsFrontendHost::Create(
-      web_contents_->GetMainFrame(),
-      base::Bind(&DevToolsUIBindings::HandleMessageFromDevToolsFrontend,
-                 base::Unretained(this))));
+  UpdateFrontendHost();
 }
 
 DevToolsUIBindings::~DevToolsUIBindings() {
@@ -883,6 +1011,8 @@
 void DevToolsUIBindings::RecordEnumeratedHistogram(const std::string& name,
                                                    int sample,
                                                    int boundary_value) {
+  if (!frontend_host_)
+    return;
   if (!(boundary_value >= 0 && boundary_value <= 100 && sample >= 0 &&
         sample < boundary_value)) {
     // TODO(nick): Replace with chrome::bad_message::ReceivedBadMessage().
@@ -1068,6 +1198,20 @@
   GlobalConfirmInfoBar::Show(std::move(delegate));
 }
 
+void DevToolsUIBindings::UpdateFrontendHost() {
+  GURL url = web_contents_->GetVisibleURL();
+  if (url.spec() != SanitizeFrontendURL(url).spec()) {
+    LOG(ERROR) << "Attempt to navigate to an invalid DevTools front-end URL: "
+        << url.spec();
+    frontend_host_.reset();
+    return;
+  }
+  frontend_host_.reset(content::DevToolsFrontendHost::Create(
+      web_contents_->GetMainFrame(),
+      base::Bind(&DevToolsUIBindings::HandleMessageFromDevToolsFrontend,
+                 base::Unretained(this))));
+}
+
 void DevToolsUIBindings::AddDevToolsExtensionsToClient() {
   const extensions::ExtensionRegistry* registry =
       extensions::ExtensionRegistry::Get(profile_->GetOriginalProfile());
@@ -1141,6 +1285,9 @@
                                             const base::Value* arg3) {
   if (!web_contents_->GetURL().SchemeIs(content::kChromeDevToolsScheme))
     return;
+  // If we're not exposing bindings, we shouldn't call functions either.
+  if (!frontend_host_)
+    return;
   std::string javascript = function_name + "(";
   if (arg1) {
     std::string json;
diff --git a/chrome/browser/devtools/devtools_ui_bindings.h b/chrome/browser/devtools/devtools_ui_bindings.h
index fd302d7..35106293 100644
--- a/chrome/browser/devtools/devtools_ui_bindings.h
+++ b/chrome/browser/devtools/devtools_ui_bindings.h
@@ -42,6 +42,8 @@
   static DevToolsUIBindings* ForWebContents(
       content::WebContents* web_contents);
 
+  static GURL SanitizeFrontendURL(const GURL& url);
+
   class Delegate {
    public:
     virtual ~Delegate() {}
@@ -198,6 +200,7 @@
   typedef base::Callback<void(bool)> InfoBarCallback;
   void ShowDevToolsConfirmInfoBar(const base::string16& message,
                                   const InfoBarCallback& callback);
+  void UpdateFrontendHost();
 
   // Extensions support.
   void AddDevToolsExtensionsToClient();
diff --git a/chrome/browser/ui/webui/devtools_ui_unittest.cc b/chrome/browser/devtools/devtools_ui_bindings_unittest.cc
similarity index 95%
rename from chrome/browser/ui/webui/devtools_ui_unittest.cc
rename to chrome/browser/devtools/devtools_ui_bindings_unittest.cc
index 7cc7585..f1754a3 100644
--- a/chrome/browser/ui/webui/devtools_ui_unittest.cc
+++ b/chrome/browser/devtools/devtools_ui_bindings_unittest.cc
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/webui/devtools_ui.h"
+#include "chrome/browser/devtools/devtools_ui_bindings.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-class DevToolsUITest : public testing::Test {
+class DevToolsUIBindingsTest : public testing::Test {
 };
 
-TEST_F(DevToolsUITest, SanitizeFrontendURL) {
+TEST_F(DevToolsUIBindingsTest, SanitizeFrontendURL) {
   std::vector<std::pair<std::string, std::string>> tests = {
     {"random-string",
      "chrome-devtools://devtools/"},
@@ -96,7 +96,7 @@
 
   for (const auto& pair : tests) {
     GURL url = GURL(pair.first);
-    url = DevToolsUI::SanitizeFrontendURL(url);
+    url = DevToolsUIBindings::SanitizeFrontendURL(url);
     EXPECT_EQ(pair.second, url.spec());
   }
 }
diff --git a/chrome/browser/devtools/devtools_window.cc b/chrome/browser/devtools/devtools_window.cc
index bfbf7347..c63a8e80 100644
--- a/chrome/browser/devtools/devtools_window.cc
+++ b/chrome/browser/devtools/devtools_window.cc
@@ -926,7 +926,7 @@
     url_string += "&can_dock=true";
   if (panel.size())
     url_string += "&panel=" + panel;
-  return DevToolsUI::SanitizeFrontendURL(GURL(url_string));
+  return DevToolsUIBindings::SanitizeFrontendURL(GURL(url_string));
 }
 
 // static
diff --git a/chrome/browser/devtools/url_constants.cc b/chrome/browser/devtools/url_constants.cc
new file mode 100644
index 0000000..bab37d36
--- /dev/null
+++ b/chrome/browser/devtools/url_constants.cc
@@ -0,0 +1,10 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/devtools/url_constants.h"
+
+const char kRemoteFrontendDomain[] = "chrome-devtools-frontend.appspot.com";
+const char kRemoteFrontendBase[] =
+    "https://chrome-devtools-frontend.appspot.com/";
+const char kRemoteFrontendPath[] = "serve_file";
diff --git a/chrome/browser/devtools/url_constants.h b/chrome/browser/devtools/url_constants.h
new file mode 100644
index 0000000..48361e5
--- /dev/null
+++ b/chrome/browser/devtools/url_constants.h
@@ -0,0 +1,12 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_DEVTOOLS_URL_CONSTANTS_H_
+#define CHROME_BROWSER_DEVTOOLS_URL_CONSTANTS_H_
+
+extern const char kRemoteFrontendDomain[];
+extern const char kRemoteFrontendBase[];
+extern const char kRemoteFrontendPath[];
+
+#endif  // CHROME_BROWSER_DEVTOOLS_URL_CONSTANTS_H_
diff --git a/chrome/browser/extensions/api/automation_internal/automation_internal_api.cc b/chrome/browser/extensions/api/automation_internal/automation_internal_api.cc
index f2238ac..ccaf89d 100644
--- a/chrome/browser/extensions/api/automation_internal/automation_internal_api.cc
+++ b/chrome/browser/extensions/api/automation_internal/automation_internal_api.cc
@@ -248,7 +248,20 @@
 
   explicit AutomationWebContentsObserver(content::WebContents* web_contents)
       : content::WebContentsObserver(web_contents),
-        browser_context_(web_contents->GetBrowserContext()) {}
+        browser_context_(web_contents->GetBrowserContext()) {
+    if (web_contents->WasRecentlyAudible()) {
+      std::vector<content::AXEventNotificationDetails> details;
+      content::RenderFrameHost* rfh = web_contents->GetMainFrame();
+      if (!rfh)
+        return;
+
+      content::AXEventNotificationDetails detail;
+      detail.ax_tree_id = rfh->GetAXTreeID();
+      detail.event_type = ui::AX_EVENT_MEDIA_STARTED_PLAYING;
+      details.push_back(detail);
+      AccessibilityEventReceived(details);
+    }
+  }
 
   content::BrowserContext* browser_context_;
 
diff --git a/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.cc
new file mode 100644
index 0000000..e9a6384
--- /dev/null
+++ b/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.cc
@@ -0,0 +1,113 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.h"
+
+#include <string>
+
+#include "base/optional.h"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"
+#include "chrome/browser/page_load_metrics/page_load_metrics_util.h"
+#include "chrome/common/page_load_metrics/page_load_timing.h"
+#include "components/google/core/browser/google_util.h"
+#include "content/public/browser/navigation_handle.h"
+#include "url/gurl.h"
+
+namespace {
+
+const char kHistogramAMPDOMContentLoadedEventFired[] =
+    "PageLoad.Clients.AMPCache.DocumentTiming."
+    "NavigationToDOMContentLoadedEventFired";
+const char kHistogramAMPFirstLayout[] =
+    "PageLoad.Clients.AMPCache.DocumentTiming.NavigationToFirstLayout";
+const char kHistogramAMPLoadEventFired[] =
+    "PageLoad.Clients.AMPCache.DocumentTiming.NavigationToLoadEventFired";
+const char kHistogramAMPFirstContentfulPaint[] =
+    "PageLoad.Clients.AMPCache.PaintTiming.NavigationToFirstContentfulPaint";
+const char kHistogramAMPParseStart[] =
+    "PageLoad.Clients.AMPCache.ParseTiming.NavigationToParseStart";
+
+// Host pattern for AMP Cache URLs.
+// See https://developers.google.com/amp/cache/overview#amp-cache-url-format
+// for a definition of the format of AMP Cache URLs.
+const char kAmpCacheHost[] = "cdn.ampproject.org";
+
+// Pattern for the path of Google AMP Viewer URLs.
+const char kGoogleAmpViewerPathPattern[] = "/amp/";
+
+bool IsAMPCacheURL(const GURL& url) {
+  return url.host() == kAmpCacheHost ||
+         (google_util::IsGoogleDomainUrl(
+              url, google_util::DISALLOW_SUBDOMAIN,
+              google_util::DISALLOW_NON_STANDARD_PORTS) &&
+          base::StartsWith(url.path(), kGoogleAmpViewerPathPattern,
+                           base::CompareCase::SENSITIVE));
+}
+
+}  // namespace
+
+AMPPageLoadMetricsObserver::AMPPageLoadMetricsObserver() {}
+
+AMPPageLoadMetricsObserver::~AMPPageLoadMetricsObserver() {}
+
+page_load_metrics::PageLoadMetricsObserver::ObservePolicy
+AMPPageLoadMetricsObserver::OnCommit(
+    content::NavigationHandle* navigation_handle) {
+  return IsAMPCacheURL(navigation_handle->GetURL()) ? CONTINUE_OBSERVING
+                                                    : STOP_OBSERVING;
+}
+
+void AMPPageLoadMetricsObserver::OnDomContentLoadedEventStart(
+    const page_load_metrics::PageLoadTiming& timing,
+    const page_load_metrics::PageLoadExtraInfo& info) {
+  if (!WasStartedInForegroundOptionalEventInForeground(
+          timing.dom_content_loaded_event_start, info)) {
+    return;
+  }
+  PAGE_LOAD_HISTOGRAM(kHistogramAMPDOMContentLoadedEventFired,
+                      timing.dom_content_loaded_event_start.value());
+}
+
+void AMPPageLoadMetricsObserver::OnLoadEventStart(
+    const page_load_metrics::PageLoadTiming& timing,
+    const page_load_metrics::PageLoadExtraInfo& info) {
+  if (!WasStartedInForegroundOptionalEventInForeground(
+          timing.dom_content_loaded_event_start, info)) {
+    return;
+  }
+  PAGE_LOAD_HISTOGRAM(kHistogramAMPLoadEventFired,
+                      timing.load_event_start.value());
+}
+
+void AMPPageLoadMetricsObserver::OnFirstLayout(
+    const page_load_metrics::PageLoadTiming& timing,
+    const page_load_metrics::PageLoadExtraInfo& info) {
+  if (!WasStartedInForegroundOptionalEventInForeground(
+          timing.dom_content_loaded_event_start, info)) {
+    return;
+  }
+  PAGE_LOAD_HISTOGRAM(kHistogramAMPFirstLayout, timing.first_layout.value());
+}
+
+void AMPPageLoadMetricsObserver::OnFirstContentfulPaint(
+    const page_load_metrics::PageLoadTiming& timing,
+    const page_load_metrics::PageLoadExtraInfo& info) {
+  if (!WasStartedInForegroundOptionalEventInForeground(
+          timing.dom_content_loaded_event_start, info)) {
+    return;
+  }
+  PAGE_LOAD_HISTOGRAM(kHistogramAMPFirstContentfulPaint,
+                      timing.first_contentful_paint.value());
+}
+
+void AMPPageLoadMetricsObserver::OnParseStart(
+    const page_load_metrics::PageLoadTiming& timing,
+    const page_load_metrics::PageLoadExtraInfo& info) {
+  if (!WasStartedInForegroundOptionalEventInForeground(
+          timing.dom_content_loaded_event_start, info)) {
+    return;
+  }
+  PAGE_LOAD_HISTOGRAM(kHistogramAMPParseStart, timing.parse_start.value());
+}
diff --git a/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.h b/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.h
new file mode 100644
index 0000000..0f0a2d1c
--- /dev/null
+++ b/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.h
@@ -0,0 +1,52 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_AMP_PAGE_LOAD_METRICS_OBSERVER_H_
+#define CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_AMP_PAGE_LOAD_METRICS_OBSERVER_H_
+
+#include "base/macros.h"
+#include "chrome/browser/page_load_metrics/page_load_metrics_observer.h"
+
+namespace content {
+class NavigationHandle;
+}
+
+namespace page_load_metrics {
+struct PageLoadExtraInfo;
+struct PageLoadTiming;
+}
+
+// Observer responsible for recording page load metrics relevant to page served
+// from the AMP cache. When AMP pages are served in a same page navigation, UMA
+// is not recorded; this is typical for AMP pages navigated to from google.com.
+// Navigations to AMP pages from the google search app or directly to the amp
+// cache page will be tracked. Refreshing an AMP page served from google.com
+// will be tracked.
+class AMPPageLoadMetricsObserver
+    : public page_load_metrics::PageLoadMetricsObserver {
+ public:
+  AMPPageLoadMetricsObserver();
+  ~AMPPageLoadMetricsObserver() override;
+
+  // page_load_metrics::PageLoadMetricsObserver:
+  ObservePolicy OnCommit(content::NavigationHandle* navigation_handle) override;
+  void OnDomContentLoadedEventStart(
+      const page_load_metrics::PageLoadTiming& timing,
+      const page_load_metrics::PageLoadExtraInfo& info) override;
+  void OnLoadEventStart(
+      const page_load_metrics::PageLoadTiming& timing,
+      const page_load_metrics::PageLoadExtraInfo& info) override;
+  void OnFirstLayout(const page_load_metrics::PageLoadTiming& timing,
+                     const page_load_metrics::PageLoadExtraInfo& info) override;
+  void OnFirstContentfulPaint(
+      const page_load_metrics::PageLoadTiming& timing,
+      const page_load_metrics::PageLoadExtraInfo& info) override;
+  void OnParseStart(const page_load_metrics::PageLoadTiming& timing,
+                    const page_load_metrics::PageLoadExtraInfo& info) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(AMPPageLoadMetricsObserver);
+};
+
+#endif  // CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_AMP_PAGE_LOAD_METRICS_OBSERVER_H_
diff --git a/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer_unittest.cc
new file mode 100644
index 0000000..a4748f8f
--- /dev/null
+++ b/chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer_unittest.cc
@@ -0,0 +1,101 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.h"
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/time/time.h"
+#include "chrome/browser/page_load_metrics/observers/page_load_metrics_observer_test_harness.h"
+#include "chrome/common/page_load_metrics/page_load_timing.h"
+#include "url/gurl.h"
+
+class AMPPageLoadMetricsObserverTest
+    : public page_load_metrics::PageLoadMetricsObserverTestHarness {
+ public:
+  AMPPageLoadMetricsObserverTest() {}
+
+  void ResetTest() {
+    // Reset to the default testing state. Does not reset histogram state.
+    timing_.navigation_start = base::Time::FromDoubleT(1);
+    timing_.response_start = base::TimeDelta::FromSeconds(2);
+    timing_.parse_start = base::TimeDelta::FromSeconds(3);
+    timing_.first_contentful_paint = base::TimeDelta::FromSeconds(4);
+    timing_.first_image_paint = base::TimeDelta::FromSeconds(5);
+    timing_.first_text_paint = base::TimeDelta::FromSeconds(6);
+    timing_.load_event_start = base::TimeDelta::FromSeconds(7);
+    PopulateRequiredTimingFields(&timing_);
+  }
+
+  void RunTest(const GURL& url) {
+    NavigateAndCommit(url);
+    SimulateTimingUpdate(timing_);
+
+    // Navigate again to force OnComplete, which happens when a new navigation
+    // occurs.
+    NavigateAndCommit(GURL("http://otherurl.com"));
+  }
+
+  void ValidateHistograms(bool expect_histograms) {
+    ValidateHistogramsFor(
+        "PageLoad.Clients.AMPCache.DocumentTiming."
+        "NavigationToDOMContentLoadedEventFired",
+        timing_.dom_content_loaded_event_start, expect_histograms);
+    ValidateHistogramsFor(
+        "PageLoad.Clients.AMPCache.DocumentTiming.NavigationToFirstLayout",
+        timing_.first_layout, expect_histograms);
+    ValidateHistogramsFor(
+        "PageLoad.Clients.AMPCache.DocumentTiming.NavigationToLoadEventFired",
+        timing_.load_event_start, expect_histograms);
+    ValidateHistogramsFor(
+        "PageLoad.Clients.AMPCache.PaintTiming."
+        "NavigationToFirstContentfulPaint",
+        timing_.first_contentful_paint, expect_histograms);
+    ValidateHistogramsFor(
+        "PageLoad.Clients.AMPCache.ParseTiming.NavigationToParseStart",
+        timing_.parse_start, expect_histograms);
+  }
+
+  void ValidateHistogramsFor(const std::string& histogram_,
+                             const base::Optional<base::TimeDelta>& event,
+                             bool expect_histograms) {
+    histogram_tester().ExpectTotalCount(histogram_, expect_histograms ? 1 : 0);
+    if (!expect_histograms)
+      return;
+    histogram_tester().ExpectUniqueSample(
+        histogram_, static_cast<base::HistogramBase::Sample>(
+                        event.value().InMilliseconds()),
+        1);
+  }
+
+ protected:
+  void RegisterObservers(page_load_metrics::PageLoadTracker* tracker) override {
+    tracker->AddObserver(base::WrapUnique(new AMPPageLoadMetricsObserver()));
+  }
+
+ private:
+  page_load_metrics::PageLoadTiming timing_;
+
+  DISALLOW_COPY_AND_ASSIGN(AMPPageLoadMetricsObserverTest);
+};
+
+TEST_F(AMPPageLoadMetricsObserverTest, AMPCachePage) {
+  ResetTest();
+  RunTest(GURL("https://cdn.ampproject.org/page"));
+  ValidateHistograms(true);
+}
+
+TEST_F(AMPPageLoadMetricsObserverTest, GoogleAMPCachePage) {
+  ResetTest();
+  RunTest(GURL("https://www.google.com/amp/page"));
+  ValidateHistograms(true);
+}
+
+TEST_F(AMPPageLoadMetricsObserverTest, NonAMPPage) {
+  ResetTest();
+  RunTest(GURL("https://www.google.com/not-amp/page"));
+  ValidateHistograms(false);
+}
diff --git a/chrome/browser/page_load_metrics/page_load_metrics_initialize.cc b/chrome/browser/page_load_metrics/page_load_metrics_initialize.cc
index c6d1f3b..a88d9a5 100644
--- a/chrome/browser/page_load_metrics/page_load_metrics_initialize.cc
+++ b/chrome/browser/page_load_metrics/page_load_metrics_initialize.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/page_load_metrics/observers/android_page_load_metrics_observer.h"
 #endif  // OS_ANDROID
 #include "chrome/browser/page_load_metrics/observers/aborts_page_load_metrics_observer.h"
+#include "chrome/browser/page_load_metrics/observers/amp_page_load_metrics_observer.h"
 #include "chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer.h"
 #include "chrome/browser/page_load_metrics/observers/css_scanning_page_load_metrics_observer.h"
 #include "chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer.h"
@@ -62,6 +63,7 @@
     page_load_metrics::PageLoadTracker* tracker) {
   // These classes are owned by the metrics.
   tracker->AddObserver(base::MakeUnique<AbortsPageLoadMetricsObserver>());
+  tracker->AddObserver(base::MakeUnique<AMPPageLoadMetricsObserver>());
   tracker->AddObserver(base::MakeUnique<CorePageLoadMetricsObserver>());
   tracker->AddObserver(
       base::MakeUnique<
diff --git a/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.html b/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.html
index df74060..108a3c9 100644
--- a/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.html
+++ b/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.html
@@ -41,13 +41,31 @@
     </label>
   </div>
 
+  <p class="i18n description" msgid="options_audio_description" id="audioDescription">
+    When playing audio
+  </p>
+
+  <div>
+    <select id="audioStrategy" class="pref" aria-labelledby="audioDescription">
+      <option id="audioNormal" class="i18n" msgid="options_audio_normal">
+        play at normal volume even if ChromeVox is speaking
+      </option>
+      <option id="audioDuck" class="i18n" msgid="options_audio_duck">
+        play at lower volume when ChromeVox is speaking
+      </option>
+      <option id="audioSuspend" class="i18n" msgid="options_audio_suspend">
+        pause playback when ChromeVox is speaking
+      </option>
+    </select>
+  </div>
+
   <h2 class="i18n" msgid="options_voices">Voices</h2>
   <p class="i18n description" msgid="options_voices_description" id="voices_description">
     Change the current voice by selecting an option from the list below.
   </p>
 
   <div>
-    <select class="pref" id="voices" aria-labelledby="voices_description"></select>
+    <select id="voices" aria-labelledby="voices_description"></select>
     <br><br>
   </div>
 
@@ -57,11 +75,11 @@
     <p class="i18n description" msgid="options_braille_description_6" id="braille_description_6">
       Change the current 6 dot braille table by selecting an option from the list below.
     </p>
-    <select class="pref" id="brailleTable6" aria-labelledby="braille_description_6"></select>
+    <select id="brailleTable6" aria-labelledby="braille_description_6"></select>
     <p class="i18n description" msgid="options_braille_description_8" id="braille_description_8">
       Change the current 8 dot braille table by selecting an option from the list below.
     </p>
-    <select class="pref" id="brailleTable8" aria-labelledby="braille_description_8"></select>
+    <select id="brailleTable8" aria-labelledby="braille_description_8"></select>
     <button id="brailleTableType"></button>
     <div class="option">
       <label>
diff --git a/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.js b/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.js
index 4ae0143..53fc20a 100644
--- a/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.js
+++ b/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.js
@@ -74,6 +74,20 @@
       localStorage['brailleSideBySide'] === 'true' ?
       currentlyDisplayingSideBySide : currentlyDisplayingInterleave;
 
+  chrome.commandLinePrivate.hasSwitch('enable-default-media-session',
+      function(result) {
+        if (!result) {
+          $('audioStrategy').hidden = true;
+          $('audioDescription').hidden = true;
+        }
+        if (localStorage['audioStrategy']) {
+          for (var i = 0, opt; opt = $('audioStrategy').options[i]; i++) {
+            if (opt.id == localStorage['audioStrategy']) {
+              opt.setAttribute('selected', '');
+            }
+          }
+        }
+      });
 
   Msgs.addTranslatedMessagesToDom(document);
   cvox.OptionsPage.hidePlatformSpecifics();
@@ -340,6 +354,11 @@
             cvox.OptionsPage.prefs.setPref(target.name, elements[i].value);
           }
         }
+      } else if (target.tagName == 'SELECT') {
+        var selIndex = target.selectedIndex;
+        var sel = target.options[selIndex];
+        var value = sel ? sel.id : 'audioNormal';
+        cvox.OptionsPage.prefs.setPref(target.id, value);
       }
     }
   }, 0);
diff --git a/chrome/browser/resources/chromeos/chromevox/chromevox/background/prefs.js b/chrome/browser/resources/chromeos/chromevox/chromevox/background/prefs.js
index eb21a385..ab1f3da 100644
--- a/chrome/browser/resources/chromeos/chromevox/chromevox/background/prefs.js
+++ b/chrome/browser/resources/chromeos/chromevox/chromevox/background/prefs.js
@@ -60,6 +60,7 @@
  */
 cvox.ChromeVoxPrefs.DEFAULT_PREFS = {
   'active': true,
+  'audioStrategy': 'audioNormal',
   'autoRead': false,
   'brailleCaptions': false,
   'brailleSideBySide': true,
diff --git a/chrome/browser/resources/chromeos/chromevox/common/chrome_extension_externs.js b/chrome/browser/resources/chromeos/chromevox/common/chrome_extension_externs.js
index 799ca2e..cd4352e 100644
--- a/chrome/browser/resources/chromeos/chromevox/common/chrome_extension_externs.js
+++ b/chrome/browser/resources/chromeos/chromevox/common/chrome_extension_externs.js
@@ -11,3 +11,11 @@
 
 /** @type {function() : !Object} */
 chrome.app.getDetails;
+
+// Media related automation actions and events.
+chrome.automation.AutomationNode.prototype.resumeMedia = function() {};
+chrome.automation.AutomationNode.prototype.startDuckingMedia = function() {};
+chrome.automation.AutomationNode.prototype.stopDuckingMedia = function() {};
+chrome.automation.AutomationNode.prototype.suspendMedia = function() {};
+chrome.automation.EventType.mediaStartedPlaying;
+chrome.automation.EventType.mediaStoppedPlaying;
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js
index 28aade9..b1f96568 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js
@@ -17,6 +17,7 @@
 goog.require('CommandHandler');
 goog.require('FindHandler');
 goog.require('LiveRegions');
+goog.require('MediaAutomationHandler');
 goog.require('NextEarcons');
 goog.require('Notifications');
 goog.require('Output');
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/media_automation_handler.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/media_automation_handler.js
index 8a57c06..f969fe7 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/media_automation_handler.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/media_automation_handler.js
@@ -3,12 +3,15 @@
 // found in the LICENSE file.
 
 /**
- * @fileoverview Handles media events automation.
+ * @fileoverview Handles media automation events.  Note that to perform any of
+ * the actions below such as ducking, and suspension of media sessions, the
+ * --enable-default-media-session flag must be passed at the command line.
  */
 
 goog.provide('MediaAutomationHandler');
 
 goog.require('BaseAutomationHandler');
+goog.require('cvox.TtsCapturingEventListener');
 
 goog.scope(function() {
 var AutomationEvent = chrome.automation.AutomationEvent;
@@ -17,32 +20,101 @@
 var RoleType = chrome.automation.RoleType;
 
 /**
- * @param {!AutomationNode} node The root to observe media changes.
  * @constructor
  * @extends {BaseAutomationHandler}
+ * @implements {cvox.TtsCapturingEventListener}
  */
-MediaAutomationHandler = function(node) {
-  BaseAutomationHandler.call(this, node);
+MediaAutomationHandler = function() {
+  /** @type {!Set<AutomationNode>} @private */
+  this.mediaRoots_ = new Set();
 
-  var e = EventType;
-  this.addListener_(e.mediaStartedPlaying, this.onMediaStartedPlaying);
-  this.addListener_(e.mediaStoppedPlaying, this.onMediaStoppedPlaying);
+  /** @type {Date} @private */
+  this.lastTtsEvent_ = new Date();
+
+  cvox.ChromeVox.tts.addCapturingEventListener(this);
+
+  chrome.automation.getDesktop(function(node) {
+    BaseAutomationHandler.call(this, node);
+
+    var e = EventType;
+    this.addListener_(e.mediaStartedPlaying, this.onMediaStartedPlaying);
+    this.addListener_(e.mediaStoppedPlaying, this.onMediaStoppedPlaying);
+  }.bind(this));
 };
 
+/** @type {number} */
+MediaAutomationHandler.MIN_WAITTIME_MS = 1000;
+
 MediaAutomationHandler.prototype = {
   __proto__: BaseAutomationHandler.prototype,
 
+  /** @override */
+  onTtsStart: function() {
+    this.lastTtsEvent_ = new Date();
+    this.update_({start: true});
+  },
+
+  /** @override */
+  onTtsEnd: function() {
+    var now = new Date();
+    setTimeout(function() {
+      var then = this.lastTtsEvent_;
+      if (now < then)
+        return;
+      this.lastTtsEvent_ = now;
+      this.update_({end: true});
+    }.bind(this), MediaAutomationHandler.MIN_WAITTIME_MS);
+  },
+
+  /** @override */
+  onTtsInterrupted: function() {
+    this.onTtsEnd();
+  },
+
   /**
    * @param {!AutomationEvent} evt
    */
   onMediaStartedPlaying: function(evt) {
+    this.mediaRoots_.add(evt.target);
+    if (cvox.ChromeVox.tts.isSpeaking())
+      this.update_({start: true});
   },
 
   /**
    * @param {!AutomationEvent} evt
    */
   onMediaStoppedPlaying: function(evt) {
+    // Intentionally does nothing (to cover resume).
+  },
+
+  /**
+   * Updates the media state for all observed automation roots.
+   * @param {{start: (boolean|undefined),
+   *          end: (boolean|undefined)}} options
+   * @private
+   */
+  update_: function(options) {
+    var it = this.mediaRoots_.values();
+    var item = it.next();
+    var audioStrategy = localStorage['audioStrategy'];
+    while (!item.done) {
+      var root = item.value;
+      if (options.start) {
+        if (audioStrategy == 'audioDuck')
+          root.startDuckingMedia();
+        else if (audioStrategy == 'audioSuspend')
+          root.suspendMedia();
+      } else if (options.end) {
+        if (audioStrategy == 'audioDuck')
+          root.stopDuckingMedia();
+        else if (audioStrategy == 'audioSuspend')
+          root.resumeMedia();
+      }
+      item = it.next();
+    }
   }
 };
 
 });  // goog.scope
+
+new MediaAutomationHandler();
diff --git a/chrome/browser/resources/chromeos/chromevox/host/chrome/tts_background.js b/chrome/browser/resources/chromeos/chromevox/host/chrome/tts_background.js
index d4d3704..d07d2ff6 100644
--- a/chrome/browser/resources/chromeos/chromevox/host/chrome/tts_background.js
+++ b/chrome/browser/resources/chromeos/chromevox/host/chrome/tts_background.js
@@ -414,6 +414,9 @@
         this.cancelUtterance_(this.utteranceQueue_[i]);
       }
       this.utteranceQueue_.length = 0;
+      this.capturingTtsEventListeners_.forEach(function(listener) {
+        listener.onTtsInterrupted();
+      });
       break;
     case 'error':
       this.onError_(event['errorMessage']);
@@ -498,6 +501,10 @@
 
   (new PanelCommand(PanelCommandType.CLEAR_SPEECH)).send();
   chrome.tts.stop();
+
+  this.capturingTtsEventListeners_.forEach(function(listener) {
+    listener.onTtsInterrupted();
+  });
 };
 
 /** @override */
diff --git a/chrome/browser/resources/chromeos/chromevox/host/interface/tts_interface.js b/chrome/browser/resources/chromeos/chromevox/host/interface/tts_interface.js
index b00e07d..4ef6d48 100644
--- a/chrome/browser/resources/chromeos/chromevox/host/interface/tts_interface.js
+++ b/chrome/browser/resources/chromeos/chromevox/host/interface/tts_interface.js
@@ -64,6 +64,10 @@
  */
 cvox.TtsCapturingEventListener.prototype.onTtsEnd = function() { };
 
+/**
+ * Called when any utterance gets interrupted.
+ */
+cvox.TtsCapturingEventListener.prototype.onTtsInterrupted = function() { };
 
 /**
  * @interface
diff --git a/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd b/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd
index 64da50f7..8fde944 100644
--- a/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd
+++ b/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd
@@ -477,6 +477,18 @@
       <message desc="An option to automatically read the page after it loads." name="IDS_CHROMEVOX_OPTIONS_AUTO_READ">
         Automatically read page after it finishes loading.
       </message>
+      <message desc="Describes the multi select option for how to play audio when ChromeVox speaks using text to speech." name="IDS_CHROMEVOX_OPTIONS_AUDIO_DESCRIPTION">
+        When playing audio...
+      </message>
+      <message desc="Sets audio playback to be at normal volume." name="IDS_CHROMEVOX_OPTIONS_AUDIO_NORMAL">
+        Play at normal volume even if ChromeVox is speaking
+      </message>
+      <message desc="An option to use audio ducking." name="IDS_CHROMEVOX_OPTIONS_AUDIO_duck">
+        Play at lower volume when ChromeVox is speaking
+      </message>
+      <message desc="An option to use audio suspension while text to speech is speaking." name="IDS_CHROMEVOX_OPTIONS_AUDIO_suspend">
+        Pause playback when ChromeVox is speaking
+      </message>
       <message desc="An option to show the cursor between characters." name="IDS_CHROMEVOX_OPTIONS_CURSOR_BETWEEN_CHARACTERS">
         Place cursor between characters when editing text (like Mac OS X).
       </message>
diff --git a/chrome/browser/resources/md_history/PRESUBMIT.py b/chrome/browser/resources/md_history/PRESUBMIT.py
index 2c041a4..8f2ca60 100644
--- a/chrome/browser/resources/md_history/PRESUBMIT.py
+++ b/chrome/browser/resources/md_history/PRESUBMIT.py
@@ -2,30 +2,29 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import os.path
-import time
 
 def CheckChangeOnUpload(input_api, output_api):
   """Warn when changing md_history without vulcanizing."""
 
-  def _is_md_history_file(path):
-    return (path.startswith('chrome/browser/resources/md_history') and
-            (not path.endswith('externs.js')) and
-            (not path.endswith('crisper.js')) and
-            (not path.endswith('vulcanized.html')) and
-            (path.endswith('js') or path.endswith('html')))
+  def _is_history_source_file(file):
+    path = file.LocalPath()
+    return (not path.endswith('externs.js') and
+            not path.endswith('crisper.js') and
+            not path.endswith('vulcanized.html') and
+            (path.endswith('.js') or path.endswith('.html')))
 
-  paths = [x.LocalPath() for x in input_api.change.AffectedFiles()]
-  earliest_vulcanize_change = min(os.path.getmtime(x) for x in
+  os_path = input_api.os_path
+  earliest_vulcanize_change = min(os_path.getmtime(x) for x in
                                   ['app.vulcanized.html',
                                    'app.crisper.js',
                                    'lazy_load.vulcanized.html',
                                    'lazy_load.crisper.js'])
-  history_changes = filter(_is_md_history_file, paths)
+
+  source_files = input_api.AffectedFiles(file_filter=_is_history_source_file)
   latest_history_change = 0
-  if history_changes:
-    latest_history_change = max(os.path.getmtime(os.path.split(x)[1]) for x in
-                                history_changes)
+  if source_files:
+    latest_history_change = max(
+        os_path.getmtime(os_path.basename(f.LocalPath())) for f in source_files)
 
   if latest_history_change > earliest_vulcanize_change:
     return [output_api.PresubmitPromptWarning(
diff --git a/chrome/browser/safe_browsing/certificate_reporting_service.cc b/chrome/browser/safe_browsing/certificate_reporting_service.cc
index f03baa6a..9bcecb8 100644
--- a/chrome/browser/safe_browsing/certificate_reporting_service.cc
+++ b/chrome/browser/safe_browsing/certificate_reporting_service.cc
@@ -35,6 +35,11 @@
   UMA_HISTOGRAM_SPARSE_SLOWLY("SSL.CertificateErrorReportFailure", -net_error);
 }
 
+void CleanupOnIOThread(
+    std::unique_ptr<CertificateReportingService::Reporter> reporter) {
+  reporter.reset();
+}
+
 }  // namespace
 
 CertificateReportingService::BoundedReportList::BoundedReportList(
@@ -164,7 +169,6 @@
     base::TimeDelta max_report_age,
     base::Clock* clock)
     : pref_service_(*profile->GetPrefs()),
-      enabled_(true),
       url_request_context_(nullptr),
       max_queued_report_count_(max_queued_report_count),
       max_report_age_(max_report_age),
@@ -187,7 +191,7 @@
   content::BrowserThread::PostTask(
       content::BrowserThread::IO, FROM_HERE,
       base::Bind(&CertificateReportingService::InitializeOnIOThread,
-                 base::Unretained(this), enabled_, url_request_context_getter,
+                 base::Unretained(this), true, url_request_context_getter,
                  max_queued_report_count_, max_report_age_, clock_,
                  server_public_key_, server_public_key_version_));
 }
@@ -199,8 +203,10 @@
 void CertificateReportingService::Shutdown() {
   // Shutdown will be called twice: Once after SafeBrowsing shuts down, and once
   // when all KeyedServices shut down. All calls after the first one are no-op.
-  enabled_ = false;
-  Reset();
+  url_request_context_ = nullptr;
+  content::BrowserThread::PostTask(
+      content::BrowserThread::IO, FROM_HERE,
+      base::Bind(&CleanupOnIOThread, base::Passed(std::move(reporter_))));
 }
 
 void CertificateReportingService::Send(const std::string& serialized_report) {
@@ -243,8 +249,16 @@
 
 void CertificateReportingService::SetEnabled(bool enabled) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  enabled_ = enabled;
-  Reset();
+  // Don't reset if the service is already shut down.
+  if (!url_request_context_)
+    return;
+
+  content::BrowserThread::PostTask(
+      content::BrowserThread::IO, FROM_HERE,
+      base::Bind(&CertificateReportingService::ResetOnIOThread,
+                 base::Unretained(this), enabled, url_request_context_,
+                 max_queued_report_count_, max_report_age_, clock_,
+                 server_public_key_, server_public_key_version_));
 }
 
 CertificateReportingService::Reporter*
@@ -257,15 +271,6 @@
   return GURL(kExtendedReportingUploadUrl);
 }
 
-void CertificateReportingService::Reset() {
-  content::BrowserThread::PostTask(
-      content::BrowserThread::IO, FROM_HERE,
-      base::Bind(&CertificateReportingService::ResetOnIOThread,
-                 base::Unretained(this), enabled_, url_request_context_,
-                 max_queued_report_count_, max_report_age_, clock_,
-                 server_public_key_, server_public_key_version_));
-}
-
 void CertificateReportingService::ResetOnIOThread(
     bool enabled,
     net::URLRequestContext* url_request_context,
@@ -277,7 +282,7 @@
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   // url_request_context_ is null during shutdown.
   if (!enabled || !url_request_context) {
-    reporter_.reset(nullptr);
+    reporter_.reset();
     return;
   }
   std::unique_ptr<certificate_reporting::ErrorReporter> error_reporter;
@@ -293,7 +298,6 @@
         url_request_context, GURL(kExtendedReportingUploadUrl),
         net::ReportSender::DO_NOT_SEND_COOKIES));
   }
-
   reporter_.reset(
       new Reporter(std::move(error_reporter),
                    std::unique_ptr<BoundedReportList>(
diff --git a/chrome/browser/safe_browsing/certificate_reporting_service.h b/chrome/browser/safe_browsing/certificate_reporting_service.h
index 330e0ab..7137655 100644
--- a/chrome/browser/safe_browsing/certificate_reporting_service.h
+++ b/chrome/browser/safe_browsing/certificate_reporting_service.h
@@ -104,7 +104,7 @@
     Reporter(
         std::unique_ptr<certificate_reporting::ErrorReporter> error_reporter_,
         std::unique_ptr<BoundedReportList> retry_list,
-        base::Clock* clock,
+        base::Clock* const clock,
         base::TimeDelta report_ttl,
         bool retries_enabled);
     ~Reporter();
@@ -129,7 +129,7 @@
 
     std::unique_ptr<certificate_reporting::ErrorReporter> error_reporter_;
     std::unique_ptr<BoundedReportList> retry_list_;
-    base::Clock* clock_;
+    base::Clock* const clock_;
     // Maximum age of a queued report. Reports older than this are discarded in
     // the next SendPending() call.
     const base::TimeDelta report_ttl_;
@@ -176,14 +176,12 @@
   static GURL GetReportingURLForTesting();
 
  private:
-  void Reset();
-
   void InitializeOnIOThread(
       bool enabled,
       scoped_refptr<net::URLRequestContextGetter> url_request_context_getter,
       size_t max_queued_report_count,
       base::TimeDelta max_report_age,
-      base::Clock* clock,
+      base::Clock* const clock,
       uint8_t* server_public_key,
       uint32_t server_public_key_version);
 
@@ -196,18 +194,13 @@
                        net::URLRequestContext* url_request_context,
                        size_t max_queued_report_count,
                        base::TimeDelta max_report_age,
-                       base::Clock* clock,
+                       base::Clock* const clock,
                        uint8_t* server_public_key,
                        uint32_t server_public_key_version);
 
   void OnPreferenceChanged();
 
   const PrefService& pref_service_;
-
-  // If true, reporting is enabled. When SafeBrowsing preferences change, this
-  // might be set to false.
-  bool enabled_;
-
   net::URLRequestContext* url_request_context_;
   std::unique_ptr<Reporter> reporter_;
 
@@ -224,14 +217,14 @@
       safe_browsing_state_subscription_;
 
   // Maximum number of reports to be queued for retry.
-  size_t max_queued_report_count_;
+  const size_t max_queued_report_count_;
 
   // Maximum age of the reports to be queued for retry, from the time the
   // certificate error was first encountered by the user. Any report older than
   // this age is ignored and is not re-uploaded.
-  base::TimeDelta max_report_age_;
+  const base::TimeDelta max_report_age_;
 
-  base::Clock* clock_;
+  base::Clock* const clock_;
 
   // Encryption parameters.
   uint8_t* server_public_key_;
diff --git a/chrome/browser/sync_file_system/drive_backend/metadata_database_index.cc b/chrome/browser/sync_file_system/drive_backend/metadata_database_index.cc
index 31c7641a..e2a092ee 100644
--- a/chrome/browser/sync_file_system/drive_backend/metadata_database_index.cc
+++ b/chrome/browser/sync_file_system/drive_backend/metadata_database_index.cc
@@ -262,9 +262,10 @@
 
 bool MetadataDatabaseIndex::GetFileMetadata(
     const std::string& file_id, FileMetadata* metadata) const {
-  FileMetadata* identified = metadata_by_id_.get(file_id);
-  if (!identified)
+  auto it = metadata_by_id_.find(file_id);
+  if (it == metadata_by_id_.end())
     return false;
+  FileMetadata* identified = it->second.get();
   if (metadata)
     metadata->CopyFrom(*identified);
   return true;
@@ -272,9 +273,10 @@
 
 bool MetadataDatabaseIndex::GetFileTracker(int64_t tracker_id,
                                            FileTracker* tracker) const {
-  FileTracker* identified = tracker_by_id_.get(tracker_id);
-  if (!identified)
+  auto it = tracker_by_id_.find(tracker_id);
+  if (it == tracker_by_id_.end())
     return false;
+  FileTracker* identified = it->second.get();
   if (tracker)
     tracker->CopyFrom(*identified);
   return true;
@@ -289,7 +291,7 @@
   }
 
   std::string file_id = metadata->file_id();
-  metadata_by_id_.set(file_id, std::move(metadata));
+  metadata_by_id_[file_id] = std::move(metadata);
 }
 
 void MetadataDatabaseIndex::StoreFileTracker(
@@ -301,9 +303,9 @@
   }
 
   int64_t tracker_id = tracker->tracker_id();
-  FileTracker* old_tracker = tracker_by_id_.get(tracker_id);
+  auto old_tracker_it = tracker_by_id_.find(tracker_id);
 
-  if (!old_tracker) {
+  if (old_tracker_it == tracker_by_id_.end()) {
     DVLOG(3) << "Adding new tracker: " << tracker->tracker_id()
              << " " << GetTrackerTitle(*tracker);
 
@@ -315,13 +317,14 @@
     DVLOG(3) << "Updating tracker: " << tracker->tracker_id()
              << " " << GetTrackerTitle(*tracker);
 
+    FileTracker* old_tracker = old_tracker_it->second.get();
     UpdateInAppIDIndex(*old_tracker, *tracker);
     UpdateInPathIndexes(*old_tracker, *tracker);
     UpdateInFileIDIndexes(*old_tracker, *tracker);
     UpdateInDirtyTrackerIndexes(*old_tracker, *tracker);
   }
 
-  tracker_by_id_.set(tracker_id, std::move(tracker));
+  tracker_by_id_[tracker_id] = std::move(tracker);
 }
 
 void MetadataDatabaseIndex::RemoveFileMetadata(const std::string& file_id) {
@@ -332,11 +335,12 @@
 void MetadataDatabaseIndex::RemoveFileTracker(int64_t tracker_id) {
   PutFileTrackerDeletionToDB(tracker_id, db_);
 
-  FileTracker* tracker = tracker_by_id_.get(tracker_id);
-  if (!tracker) {
+  auto tracker_it = tracker_by_id_.find(tracker_id);
+  if (tracker_it == tracker_by_id_.end()) {
     NOTREACHED();
     return;
   }
+  FileTracker* tracker = tracker_it->second.get();
 
   DVLOG(3) << "Removing tracker: "
            << tracker->tracker_id() << " " << GetTrackerTitle(*tracker);
@@ -491,27 +495,22 @@
 std::vector<std::string> MetadataDatabaseIndex::GetRegisteredAppIDs() const {
   std::vector<std::string> result;
   result.reserve(app_root_by_app_id_.size());
-  for (TrackerIDByAppID::const_iterator itr = app_root_by_app_id_.begin();
-       itr != app_root_by_app_id_.end(); ++itr)
-    result.push_back(itr->first);
+  for (const auto& pair : app_root_by_app_id_)
+    result.push_back(pair.first);
   return result;
 }
 
 std::vector<int64_t> MetadataDatabaseIndex::GetAllTrackerIDs() const {
   std::vector<int64_t> result;
-  for (TrackerByID::const_iterator itr = tracker_by_id_.begin();
-       itr != tracker_by_id_.end(); ++itr) {
-    result.push_back(itr->first);
-  }
+  for (const auto& pair : tracker_by_id_)
+    result.push_back(pair.first);
   return result;
 }
 
 std::vector<std::string> MetadataDatabaseIndex::GetAllMetadataIDs() const {
   std::vector<std::string> result;
-  for (MetadataByID::const_iterator itr = metadata_by_id_.begin();
-       itr != metadata_by_id_.end(); ++itr) {
-    result.push_back(itr->first);
-  }
+  for (const auto& pair : metadata_by_id_)
+    result.push_back(pair.first);
   return result;
 }
 
diff --git a/chrome/browser/sync_file_system/drive_backend/metadata_database_index.h b/chrome/browser/sync_file_system/drive_backend/metadata_database_index.h
index 688ae85..16eab073 100644
--- a/chrome/browser/sync_file_system/drive_backend/metadata_database_index.h
+++ b/chrome/browser/sync_file_system/drive_backend/metadata_database_index.h
@@ -11,6 +11,7 @@
 #include <map>
 #include <set>
 #include <string>
+#include <unordered_map>
 #include <vector>
 
 #include "base/containers/hash_tables.h"
@@ -104,10 +105,9 @@
   std::vector<std::string> GetAllMetadataIDs() const override;
 
  private:
-  typedef base::ScopedPtrHashMap<std::string, std::unique_ptr<FileMetadata>>
+  typedef std::unordered_map<std::string, std::unique_ptr<FileMetadata>>
       MetadataByID;
-  typedef base::ScopedPtrHashMap<int64_t, std::unique_ptr<FileTracker>>
-      TrackerByID;
+  typedef std::unordered_map<int64_t, std::unique_ptr<FileTracker>> TrackerByID;
   typedef base::hash_map<std::string, TrackerIDSet> TrackerIDsByFileID;
   typedef base::hash_map<std::string, TrackerIDSet> TrackerIDsByTitle;
   typedef std::map<int64_t, TrackerIDsByTitle> TrackerIDsByParentAndTitle;
diff --git a/chrome/browser/sync_file_system/drive_backend/metadata_database_unittest.cc b/chrome/browser/sync_file_system/drive_backend/metadata_database_unittest.cc
index 747d18d..e456d4ce7 100644
--- a/chrome/browser/sync_file_system/drive_backend/metadata_database_unittest.cc
+++ b/chrome/browser/sync_file_system/drive_backend/metadata_database_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <stdint.h>
 
+#include <unordered_map>
 #include <utility>
 
 #include "base/bind.h"
@@ -105,16 +106,24 @@
 template <typename Key, typename Value>
 void ExpectEquivalent(const base::hash_map<Key, Value>& left,
                       const base::hash_map<Key, Value>& right) {
+  // Convert from a hash container to an ordered container for comparison.
   ExpectEquivalentMaps(std::map<Key, Value>(left.begin(), left.end()),
                        std::map<Key, Value>(right.begin(), right.end()));
 }
 
 template <typename Key, typename Value>
 void ExpectEquivalent(
-    const base::ScopedPtrHashMap<Key, std::unique_ptr<Value>>& left,
-    const base::ScopedPtrHashMap<Key, std::unique_ptr<Value>>& right) {
-  ExpectEquivalentMaps(std::map<Key, Value*>(left.begin(), left.end()),
-                       std::map<Key, Value*>(right.begin(), right.end()));
+    const std::unordered_map<Key, std::unique_ptr<Value>>& left,
+    const std::unordered_map<Key, std::unique_ptr<Value>>& right) {
+  // Convert from a hash container to an ordered container for comparison.
+  std::map<Key, Value*> left_ordered;
+  std::map<Key, Value*> right_ordered;
+  for (const auto& item : left)
+    left_ordered[item.first] = item.second.get();
+  for (const auto& item : right)
+    right_ordered[item.first] = item.second.get();
+
+  ExpectEquivalentMaps(left_ordered, right_ordered);
 }
 
 template <typename Container>
@@ -129,6 +138,7 @@
 template <typename Value>
 void ExpectEquivalent(const base::hash_set<Value>& left,
                       const base::hash_set<Value>& right) {
+  // Convert from a hash container to an ordered container for comparison.
   return ExpectEquivalentSets(std::set<Value>(left.begin(), left.end()),
                               std::set<Value>(right.begin(), right.end()));
 }
diff --git a/chrome/browser/sync_file_system/drive_backend/sync_task_manager.cc b/chrome/browser/sync_file_system/drive_backend/sync_task_manager.cc
index acdb242..b971db1 100644
--- a/chrome/browser/sync_file_system/drive_backend/sync_task_manager.cc
+++ b/chrome/browser/sync_file_system/drive_backend/sync_task_manager.cc
@@ -201,7 +201,8 @@
   if (token_id == SyncTaskToken::kForegroundTaskTokenID)
     return true;
 
-  return ContainsKey(running_background_tasks_, token_id);
+  return running_background_tasks_.find(token_id) !=
+         running_background_tasks_.end();
 }
 
 void SyncTaskManager::DetachFromSequence() {
@@ -240,7 +241,8 @@
     token_ = std::move(token);
     task = std::move(running_foreground_task_);
   } else {
-    task = running_background_tasks_.take_and_erase(token->token_id());
+    task = std::move(running_background_tasks_[token->token_id()]);
+    running_background_tasks_.erase(token->token_id());
   }
 
   // Acquire the token to prevent a new task to jump into the queue.
@@ -343,8 +345,8 @@
         weak_ptr_factory_.GetWeakPtr(), task_runner_.get(), task_token_seq_++,
         std::move(task_blocker));
     background_task_token->UpdateTask(from_here, callback);
-    running_background_tasks_.set(background_task_token->token_id(),
-                                  std::move(running_foreground_task_));
+    running_background_tasks_[background_task_token->token_id()] =
+        std::move(running_foreground_task_);
   }
 
   token_ = std::move(foreground_task_token);
diff --git a/chrome/browser/sync_file_system/drive_backend/sync_task_manager.h b/chrome/browser/sync_file_system/drive_backend/sync_task_manager.h
index 364383d..7531682 100644
--- a/chrome/browser/sync_file_system/drive_backend/sync_task_manager.h
+++ b/chrome/browser/sync_file_system/drive_backend/sync_task_manager.h
@@ -10,10 +10,10 @@
 
 #include <memory>
 #include <queue>
+#include <unordered_map>
 #include <vector>
 
 #include "base/callback.h"
-#include "base/containers/scoped_ptr_hash_map.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
@@ -186,7 +186,7 @@
 
   // Owns running backgrounded SyncTask to cancel the task on SyncTaskManager
   // deletion.
-  base::ScopedPtrHashMap<int64_t, std::unique_ptr<SyncTask>>
+  std::unordered_map<int64_t, std::unique_ptr<SyncTask>>
       running_background_tasks_;
 
   size_t maximum_background_task_;
diff --git a/chrome/browser/ui/webui/devtools_ui.cc b/chrome/browser/ui/webui/devtools_ui.cc
index 587c74f..9cacacc 100644
--- a/chrome/browser/ui/webui/devtools_ui.cc
+++ b/chrome/browser/ui/webui/devtools_ui.cc
@@ -7,9 +7,9 @@
 #include "base/command_line.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted_memory.h"
-#include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
+#include "chrome/browser/devtools/url_constants.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/url_constants.h"
@@ -21,10 +21,8 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_ui.h"
 #include "content/public/common/user_agent.h"
-#include "net/base/escape.h"
 #include "net/base/filename_util.h"
 #include "net/base/load_flags.h"
-#include "net/base/url_util.h"
 #include "net/url_request/url_fetcher.h"
 #include "net/url_request/url_fetcher_delegate.h"
 #include "net/url_request/url_request_context_getter.h"
@@ -41,10 +39,6 @@
       .path().substr(1);
 }
 
-const char kRemoteFrontendDomain[] = "chrome-devtools-frontend.appspot.com";
-const char kRemoteFrontendBase[] =
-    "https://chrome-devtools-frontend.appspot.com/";
-const char kRemoteFrontendPath[] = "serve_file";
 const char kHttpNotFound[] = "HTTP/1.1 404 Not Found\n\n";
 
 #if BUILDFLAG(DEBUG_DEVTOOLS)
@@ -57,131 +51,6 @@
     "data:text/plain,Cannot load DevTools frontend from an untrusted origin";
 #endif  // BUILDFLAG(DEBUG_DEVTOOLS)
 
-GURL SanitizeFrontendURL(
-    const GURL& url,
-    const std::string& scheme,
-    const std::string& host,
-    const std::string& path,
-    bool allow_query);
-
-std::string SanitizeRevision(const std::string& revision) {
-  for (size_t i = 0; i < revision.length(); i++) {
-    if (!(revision[i] == '@' && i == 0)
-        && !(revision[i] >= '0' && revision[i] <= '9')
-        && !(revision[i] >= 'a' && revision[i] <= 'z')
-        && !(revision[i] >= 'A' && revision[i] <= 'Z')) {
-      return std::string();
-    }
-  }
-  return revision;
-}
-
-std::string SanitizeFrontendPath(const std::string& path) {
-  for (size_t i = 0; i < path.length(); i++) {
-    if (path[i] != '/' && path[i] != '-' && path[i] != '_'
-        && path[i] != '.' && path[i] != '@'
-        && !(path[i] >= '0' && path[i] <= '9')
-        && !(path[i] >= 'a' && path[i] <= 'z')
-        && !(path[i] >= 'A' && path[i] <= 'Z')) {
-      return std::string();
-    }
-  }
-  return path;
-}
-
-std::string SanitizeEndpoint(const std::string& value) {
-  if (value.find('&') != std::string::npos
-      || value.find('?') != std::string::npos)
-    return std::string();
-  return value;
-}
-
-std::string SanitizeRemoteBase(const std::string& value) {
-  GURL url(value);
-  std::string path = url.path();
-  std::vector<std::string> parts = base::SplitString(
-      path, "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
-  std::string revision = parts.size() > 2 ? parts[2] : "";
-  revision = SanitizeRevision(revision);
-  path = base::StringPrintf("/%s/%s/", kRemoteFrontendPath, revision.c_str());
-  return SanitizeFrontendURL(url, url::kHttpsScheme,
-                             kRemoteFrontendDomain, path, false).spec();
-}
-
-std::string SanitizeRemoteFrontendURL(const std::string& value) {
-  GURL url(net::UnescapeURLComponent(value,
-      net::UnescapeRule::SPACES | net::UnescapeRule::PATH_SEPARATORS |
-      net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS |
-      net::UnescapeRule::REPLACE_PLUS_WITH_SPACE));
-  std::string path = url.path();
-  std::vector<std::string> parts = base::SplitString(
-      path, "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
-  std::string revision = parts.size() > 2 ? parts[2] : "";
-  revision = SanitizeRevision(revision);
-  std::string filename = parts.size() ? parts[parts.size() - 1] : "";
-  if (filename != "devtools.html")
-    filename = "inspector.html";
-  path = base::StringPrintf("/serve_rev/%s/%s",
-                            revision.c_str(), filename.c_str());
-  std::string sanitized = SanitizeFrontendURL(url, url::kHttpsScheme,
-      kRemoteFrontendDomain, path, true).spec();
-  return net::EscapeQueryParamValue(sanitized, false);
-}
-
-std::string SanitizeFrontendQueryParam(
-    const std::string& key,
-    const std::string& value) {
-  // Convert boolean flags to true.
-  if (key == "can_dock" || key == "debugFrontend" || key == "experiments" ||
-      key == "isSharedWorker" || key == "v8only" || key == "remoteFrontend")
-    return "true";
-
-  // Pass connection endpoints as is.
-  if (key == "ws" || key == "service-backend")
-    return SanitizeEndpoint(value);
-
-  // Only support undocked for old frontends.
-  if (key == "dockSide" && value == "undocked")
-    return value;
-
-  if (key == "panel" && (value == "elements" || value == "console"))
-    return value;
-
-  if (key == "remoteBase")
-    return SanitizeRemoteBase(value);
-
-  if (key == "remoteFrontendUrl")
-    return SanitizeRemoteFrontendURL(value);
-
-  return std::string();
-}
-
-GURL SanitizeFrontendURL(
-    const GURL& url,
-    const std::string& scheme,
-    const std::string& host,
-    const std::string& path,
-    bool allow_query) {
-  std::vector<std::string> query_parts;
-  if (allow_query) {
-    for (net::QueryIterator it(url); !it.IsAtEnd(); it.Advance()) {
-      std::string value = SanitizeFrontendQueryParam(it.GetKey(),
-          it.GetValue());
-      if (!value.empty()) {
-        query_parts.push_back(
-            base::StringPrintf("%s=%s", it.GetKey().c_str(), value.c_str()));
-      }
-    }
-  }
-  std::string query =
-      query_parts.empty() ? "" : "?" + base::JoinString(query_parts, "&");
-  std::string constructed = base::StringPrintf("%s://%s%s%s",
-      scheme.c_str(), host.c_str(), path.c_str(), query.c_str());
-  GURL result = GURL(constructed);
-  if (!result.is_valid())
-    return GURL();
-  return result;
-}
 
 // DevToolsDataSource ---------------------------------------------------------
 
@@ -429,30 +298,20 @@
       content::GetWebKitRevision().c_str()));
 }
 
-// static
-GURL DevToolsUI::SanitizeFrontendURL(const GURL& url) {
-  return ::SanitizeFrontendURL(url, content::kChromeDevToolsScheme,
-      chrome::kChromeUIDevToolsHost, SanitizeFrontendPath(url.path()), true);
-}
-
 DevToolsUI::DevToolsUI(content::WebUI* web_ui)
-    : WebUIController(web_ui) {
+    : WebUIController(web_ui), bindings_(web_ui->GetWebContents()) {
   web_ui->SetBindings(0);
   Profile* profile = Profile::FromWebUI(web_ui);
   content::URLDataSource::Add(
       profile,
       new DevToolsDataSource(profile->GetRequestContext()));
 
-  GURL url = web_ui->GetWebContents()->GetVisibleURL();
-  if (url.spec() != SanitizeFrontendURL(url).spec())
+  if (!profile->IsOffTheRecord())
     return;
-
-  if (profile->IsOffTheRecord()) {
-    GURL site = content::SiteInstance::GetSiteForURL(profile, url);
-    content::BrowserContext::GetStoragePartitionForSite(profile, site)->
-        GetFileSystemContext()->EnableTemporaryFileSystemInIncognito();
-  }
-  bindings_.reset(new DevToolsUIBindings(web_ui->GetWebContents()));
+  GURL url = web_ui->GetWebContents()->GetVisibleURL();
+  GURL site = content::SiteInstance::GetSiteForURL(profile, url);
+  content::BrowserContext::GetStoragePartitionForSite(profile, site)->
+      GetFileSystemContext()->EnableTemporaryFileSystemInIncognito();
 }
 
 DevToolsUI::~DevToolsUI() {
diff --git a/chrome/browser/ui/webui/devtools_ui.h b/chrome/browser/ui/webui/devtools_ui.h
index 8e6328d..809e4e4 100644
--- a/chrome/browser/ui/webui/devtools_ui.h
+++ b/chrome/browser/ui/webui/devtools_ui.h
@@ -5,9 +5,6 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_DEVTOOLS_UI_H_
 #define CHROME_BROWSER_UI_WEBUI_DEVTOOLS_UI_H_
 
-#include <memory>
-
-#include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "chrome/browser/devtools/devtools_ui_bindings.h"
 #include "content/public/browser/web_ui_controller.h"
@@ -16,13 +13,12 @@
  public:
   static GURL GetProxyURL(const std::string& frontend_url);
   static GURL GetRemoteBaseURL();
-  static GURL SanitizeFrontendURL(const GURL& url);
 
   explicit DevToolsUI(content::WebUI* web_ui);
   ~DevToolsUI() override;
 
  private:
-  std::unique_ptr<DevToolsUIBindings> bindings_;
+  DevToolsUIBindings bindings_;
   DISALLOW_COPY_AND_ASSIGN(DevToolsUI);
 };
 
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_handler.cc b/chrome/browser/ui/webui/print_preview/print_preview_handler.cc
index 2561020d..52ba8300 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_handler.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_handler.cc
@@ -107,6 +107,8 @@
 
 namespace {
 
+// This enum is used to back an UMA histogram, and should therefore be treated
+// as append only.
 enum UserActionBuckets {
   PRINT_TO_PRINTER,
   PRINT_TO_PDF,
@@ -122,6 +124,8 @@
   USERACTION_BUCKET_BOUNDARY
 };
 
+// This enum is used to back an UMA histogram, and should therefore be treated
+// as append only.
 enum PrintSettingsBuckets {
   LANDSCAPE = 0,
   PORTRAIT,
@@ -146,6 +150,14 @@
   PRINT_SETTINGS_BUCKET_BOUNDARY
 };
 
+// This enum is used to back an UMA histogram, and should therefore be treated
+// as append only.
+enum PrintDocumentTypeBuckets {
+  HTML_DOCUMENT = 0,
+  PDF_DOCUMENT,
+  PRINT_DOCUMENT_TYPE_BUCKET_BOUNDARY
+};
+
 void ReportUserActionHistogram(enum UserActionBuckets event) {
   UMA_HISTOGRAM_ENUMERATION("PrintPreview.UserAction", event,
                             USERACTION_BUCKET_BOUNDARY);
@@ -156,6 +168,11 @@
                             PRINT_SETTINGS_BUCKET_BOUNDARY);
 }
 
+void ReportPrintDocumentTypeHistogram(enum PrintDocumentTypeBuckets doctype) {
+  UMA_HISTOGRAM_ENUMERATION("PrintPreview.PrintDocumentType", doctype,
+                            PRINT_DOCUMENT_TYPE_BUCKET_BOUNDARY);
+}
+
 // Name of a dictionary field holding cloud print related data;
 const char kAppState[] = "appState";
 // Name of a dictionary field holding the initiator title.
@@ -811,6 +828,10 @@
 
   ReportPrintSettingsStats(*settings);
 
+  // Report whether the user printed a PDF or an HTML document.
+  ReportPrintDocumentTypeHistogram(print_preview_ui()->source_is_modifiable() ?
+                                   HTML_DOCUMENT : PDF_DOCUMENT);
+
   // Never try to add headers/footers here. It's already in the generated PDF.
   settings->SetBoolean(printing::kSettingHeaderFooterEnabled, false);
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 382ed7f..f0680de0 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3192,6 +3192,7 @@
     "../browser/ntp_snippets/download_suggestions_provider_unittest.cc",
     "../browser/page_load_metrics/metrics_web_contents_observer_unittest.cc",
     "../browser/page_load_metrics/observers/aborts_page_load_metrics_observer_unittest.cc",
+    "../browser/page_load_metrics/observers/amp_page_load_metrics_observer_unittest.cc",
     "../browser/page_load_metrics/observers/core_page_load_metrics_observer_unittest.cc",
     "../browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer_unittest.cc",
     "../browser/page_load_metrics/observers/document_write_page_load_metrics_observer_unittest.cc",
@@ -3719,6 +3720,7 @@
 
   if (!is_ios && !is_android) {
     sources += [
+      "../browser/devtools/devtools_ui_bindings_unittest.cc",
       "../browser/download/download_dir_policy_handler_unittest.cc",
       "../browser/lifetime/keep_alive_registry_unittest.cc",
       "../browser/renderer_context_menu/render_view_context_menu_test_util.cc",
@@ -3727,7 +3729,6 @@
       "../browser/ui/autofill/save_card_bubble_controller_impl_unittest.cc",
       "../browser/ui/bluetooth/bluetooth_chooser_controller_unittest.cc",
       "../browser/ui/passwords/manage_passwords_ui_controller_unittest.cc",
-      "../browser/ui/webui/devtools_ui_unittest.cc",
     ]
     deps += [
       ":unit_tests_js",
diff --git a/content/browser/service_worker/embedded_worker_test_helper.cc b/content/browser/service_worker/embedded_worker_test_helper.cc
index 3b14438..0ef4127 100644
--- a/content/browser/service_worker/embedded_worker_test_helper.cc
+++ b/content/browser/service_worker/embedded_worker_test_helper.cc
@@ -22,7 +22,6 @@
 #include "content/common/service_worker/embedded_worker_messages.h"
 #include "content/common/service_worker/embedded_worker_setup.mojom.h"
 #include "content/common/service_worker/embedded_worker_start_params.h"
-#include "content/common/service_worker/service_worker_event_dispatcher.mojom.h"
 #include "content/common/service_worker/service_worker_messages.h"
 #include "content/common/service_worker/service_worker_utils.h"
 #include "content/public/common/push_event_payload.h"
@@ -32,6 +31,7 @@
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
 #include "services/service_manager/public/cpp/interface_registry.h"
+#include "services/service_manager/public/interfaces/interface_provider.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace content {
diff --git a/content/browser/service_worker/embedded_worker_test_helper.h b/content/browser/service_worker/embedded_worker_test_helper.h
index 6a1216d..108327d1 100644
--- a/content/browser/service_worker/embedded_worker_test_helper.h
+++ b/content/browser/service_worker/embedded_worker_test_helper.h
@@ -25,16 +25,11 @@
 #include "ipc/ipc_test_sink.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "net/http/http_response_info.h"
-#include "services/service_manager/public/cpp/interface_provider.h"
-#include "services/service_manager/public/cpp/interface_registry.h"
-#include "services/service_manager/public/interfaces/interface_provider.mojom.h"
-#include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
 class GURL;
 
 namespace service_manager {
-class InterfaceProvider;
 class InterfaceRegistry;
 }
 
@@ -215,10 +210,6 @@
   EmbeddedWorkerRegistry* registry();
 
  private:
-  using InterfaceRegistryAndProvider =
-      std::pair<std::unique_ptr<service_manager::InterfaceRegistry>,
-                std::unique_ptr<service_manager::InterfaceProvider>>;
-
   class MockEmbeddedWorkerSetup;
   class MockServiceWorkerEventDispatcher;
 
@@ -272,11 +263,6 @@
   std::map<int /* thread_id */, int /* embedded_worker_id */>
       thread_id_embedded_worker_id_map_;
 
-  // Stores the InterfaceRegistry/InterfaceProviders that are associated with
-  // each individual service worker.
-  base::hash_map<int, InterfaceRegistryAndProvider>
-      thread_id_service_registry_map_;
-
   // Updated each time MessageToWorker message is received.
   int current_embedded_worker_id_;
 
diff --git a/device/bluetooth/bluetooth_device_android.cc b/device/bluetooth/bluetooth_device_android.cc
index c9cde3fa..04a0129 100644
--- a/device/bluetooth/bluetooth_device_android.cc
+++ b/device/bluetooth/bluetooth_device_android.cc
@@ -8,6 +8,7 @@
 #include "base/android/jni_android.h"
 #include "base/android/jni_string.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "device/bluetooth/bluetooth_adapter_android.h"
 #include "device/bluetooth/bluetooth_remote_gatt_service_android.h"
@@ -246,7 +247,7 @@
   std::string instance_id_string =
       base::android::ConvertJavaStringToUTF8(env, instance_id);
 
-  if (gatt_services_.find(instance_id_string) != gatt_services_.end())
+  if (base::ContainsKey(gatt_services_, instance_id_string))
     return;
 
   std::unique_ptr<BluetoothRemoteGattServiceAndroid> service =
diff --git a/device/bluetooth/bluetooth_device_win.cc b/device/bluetooth/bluetooth_device_win.cc
index a82eebd..7b0ac1f 100644
--- a/device/bluetooth/bluetooth_device_win.cc
+++ b/device/bluetooth/bluetooth_device_win.cc
@@ -239,9 +239,8 @@
       new_service_records;
   for (auto iter = device_state.service_record_states.begin();
        iter != device_state.service_record_states.end(); ++iter) {
-    std::unique_ptr<BluetoothServiceRecordWin> service_record =
-        base::MakeUnique<BluetoothServiceRecordWin>(
-            address_, (*iter)->name, (*iter)->sdp_bytes, (*iter)->gatt_uuid);
+    auto service_record = base::MakeUnique<BluetoothServiceRecordWin>(
+        address_, (*iter)->name, (*iter)->sdp_bytes, (*iter)->gatt_uuid);
     new_services.insert(service_record->uuid());
     new_service_records[service_record->uuid().canonical_value()] =
         std::move(service_record);
diff --git a/device/bluetooth/bluetooth_remote_gatt_characteristic_android.cc b/device/bluetooth/bluetooth_remote_gatt_characteristic_android.cc
index 5bcd437d..f03e811 100644
--- a/device/bluetooth/bluetooth_remote_gatt_characteristic_android.cc
+++ b/device/bluetooth/bluetooth_remote_gatt_characteristic_android.cc
@@ -13,6 +13,7 @@
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "device/bluetooth/bluetooth_adapter_android.h"
 #include "device/bluetooth/bluetooth_remote_gatt_descriptor_android.h"
@@ -241,7 +242,7 @@
   std::string instanceIdString =
       base::android::ConvertJavaStringToUTF8(env, instanceId);
 
-  DCHECK(descriptors_.find(instanceIdString) == descriptors_.end());
+  DCHECK(!base::ContainsKey(descriptors_, instanceIdString));
 
   descriptors_[instanceIdString] = BluetoothRemoteGattDescriptorAndroid::Create(
       instanceIdString, bluetooth_gatt_descriptor_wrapper,
diff --git a/device/bluetooth/bluetooth_remote_gatt_service_android.cc b/device/bluetooth/bluetooth_remote_gatt_service_android.cc
index c7b20ad..3864070 100644
--- a/device/bluetooth/bluetooth_remote_gatt_service_android.cc
+++ b/device/bluetooth/bluetooth_remote_gatt_service_android.cc
@@ -8,6 +8,7 @@
 
 #include "base/android/jni_android.h"
 #include "base/android/jni_string.h"
+#include "base/stl_util.h"
 #include "device/bluetooth/bluetooth_adapter_android.h"
 #include "device/bluetooth/bluetooth_device_android.h"
 #include "device/bluetooth/bluetooth_remote_gatt_characteristic_android.h"
@@ -166,7 +167,7 @@
   std::string instance_id_string =
       base::android::ConvertJavaStringToUTF8(env, instance_id);
 
-  DCHECK(characteristics_.find(instance_id_string) == characteristics_.end());
+  DCHECK(!base::ContainsKey(characteristics_, instance_id_string));
 
   characteristics_[instance_id_string] =
       BluetoothRemoteGattCharacteristicAndroid::Create(
diff --git a/extensions/renderer/wake_event_page.cc b/extensions/renderer/wake_event_page.cc
index fcf15f6..f17a4e87 100644
--- a/extensions/renderer/wake_event_page.cc
+++ b/extensions/renderer/wake_event_page.cc
@@ -13,6 +13,7 @@
 #include "base/lazy_instance.h"
 #include "base/logging.h"
 #include "base/macros.h"
+#include "base/memory/ptr_util.h"
 #include "content/public/child/worker_thread.h"
 #include "content/public/renderer/render_thread.h"
 #include "extensions/common/extension_messages.h"
@@ -160,10 +161,9 @@
   static base::AtomicSequenceNumber sequence_number;
   int request_id = sequence_number.GetNext();
   {
-    std::unique_ptr<RequestData> request_data(
-        new RequestData(content::WorkerThread::GetCurrentId(), on_response));
     base::AutoLock lock(requests_lock_);
-    requests_.set(request_id, std::move(request_data));
+    requests_[request_id] = base::MakeUnique<RequestData>(
+        content::WorkerThread::GetCurrentId(), on_response);
   }
   message_filter_->Send(
       new ExtensionHostMsg_WakeEventPage(request_id, extension_id));
@@ -183,9 +183,11 @@
   std::unique_ptr<RequestData> request_data;
   {
     base::AutoLock lock(requests_lock_);
-    request_data = requests_.take(request_id);
+    auto it = requests_.find(request_id);
+    CHECK(it != requests_.end()) << "No request with ID " << request_id;
+    request_data = std::move(it->second);
+    requests_.erase(it);
   }
-  CHECK(request_data) << "No request with ID " << request_id;
   if (request_data->thread_id == 0) {
     // Thread ID of 0 means it wasn't called on a worker thread, so safe to
     // call immediately.
diff --git a/extensions/renderer/wake_event_page.h b/extensions/renderer/wake_event_page.h
index 0eec285..3542b42 100644
--- a/extensions/renderer/wake_event_page.h
+++ b/extensions/renderer/wake_event_page.h
@@ -7,9 +7,9 @@
 
 #include <memory>
 #include <string>
+#include <unordered_map>
 
 #include "base/callback.h"
-#include "base/containers/scoped_ptr_hash_map.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
@@ -103,7 +103,7 @@
 
   // All in-flight requests, keyed by request ID. Used on multiple threads, so
   // must be guarded by |requests_lock_|.
-  base::ScopedPtrHashMap<int, std::unique_ptr<RequestData>> requests_;
+  std::unordered_map<int, std::unique_ptr<RequestData>> requests_;
 
   // Lock for |requests_|.
   base::Lock requests_lock_;
diff --git a/gpu/ipc/service/gpu_channel.cc b/gpu/ipc/service/gpu_channel.cc
index 8ed788a3..4a99a6db 100644
--- a/gpu/ipc/service/gpu_channel.cc
+++ b/gpu/ipc/service/gpu_channel.cc
@@ -701,7 +701,11 @@
 }
 
 GpuCommandBufferStub* GpuChannel::LookupCommandBuffer(int32_t route_id) {
-  return stubs_.get(route_id);
+  auto it = stubs_.find(route_id);
+  if (it == stubs_.end())
+    return nullptr;
+
+  return it->second.get();
 }
 
 void GpuChannel::LoseAllContexts() {
@@ -772,7 +776,7 @@
 
   const IPC::Message& msg = channel_msg->message;
   int32_t routing_id = msg.routing_id();
-  GpuCommandBufferStub* stub = stubs_.get(routing_id);
+  GpuCommandBufferStub* stub = LookupCommandBuffer(routing_id);
 
   DCHECK(!stub || stub->IsScheduled());
 
@@ -873,7 +877,7 @@
 #if defined(OS_ANDROID)
 const GpuCommandBufferStub* GpuChannel::GetOneStub() const {
   for (const auto& kv : stubs_) {
-    const GpuCommandBufferStub* stub = kv.second;
+    const GpuCommandBufferStub* stub = kv.second.get();
     if (stub->decoder() && !stub->decoder()->WasContextLost())
       return stub;
   }
@@ -896,7 +900,7 @@
   if (stub) {
     *result = true;
     *capabilities = stub->decoder()->GetCapabilities();
-    stubs_.set(route_id, std::move(stub));
+    stubs_[route_id] = std::move(stub);
   } else {
     *result = false;
     *capabilities = gpu::Capabilities();
@@ -915,7 +919,7 @@
   }
 
   int32_t share_group_id = init_params.share_group_id;
-  GpuCommandBufferStub* share_group = stubs_.get(share_group_id);
+  GpuCommandBufferStub* share_group = LookupCommandBuffer(share_group_id);
 
   if (!share_group && share_group_id != MSG_ROUTING_NONE) {
     DLOG(ERROR)
@@ -977,7 +981,12 @@
   TRACE_EVENT1("gpu", "GpuChannel::OnDestroyCommandBuffer",
                "route_id", route_id);
 
-  std::unique_ptr<GpuCommandBufferStub> stub = stubs_.take_and_erase(route_id);
+  std::unique_ptr<GpuCommandBufferStub> stub;
+  auto it = stubs_.find(route_id);
+  if (it != stubs_.end()) {
+    stub = std::move(it->second);
+    stubs_.erase(it);
+  }
   // In case the renderer is currently blocked waiting for a sync reply from the
   // stub, we need to make sure to reschedule the correct stream here.
   if (stub && !stub->IsScheduled()) {
diff --git a/gpu/ipc/service/gpu_channel.h b/gpu/ipc/service/gpu_channel.h
index 47c1ba6..cbe0869 100644
--- a/gpu/ipc/service/gpu_channel.h
+++ b/gpu/ipc/service/gpu_channel.h
@@ -10,9 +10,9 @@
 
 #include <memory>
 #include <string>
+#include <unordered_map>
 
 #include "base/containers/hash_tables.h"
-#include "base/containers/scoped_ptr_hash_map.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
@@ -177,7 +177,7 @@
   scoped_refptr<GpuChannelMessageFilter> filter_;
 
   // Map of routing id to command buffer stub.
-  base::ScopedPtrHashMap<int32_t, std::unique_ptr<GpuCommandBufferStub>> stubs_;
+  std::unordered_map<int32_t, std::unique_ptr<GpuCommandBufferStub>> stubs_;
 
  private:
   friend class TestGpuChannel;
diff --git a/gpu/ipc/service/gpu_channel_manager.cc b/gpu/ipc/service/gpu_channel_manager.cc
index 34f2be4..8758a5a 100644
--- a/gpu/ipc/service/gpu_channel_manager.cc
+++ b/gpu/ipc/service/gpu_channel_manager.cc
@@ -120,7 +120,7 @@
 
 GpuChannel* GpuChannelManager::LookupChannel(int32_t client_id) const {
   const auto& it = gpu_channels_.find(client_id);
-  return it != gpu_channels_.end() ? it->second : nullptr;
+  return it != gpu_channels_.end() ? it->second.get() : nullptr;
 }
 
 std::unique_ptr<GpuChannel> GpuChannelManager::CreateGpuChannel(
@@ -147,7 +147,7 @@
       CreateGpuChannel(client_id, client_tracing_id, preempts,
                        allow_view_command_buffers, allow_real_time_streams));
   IPC::ChannelHandle channel_handle = channel->Init(shutdown_event_);
-  gpu_channels_.set(client_id, std::move(channel));
+  gpu_channels_[client_id] = std::move(channel);
   return channel_handle;
 }
 
@@ -275,7 +275,7 @@
 void GpuChannelManager::DoWakeUpGpu() {
   const GpuCommandBufferStub* stub = nullptr;
   for (const auto& kv : gpu_channels_) {
-    const GpuChannel* channel = kv.second;
+    const GpuChannel* channel = kv.second.get();
     stub = channel->GetOneStub();
     if (stub) {
       DCHECK(stub->decoder());
diff --git a/gpu/ipc/service/gpu_channel_manager.h b/gpu/ipc/service/gpu_channel_manager.h
index 797dad1..70a10e4 100644
--- a/gpu/ipc/service/gpu_channel_manager.h
+++ b/gpu/ipc/service/gpu_channel_manager.h
@@ -10,9 +10,9 @@
 #include <deque>
 #include <memory>
 #include <string>
+#include <unordered_map>
 #include <vector>
 
-#include "base/containers/scoped_ptr_hash_map.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
@@ -156,10 +156,10 @@
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
   scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
 
-  // These objects manage channels to individual renderer processes there is
+  // These objects manage channels to individual renderer processes. There is
   // one channel for each renderer process that has connected to this GPU
   // process.
-  base::ScopedPtrHashMap<int32_t, std::unique_ptr<GpuChannel>> gpu_channels_;
+  std::unordered_map<int32_t, std::unique_ptr<GpuChannel>> gpu_channels_;
 
  private:
   void InternalDestroyGpuMemoryBuffer(gfx::GpuMemoryBufferId id, int client_id);
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 8d1b3b6..145790b 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -2076,6 +2076,18 @@
   dict = "data/fuzzer_dictionaries/net_http_security_headers_fuzzer.dict"
 }
 
+fuzzer_test("net_http_transport_security_state_static_fuzzer") {
+  sources = [
+    "http/transport_security_state_static_fuzzer.cc",
+  ]
+  deps = [
+    ":net_fuzzer_test_support",
+    "//net",
+  ]
+  dict =
+      "data/fuzzer_dictionaries/net_http_transport_security_state_fuzzer.dict"
+}
+
 if (host_toolchain == current_toolchain) {
   executable("domain_security_preload_generator") {
     sources = gypi_values.net_domain_security_state_generator_sources
diff --git a/net/cert/x509_certificate_known_roots_win.h b/net/cert/x509_certificate_known_roots_win.h
index eea3a687..43ce812 100644
--- a/net/cert/x509_certificate_known_roots_win.h
+++ b/net/cert/x509_certificate_known_roots_win.h
@@ -16,7 +16,7 @@
 //
 // Note that these *are not* trust anchors for Chromium. They are only used to
 // distinguish `real' root CAs from roots that were user-installed.
-static uint8_t kKnownRootCertSHA256Hashes[][32] = {
+static const uint8_t kKnownRootCertSHA256Hashes[][32] = {
     // C=US, O=Network Solutions L.L.C., CN=Network Solutions Certificate
     // Authority
     {0x00, 0x16, 0x86, 0xCD, 0x18, 0x1F, 0x83, 0xA1, 0xB1, 0x21, 0x7D, 0x30,
diff --git a/net/data/fuzzer_dictionaries/net_http_transport_security_state_fuzzer.dict b/net/data/fuzzer_dictionaries/net_http_transport_security_state_fuzzer.dict
new file mode 100644
index 0000000..8c0f09eb
--- /dev/null
+++ b/net/data/fuzzer_dictionaries/net_http_transport_security_state_fuzzer.dict
@@ -0,0 +1,62 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Fuzzer dictionary targetting (static) transport security state lookups.
+
+"xn--"
+
+# Common preloaded TLDs
+"com"
+"org"
+"org"
+"de"
+"cn"
+"net"
+"eu"
+"nl"
+"net"
+"us"
+"co.uk"
+
+# Characters in the Huffman Tree
+"\x00"
+"-"
+"."
+"0"
+"1"
+"2"
+"3"
+"4"
+"5"
+"6"
+"7"
+"8"
+"9"
+"a"
+"b"
+"c"
+"d"
+"e"
+"f"
+"g"
+"h"
+"i"
+"j"
+"k"
+"l"
+"m"
+"n"
+"o"
+"p"
+"q"
+"r"
+"s"
+"t"
+"u"
+"v"
+"w"
+"x"
+"y"
+"z"
+"\x7F"
diff --git a/net/http/http_util.cc b/net/http/http_util.cc
index 40c37ed..bb980bd0d 100644
--- a/net/http/http_util.cc
+++ b/net/http/http_util.cc
@@ -165,34 +165,6 @@
 }
 
 // static
-// Parse the Range header according to RFC 2616 14.35.1
-// ranges-specifier = byte-ranges-specifier
-// byte-ranges-specifier = bytes-unit "=" byte-range-set
-// byte-range-set  = 1#( byte-range-spec | suffix-byte-range-spec )
-// byte-range-spec = first-byte-pos "-" [last-byte-pos]
-// first-byte-pos  = 1*DIGIT
-// last-byte-pos   = 1*DIGIT
-bool HttpUtil::ParseRanges(const std::string& headers,
-                           std::vector<HttpByteRange>* ranges) {
-  std::string ranges_specifier;
-  HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\r\n");
-
-  while (it.GetNext()) {
-    // Look for "Range" header.
-    if (!base::LowerCaseEqualsASCII(it.name(), "range"))
-      continue;
-    ranges_specifier = it.values();
-    // We just care about the first "Range" header, so break here.
-    break;
-  }
-
-  if (ranges_specifier.empty())
-    return false;
-
-  return ParseRangeHeader(ranges_specifier, ranges);
-}
-
-// static
 bool HttpUtil::ParseRangeHeader(const std::string& ranges_specifier,
                                 std::vector<HttpByteRange>* ranges) {
   size_t equal_char_offset = ranges_specifier.find('=');
@@ -209,8 +181,9 @@
   TrimLWS(&bytes_unit_begin, &bytes_unit_end);
   // "bytes" unit identifier is not found.
   if (!base::LowerCaseEqualsASCII(
-          base::StringPiece(bytes_unit_begin, bytes_unit_end), "bytes"))
+          base::StringPiece(bytes_unit_begin, bytes_unit_end), "bytes")) {
     return false;
+  }
 
   ValuesIterator byte_range_set_iterator(byte_range_set_begin,
                                          byte_range_set_end, ',');
@@ -422,32 +395,6 @@
 }
 
 // static
-std::string HttpUtil::StripHeaders(const std::string& headers,
-                                   const char* const headers_to_remove[],
-                                   size_t headers_to_remove_len) {
-  std::string stripped_headers;
-  HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\r\n");
-
-  while (it.GetNext()) {
-    bool should_remove = false;
-    for (size_t i = 0; i < headers_to_remove_len; ++i) {
-      if (base::LowerCaseEqualsASCII(
-              base::StringPiece(it.name_begin(), it.name_end()),
-              headers_to_remove[i])) {
-        should_remove = true;
-        break;
-      }
-    }
-    if (!should_remove) {
-      // Assume that name and values are on the same line.
-      stripped_headers.append(it.name_begin(), it.values_end());
-      stripped_headers.append("\r\n");
-    }
-  }
-  return stripped_headers;
-}
-
-// static
 bool HttpUtil::IsNonCoalescingHeader(std::string::const_iterator name_begin,
                                      std::string::const_iterator name_end) {
   // NOTE: "set-cookie2" headers do not support expires attributes, so we don't
diff --git a/net/http/http_util.h b/net/http/http_util.h
index de4b1a6..1ccf9cc 100644
--- a/net/http/http_util.h
+++ b/net/http/http_util.h
@@ -46,17 +46,9 @@
                                bool* had_charset,
                                std::string* boundary);
 
-  // Scans the headers and look for the first "Range" header in |headers|,
-  // if "Range" exists and the first one of it is well formatted then returns
-  // true, |ranges| will contain a list of valid ranges. If return
-  // value is false then values in |ranges| should not be used. The format of
-  // "Range" header is defined in RFC 7233 Section 2.1.
+  // Parses the value of a "Range" header as defined in RFC 7233 Section 2.1.
   // https://tools.ietf.org/html/rfc7233#section-2.1
-  static bool ParseRanges(const std::string& headers,
-                          std::vector<HttpByteRange>* ranges);
-
-  // Same thing as ParseRanges except the Range header is known and its value
-  // is directly passed in, rather than requiring searching through a string.
+  // Returns false on failure.
   static bool ParseRangeHeader(const std::string& range_specifier,
                                std::vector<HttpByteRange>* ranges);
 
@@ -98,14 +90,6 @@
   // a fully RFC-2616-compliant header value validation.
   static bool IsValidHeaderValue(const base::StringPiece& value);
 
-  // Strips all header lines from |headers| whose name matches
-  // |headers_to_remove|. |headers_to_remove| is a list of null-terminated
-  // lower-case header names, with array length |headers_to_remove_len|.
-  // Returns the stripped header lines list, separated by "\r\n".
-  static std::string StripHeaders(const std::string& headers,
-                                  const char* const headers_to_remove[],
-                                  size_t headers_to_remove_len);
-
   // Multiple occurances of some headers cannot be coalesced into a comma-
   // separated list since their values are (or contain) unquoted HTTP-date
   // values, which may contain a comma (see RFC 2616 section 3.3.1).
diff --git a/net/http/http_util_unittest.cc b/net/http/http_util_unittest.cc
index b1d4c64a..34c98483 100644
--- a/net/http/http_util_unittest.cc
+++ b/net/http/http_util_unittest.cc
@@ -120,29 +120,6 @@
   }
 }
 
-TEST(HttpUtilTest, StripHeaders) {
-  static const char* const headers =
-      "Origin: origin\r\n"
-      "Content-Type: text/plain\r\n"
-      "Cookies: foo1\r\n"
-      "Custom: baz\r\n"
-      "COOKIES: foo2\r\n"
-      "Server: Apache\r\n"
-      "OrIGin: origin2\r\n";
-
-  static const char* const header_names[] = {
-    "origin", "content-type", "cookies"
-  };
-
-  static const char* const expected_stripped_headers =
-      "Custom: baz\r\n"
-      "Server: Apache\r\n";
-
-  EXPECT_EQ(expected_stripped_headers,
-            HttpUtil::StripHeaders(headers, header_names,
-                                   arraysize(header_names)));
-}
-
 TEST(HttpUtilTest, HeadersIterator) {
   std::string headers = "foo: 1\t\r\nbar: hello world\r\nbaz: 3 \r\n";
 
@@ -844,139 +821,6 @@
   }
 }
 
-TEST(HttpUtilTest, ParseRanges) {
-  const struct {
-    const char* const headers;
-    bool expected_return_value;
-    size_t expected_ranges_size;
-    const struct {
-      int64_t expected_first_byte_position;
-      int64_t expected_last_byte_position;
-      int64_t expected_suffix_length;
-    } expected_ranges[10];
-  } tests[] = {
-    { "Range: bytes=0-10",
-      true,
-      1,
-      { {0, 10, -1}, }
-    },
-    { "Range: bytes=10-0",
-      false,
-      0,
-      {}
-    },
-    { "Range: BytES=0-10",
-      true,
-      1,
-      { {0, 10, -1}, }
-    },
-    { "Range: megabytes=0-10",
-      false,
-      0,
-      {}
-    },
-    { "Range: bytes0-10",
-      false,
-      0,
-      {}
-    },
-    { "Range: bytes=0-0,0-10,10-20,100-200,100-,-200",
-      true,
-      6,
-      { {0, 0, -1},
-        {0, 10, -1},
-        {10, 20, -1},
-        {100, 200, -1},
-        {100, -1, -1},
-        {-1, -1, 200},
-      }
-    },
-    { "Range: bytes=0-10\r\n"
-      "Range: bytes=0-10,10-20,100-200,100-,-200",
-      true,
-      1,
-      { {0, 10, -1}
-      }
-    },
-    { "Range: bytes=",
-      false,
-      0,
-      {}
-    },
-    { "Range: bytes=-",
-      false,
-      0,
-      {}
-    },
-    { "Range: bytes=0-10-",
-      false,
-      0,
-      {}
-    },
-    { "Range: bytes=-0-10",
-      false,
-      0,
-      {}
-    },
-    { "Range: bytes =0-10\r\n",
-      true,
-      1,
-      { {0, 10, -1}
-      }
-    },
-    { "Range: bytes=  0-10      \r\n",
-      true,
-      1,
-      { {0, 10, -1}
-      }
-    },
-    { "Range: bytes  =   0  -   10      \r\n",
-      true,
-      1,
-      { {0, 10, -1}
-      }
-    },
-    { "Range: bytes=   0-1   0\r\n",
-      false,
-      0,
-      {}
-    },
-    { "Range: bytes=   0-     -10\r\n",
-      false,
-      0,
-      {}
-    },
-    { "Range: bytes=   0  -  1   ,   10 -20,   100- 200 ,  100-,  -200 \r\n",
-      true,
-      5,
-      { {0, 1, -1},
-        {10, 20, -1},
-        {100, 200, -1},
-        {100, -1, -1},
-        {-1, -1, 200},
-      }
-    },
-  };
-
-  for (size_t i = 0; i < arraysize(tests); ++i) {
-    std::vector<HttpByteRange> ranges;
-    bool return_value = HttpUtil::ParseRanges(std::string(tests[i].headers),
-                                              &ranges);
-    EXPECT_EQ(tests[i].expected_return_value, return_value);
-    if (return_value) {
-      EXPECT_EQ(tests[i].expected_ranges_size, ranges.size());
-      for (size_t j = 0; j < ranges.size(); ++j) {
-        EXPECT_EQ(tests[i].expected_ranges[j].expected_first_byte_position,
-                  ranges[j].first_byte_position());
-        EXPECT_EQ(tests[i].expected_ranges[j].expected_last_byte_position,
-                  ranges[j].last_byte_position());
-        EXPECT_EQ(tests[i].expected_ranges[j].expected_suffix_length,
-                  ranges[j].suffix_length());
-      }
-    }
-  }
-}
-
 TEST(HttpUtilTest, ParseContentRangeHeader) {
   const struct {
     const char* const content_range_header_spec;
diff --git a/net/http/transport_security_state.h b/net/http/transport_security_state.h
index c020ec1..a55cf62 100644
--- a/net/http/transport_security_state.h
+++ b/net/http/transport_security_state.h
@@ -477,6 +477,7 @@
 
  private:
   friend class TransportSecurityStateTest;
+  friend class TransportSecurityStateStaticFuzzer;
   FRIEND_TEST_ALL_PREFIXES(HttpSecurityHeadersTest, UpdateDynamicPKPOnly);
   FRIEND_TEST_ALL_PREFIXES(HttpSecurityHeadersTest, UpdateDynamicPKPMaxAge0);
   FRIEND_TEST_ALL_PREFIXES(HttpSecurityHeadersTest, NoClobberPins);
diff --git a/net/http/transport_security_state_static_fuzzer.cc b/net/http/transport_security_state_static_fuzzer.cc
new file mode 100644
index 0000000..b7b9b5f7d
--- /dev/null
+++ b/net/http/transport_security_state_static_fuzzer.cc
@@ -0,0 +1,49 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "net/http/transport_security_state.h"
+
+namespace net {
+
+class TransportSecurityStateStaticFuzzer {
+ public:
+  bool FuzzStaticDomainState(TransportSecurityState* state,
+                             const std::string& input) {
+    state->enable_static_pins_ = true;
+    TransportSecurityState::STSState sts_result;
+    TransportSecurityState::PKPState pkp_result;
+    return state->GetStaticDomainState(input, &sts_result, &pkp_result);
+  }
+
+  bool FuzzStaticExpectCTState(TransportSecurityState* state,
+                               const std::string& input) {
+    state->enable_static_expect_ct_ = true;
+    TransportSecurityState::ExpectCTState result;
+    return state->GetStaticExpectCTState(input, &result);
+  }
+
+  bool FuzzStaticExpectStapleState(TransportSecurityState* state,
+                                   const std::string& input) {
+    state->enable_static_expect_staple_ = true;
+    TransportSecurityState::ExpectStapleState result;
+    return state->GetStaticExpectStapleState(input, &result);
+  }
+};
+
+}  // namespace net
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  std::string input(reinterpret_cast<const char*>(data), size);
+
+  net::TransportSecurityStateStaticFuzzer helper;
+  net::TransportSecurityState state;
+
+  helper.FuzzStaticDomainState(&state, input);
+  helper.FuzzStaticExpectCTState(&state, input);
+  helper.FuzzStaticExpectStapleState(&state, input);
+
+  return 0;
+}
diff --git a/net/http/transport_security_state_unittest.cc b/net/http/transport_security_state_unittest.cc
index 061a0ae6..7fb0b491 100644
--- a/net/http/transport_security_state_unittest.cc
+++ b/net/http/transport_security_state_unittest.cc
@@ -486,29 +486,6 @@
   EXPECT_TRUE(state.ShouldUpgradeToSSL("example.com"));
 }
 
-TEST_F(TransportSecurityStateTest, Fuzz) {
-  TransportSecurityState state;
-  TransportSecurityState::STSState sts_state;
-  TransportSecurityState::PKPState pkp_state;
-
-  EnableStaticPins(&state);
-
-  for (size_t i = 0; i < 128; i++) {
-    std::string hostname;
-
-    for (;;) {
-      if (base::RandInt(0, 16) == 7) {
-        break;
-      }
-      if (i > 0 && base::RandInt(0, 7) == 7) {
-        hostname.append(1, '.');
-      }
-      hostname.append(1, 'a' + base::RandInt(0, 25));
-    }
-    state.GetStaticDomainState(hostname, &sts_state, &pkp_state);
-  }
-}
-
 TEST_F(TransportSecurityStateTest, MatchesCase2) {
   TransportSecurityState state;
   const base::Time current_time(base::Time::Now());
diff --git a/net/net.gypi b/net/net.gypi
index 8774c56..ffe6103 100644
--- a/net/net.gypi
+++ b/net/net.gypi
@@ -2008,8 +2008,6 @@
       'quic/test_tools/quic_flow_controller_peer.h',
       'quic/test_tools/quic_framer_peer.cc',
       'quic/test_tools/quic_framer_peer.h',
-      'quic/test_tools/quic_headers_stream_peer.cc',
-      'quic/test_tools/quic_headers_stream_peer.h',
       'quic/test_tools/quic_multipath_sent_packet_manager_peer.cc',
       'quic/test_tools/quic_multipath_sent_packet_manager_peer.h',
       'quic/test_tools/quic_packet_creator_peer.cc',
diff --git a/net/quic/chromium/quic_chromium_client_session.cc b/net/quic/chromium/quic_chromium_client_session.cc
index 9cffc527..fac5b9e 100644
--- a/net/quic/chromium/quic_chromium_client_session.cc
+++ b/net/quic/chromium/quic_chromium_client_session.cc
@@ -147,7 +147,7 @@
   return std::move(dict);
 }
 
-class HpackEncoderDebugVisitor : public QuicHeadersStream::HpackDebugVisitor {
+class HpackEncoderDebugVisitor : public QuicHpackDebugVisitor {
   void OnUseEntry(QuicTime::Delta elapsed) override {
     UMA_HISTOGRAM_TIMES(
         "Net.QuicHpackEncoder.IndexedEntryAge",
@@ -155,7 +155,7 @@
   }
 };
 
-class HpackDecoderDebugVisitor : public QuicHeadersStream::HpackDebugVisitor {
+class HpackDecoderDebugVisitor : public QuicHpackDebugVisitor {
   void OnUseEntry(QuicTime::Delta elapsed) override {
     UMA_HISTOGRAM_TIMES(
         "Net.QuicHpackDecoder.IndexedEntryAge",
@@ -429,9 +429,9 @@
 
 void QuicChromiumClientSession::Initialize() {
   QuicClientSessionBase::Initialize();
-  headers_stream()->SetHpackEncoderDebugVisitor(
+  SetHpackEncoderDebugVisitor(
       base::MakeUnique<HpackEncoderDebugVisitor>());
-  headers_stream()->SetHpackDecoderDebugVisitor(
+  SetHpackDecoderDebugVisitor(
       base::MakeUnique<HpackDecoderDebugVisitor>());
 }
 
diff --git a/net/quic/core/quic_connection_test.cc b/net/quic/core/quic_connection_test.cc
index 007853dc..877356e 100644
--- a/net/quic/core/quic_connection_test.cc
+++ b/net/quic/core/quic_connection_test.cc
@@ -22,6 +22,7 @@
 #include "net/quic/core/quic_packets.h"
 #include "net/quic/core/quic_simple_buffer_allocator.h"
 #include "net/quic/core/quic_utils.h"
+#include "net/quic/platform/api/quic_reference_counted.h"
 #include "net/quic/platform/api/quic_str_cat.h"
 #include "net/quic/test_tools/mock_clock.h"
 #include "net/quic/test_tools/mock_random.h"
diff --git a/net/quic/core/quic_headers_stream.cc b/net/quic/core/quic_headers_stream.cc
index cedcb34b..9f03573 100644
--- a/net/quic/core/quic_headers_stream.cc
+++ b/net/quic/core/quic_headers_stream.cc
@@ -4,453 +4,19 @@
 
 #include "net/quic/core/quic_headers_stream.h"
 
-#include <algorithm>
-#include <cstdint>
-#include <string>
-#include <utility>
-
-#include "base/macros.h"
-#include "base/metrics/histogram_macros.h"
-#include "net/quic/core/quic_bug_tracker.h"
 #include "net/quic/core/quic_flags.h"
-#include "net/quic/core/quic_header_list.h"
-#include "net/quic/core/quic_server_session_base.h"
 #include "net/quic/core/quic_spdy_session.h"
-#include "net/quic/core/quic_time.h"
-#include "net/quic/platform/api/quic_str_cat.h"
-#include "net/spdy/spdy_protocol.h"
-
-using base::StringPiece;
-using std::string;
 
 namespace net {
 
-namespace {
-
-class HeaderTableDebugVisitor : public HpackHeaderTable::DebugVisitorInterface {
- public:
-  HeaderTableDebugVisitor(
-      const QuicClock* clock,
-      std::unique_ptr<QuicHeadersStream::HpackDebugVisitor> visitor)
-      : clock_(clock), headers_stream_hpack_visitor_(std::move(visitor)) {}
-
-  int64_t OnNewEntry(const HpackEntry& entry) override {
-    DVLOG(1) << entry.GetDebugString();
-    return (clock_->ApproximateNow() - QuicTime::Zero()).ToMicroseconds();
-  }
-
-  void OnUseEntry(const HpackEntry& entry) override {
-    const QuicTime::Delta elapsed(
-        clock_->ApproximateNow() -
-        QuicTime::Delta::FromMicroseconds(entry.time_added()) -
-        QuicTime::Zero());
-    DVLOG(1) << entry.GetDebugString() << " " << elapsed.ToMilliseconds()
-             << " ms";
-    headers_stream_hpack_visitor_->OnUseEntry(elapsed);
-  }
-
- private:
-  const QuicClock* clock_;
-  std::unique_ptr<QuicHeadersStream::HpackDebugVisitor>
-      headers_stream_hpack_visitor_;
-
-  DISALLOW_COPY_AND_ASSIGN(HeaderTableDebugVisitor);
-};
-
-// When forced HOL blocking is enabled, extra bytes in the form of
-// HTTP/2 DATA frame headers are inserted on the way down to the
-// session layer.  |ForceAckListener| filters the |OnPacketAcked()|
-// notifications generated by the session layer to not count the extra
-// bytes.  Otherwise, code that is using ack listener on streams might
-// consider it an error if more bytes are acked than were written to
-// the stream, it is the case with some internal stats gathering code.
-class ForceHolAckListener : public QuicAckListenerInterface {
- public:
-  // |extra_bytes| should be initialized to the size of the HTTP/2
-  // DATA frame header inserted when forced HOL blocking is enabled.
-  ForceHolAckListener(
-      QuicReferenceCountedPointer<QuicAckListenerInterface> stream_ack_listener,
-      int extra_bytes)
-      : stream_ack_listener_(std::move(stream_ack_listener)),
-        extra_bytes_(extra_bytes) {
-    DCHECK_GE(extra_bytes, 0);
-  }
-
-  void OnPacketAcked(int acked_bytes, QuicTime::Delta ack_delay_time) override {
-    if (extra_bytes_ > 0) {
-      // Don't count the added HTTP/2 DATA frame header bytes
-      int delta = std::min(extra_bytes_, acked_bytes);
-      extra_bytes_ -= delta;
-      acked_bytes -= delta;
-    }
-    stream_ack_listener_->OnPacketAcked(acked_bytes, ack_delay_time);
-  }
-
-  void OnPacketRetransmitted(int retransmitted_bytes) override {
-    stream_ack_listener_->OnPacketRetransmitted(retransmitted_bytes);
-  }
-
- protected:
-  ~ForceHolAckListener() override {}
-
- private:
-  QuicReferenceCountedPointer<QuicAckListenerInterface> stream_ack_listener_;
-  int extra_bytes_;
-
-  DISALLOW_COPY_AND_ASSIGN(ForceHolAckListener);
-};
-
-}  // namespace
-
-QuicHeadersStream::HpackDebugVisitor::HpackDebugVisitor() {}
-
-QuicHeadersStream::HpackDebugVisitor::~HpackDebugVisitor() {}
-
-// A SpdyFramerVisitor that passes HEADERS frames to the QuicSpdyStream, and
-// closes the connection if any unexpected frames are received.
-class QuicHeadersStream::SpdyFramerVisitor
-    : public SpdyFramerVisitorInterface,
-      public SpdyFramerDebugVisitorInterface {
- public:
-  explicit SpdyFramerVisitor(QuicHeadersStream* stream) : stream_(stream) {}
-
-  SpdyHeadersHandlerInterface* OnHeaderFrameStart(
-      SpdyStreamId /* stream_id */) override {
-    return &header_list_;
-  }
-
-  void OnHeaderFrameEnd(SpdyStreamId /* stream_id */,
-                        bool end_headers) override {
-    if (end_headers) {
-      if (stream_->IsConnected()) {
-        stream_->OnHeaderList(header_list_);
-      }
-      header_list_.Clear();
-    }
-  }
-
-  void OnStreamFrameData(SpdyStreamId stream_id,
-                         const char* data,
-                         size_t len) override {
-    if (stream_->OnStreamFrameData(stream_id, data, len)) {
-      return;
-    }
-    CloseConnection("SPDY DATA frame received.");
-  }
-
-  void OnStreamEnd(SpdyStreamId stream_id) override {
-    // The framer invokes OnStreamEnd after processing a frame that had the fin
-    // bit set.
-  }
-
-  void OnStreamPadding(SpdyStreamId stream_id, size_t len) override {
-    CloseConnection("SPDY frame padding received.");
-  }
-
-  void OnError(SpdyFramer* framer) override {
-    CloseConnection(
-        QuicStrCat("SPDY framing error: ",
-                   SpdyFramer::ErrorCodeToString(framer->error_code())));
-  }
-
-  void OnDataFrameHeader(SpdyStreamId stream_id,
-                         size_t length,
-                         bool fin) override {
-    if (stream_->OnDataFrameHeader(stream_id, length, fin)) {
-      return;
-    }
-    CloseConnection("SPDY DATA frame received.");
-  }
-
-  void OnRstStream(SpdyStreamId stream_id,
-                   SpdyRstStreamStatus status) override {
-    CloseConnection("SPDY RST_STREAM frame received.");
-  }
-
-  void OnSetting(SpdySettingsIds id, uint32_t value) override {
-    if (!FLAGS_quic_reloadable_flag_quic_respect_http2_settings_frame) {
-      CloseConnection("SPDY SETTINGS frame received.");
-      return;
-    }
-    switch (id) {
-      case SETTINGS_HEADER_TABLE_SIZE:
-        stream_->UpdateHeaderEncoderTableSize(value);
-        break;
-      case SETTINGS_ENABLE_PUSH:
-        if (FLAGS_quic_reloadable_flag_quic_enable_server_push_by_default &&
-            stream_->session()->perspective() == Perspective::IS_SERVER) {
-          // See rfc7540, Section 6.5.2.
-          if (value > 1) {
-            CloseConnection(
-                QuicStrCat("Invalid value for SETTINGS_ENABLE_PUSH: ", value));
-            return;
-          }
-          stream_->UpdateEnableServerPush(value > 0);
-          break;
-        } else {
-          CloseConnection(
-              QuicStrCat("Unsupported field of HTTP/2 SETTINGS frame: ", id));
-        }
-        break;
-      // TODO(fayang): Need to support SETTINGS_MAX_HEADER_LIST_SIZE when
-      // clients are actually sending it.
-      case SETTINGS_MAX_HEADER_LIST_SIZE:
-        if (FLAGS_quic_reloadable_flag_quic_send_max_header_list_size) {
-          break;
-        }
-      default:
-        CloseConnection(
-            QuicStrCat("Unsupported field of HTTP/2 SETTINGS frame: ", id));
-    }
-  }
-
-  void OnSettingsAck() override {
-    if (!FLAGS_quic_reloadable_flag_quic_respect_http2_settings_frame) {
-      CloseConnection("SPDY SETTINGS frame received.");
-    }
-  }
-
-  void OnSettingsEnd() override {
-    if (!FLAGS_quic_reloadable_flag_quic_respect_http2_settings_frame) {
-      CloseConnection("SPDY SETTINGS frame received.");
-    }
-  }
-
-  void OnPing(SpdyPingId unique_id, bool is_ack) override {
-    CloseConnection("SPDY PING frame received.");
-  }
-
-  void OnGoAway(SpdyStreamId last_accepted_stream_id,
-                SpdyGoAwayStatus status) override {
-    CloseConnection("SPDY GOAWAY frame received.");
-  }
-
-  void OnHeaders(SpdyStreamId stream_id,
-                 bool has_priority,
-                 int weight,
-                 SpdyStreamId /*parent_stream_id*/,
-                 bool /*exclusive*/,
-                 bool fin,
-                 bool end) override {
-    if (!stream_->IsConnected()) {
-      return;
-    }
-
-    // TODO(mpw): avoid down-conversion and plumb SpdyStreamPrecedence through
-    // QuicHeadersStream.
-    SpdyPriority priority =
-        has_priority ? Http2WeightToSpdy3Priority(weight) : 0;
-    stream_->OnHeaders(stream_id, has_priority, priority, fin);
-  }
-
-  void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) override {
-    CloseConnection("SPDY WINDOW_UPDATE frame received.");
-  }
-
-  void OnPushPromise(SpdyStreamId stream_id,
-                     SpdyStreamId promised_stream_id,
-                     bool end) override {
-    if (!stream_->supports_push_promise()) {
-      CloseConnection("PUSH_PROMISE not supported.");
-      return;
-    }
-    if (!stream_->IsConnected()) {
-      return;
-    }
-    stream_->OnPushPromise(stream_id, promised_stream_id, end);
-  }
-
-  void OnContinuation(SpdyStreamId stream_id, bool end) override {}
-
-  void OnPriority(SpdyStreamId stream_id,
-                  SpdyStreamId parent_id,
-                  int weight,
-                  bool exclusive) override {
-    CloseConnection("SPDY PRIORITY frame received.");
-  }
-
-  bool OnUnknownFrame(SpdyStreamId stream_id, int frame_type) override {
-    CloseConnection("Unknown frame type received.");
-    return false;
-  }
-
-  // SpdyFramerDebugVisitorInterface implementation
-  void OnSendCompressedFrame(SpdyStreamId stream_id,
-                             SpdyFrameType type,
-                             size_t payload_len,
-                             size_t frame_len) override {
-    if (payload_len == 0) {
-      QUIC_BUG << "Zero payload length.";
-      return;
-    }
-    int compression_pct = 100 - (100 * frame_len) / payload_len;
-    DVLOG(1) << "Net.QuicHpackCompressionPercentage: " << compression_pct;
-    UMA_HISTOGRAM_PERCENTAGE("Net.QuicHpackCompressionPercentage",
-                             compression_pct);
-  }
-
-  void OnReceiveCompressedFrame(SpdyStreamId stream_id,
-                                SpdyFrameType type,
-                                size_t frame_len) override {
-    if (stream_->IsConnected()) {
-      stream_->OnCompressedFrameSize(frame_len);
-    }
-  }
-
-  void set_max_uncompressed_header_bytes(
-      size_t set_max_uncompressed_header_bytes) {
-    header_list_.set_max_uncompressed_header_bytes(
-        set_max_uncompressed_header_bytes);
-  }
-
- private:
-  void CloseConnection(const string& details) {
-    if (stream_->IsConnected()) {
-      stream_->CloseConnectionWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
-                                          details);
-    }
-  }
-
- private:
-  QuicHeadersStream* stream_;
-  QuicHeaderList header_list_;
-
-  DISALLOW_COPY_AND_ASSIGN(SpdyFramerVisitor);
-};
-
 QuicHeadersStream::QuicHeadersStream(QuicSpdySession* session)
-    : QuicStream(kHeadersStreamId, session),
-      spdy_session_(session),
-      stream_id_(kInvalidStreamId),
-      promised_stream_id_(kInvalidStreamId),
-      fin_(false),
-      frame_len_(0),
-      uncompressed_frame_len_(0),
-      supports_push_promise_(session->perspective() == Perspective::IS_CLIENT),
-      cur_max_timestamp_(QuicTime::Zero()),
-      prev_max_timestamp_(QuicTime::Zero()),
-      spdy_framer_(SpdyFramer::ENABLE_COMPRESSION),
-      spdy_framer_visitor_(new SpdyFramerVisitor(this)) {
-  spdy_framer_.set_visitor(spdy_framer_visitor_.get());
-  spdy_framer_.set_debug_visitor(spdy_framer_visitor_.get());
+    : QuicStream(kHeadersStreamId, session), spdy_session_(session) {
   // The headers stream is exempt from connection level flow control.
   DisableConnectionFlowControlForThisStream();
 }
 
 QuicHeadersStream::~QuicHeadersStream() {}
 
-size_t QuicHeadersStream::WriteHeaders(
-    QuicStreamId stream_id,
-    SpdyHeaderBlock headers,
-    bool fin,
-    SpdyPriority priority,
-    QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
-  SpdyHeadersIR headers_frame(stream_id, std::move(headers));
-  headers_frame.set_fin(fin);
-  if (session()->perspective() == Perspective::IS_CLIENT) {
-    headers_frame.set_has_priority(true);
-    headers_frame.set_weight(Spdy3PriorityToHttp2Weight(priority));
-  }
-  SpdySerializedFrame frame(spdy_framer_.SerializeFrame(headers_frame));
-  WriteOrBufferData(StringPiece(frame.data(), frame.size()), false,
-                    std::move(ack_listener));
-  return frame.size();
-}
-
-size_t QuicHeadersStream::WritePushPromise(QuicStreamId original_stream_id,
-                                           QuicStreamId promised_stream_id,
-                                           SpdyHeaderBlock headers) {
-  if (session()->perspective() == Perspective::IS_CLIENT) {
-    QUIC_BUG << "Client shouldn't send PUSH_PROMISE";
-    return 0;
-  }
-
-  SpdyPushPromiseIR push_promise(original_stream_id, promised_stream_id,
-                                 std::move(headers));
-
-  // PUSH_PROMISE must not be the last frame sent out, at least followed by
-  // response headers.
-  push_promise.set_fin(false);
-
-  SpdySerializedFrame frame(spdy_framer_.SerializeFrame(push_promise));
-  WriteOrBufferData(StringPiece(frame.data(), frame.size()), false, nullptr);
-  return frame.size();
-}
-
-void QuicHeadersStream::WriteDataFrame(
-    QuicStreamId id,
-    StringPiece data,
-    bool fin,
-    QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
-  SpdyDataIR spdy_data(id, data);
-  spdy_data.set_fin(fin);
-  SpdySerializedFrame frame(spdy_framer_.SerializeFrame(spdy_data));
-  QuicReferenceCountedPointer<ForceHolAckListener> force_hol_ack_listener;
-  if (ack_listener != nullptr) {
-    force_hol_ack_listener = new ForceHolAckListener(
-        std::move(ack_listener), frame.size() - data.length());
-  }
-  // Use buffered writes so that coherence of framing is preserved
-  // between streams.
-  WriteOrBufferData(StringPiece(frame.data(), frame.size()), false,
-                    std::move(force_hol_ack_listener));
-}
-
-QuicConsumedData QuicHeadersStream::WritevStreamData(
-    QuicStreamId id,
-    QuicIOVector iov,
-    QuicStreamOffset offset,
-    bool fin,
-    QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
-  const size_t max_len = kSpdyInitialFrameSizeLimit - kDataFrameMinimumSize;
-
-  QuicConsumedData result(0, false);
-  size_t total_length = iov.total_length;
-
-  if (total_length == 0 && fin) {
-    WriteDataFrame(id, StringPiece(), true, std::move(ack_listener));
-    result.fin_consumed = true;
-    return result;
-  }
-
-  // Encapsulate the data into HTTP/2 DATA frames.  The outer loop
-  // handles each element of the source iov, the inner loop handles
-  // the possibility of fragmenting each of those into multiple DATA
-  // frames, as the DATA frames have a max size of 16KB.
-  for (int i = 0; i < iov.iov_count; i++) {
-    size_t src_iov_offset = 0;
-    const struct iovec* src_iov = &iov.iov[i];
-    do {
-      if (queued_data_bytes() > 0) {
-        // Limit the amount of buffering to the minimum needed to
-        // preserve framing.
-        return result;
-      }
-      size_t len = std::min(
-          std::min(src_iov->iov_len - src_iov_offset, max_len), total_length);
-      char* data = static_cast<char*>(src_iov->iov_base) + src_iov_offset;
-      src_iov_offset += len;
-      offset += len;
-      // fin handling, only set it for the final HTTP/2 DATA frame.
-      bool last_iov = i == iov.iov_count - 1;
-      bool last_fragment_within_iov = src_iov_offset >= src_iov->iov_len;
-      bool frame_fin = (last_iov && last_fragment_within_iov) ? fin : false;
-      WriteDataFrame(id, StringPiece(data, len), frame_fin, ack_listener);
-      result.bytes_consumed += len;
-      if (frame_fin) {
-        result.fin_consumed = true;
-      }
-      DCHECK_GE(total_length, len);
-      total_length -= len;
-      if (total_length <= 0) {
-        return result;
-      }
-    } while (src_iov_offset < src_iov->iov_len);
-  }
-
-  return result;
-}
-
 void QuicHeadersStream::OnDataAvailable() {
   char buffer[1024];
   struct iovec iov;
@@ -462,10 +28,7 @@
       // No more data to read.
       break;
     }
-    DCHECK(timestamp.IsInitialized());
-    cur_max_timestamp_ = std::max(timestamp, cur_max_timestamp_);
-    if (spdy_framer_.ProcessInput(static_cast<char*>(iov.iov_base),
-                                  iov.iov_len) != iov.iov_len) {
+    if (spdy_session_->ProcessHeaderData(iov, timestamp) != iov.iov_len) {
       // Error processing data.
       return;
     }
@@ -474,111 +37,6 @@
   }
 }
 
-void QuicHeadersStream::set_max_uncompressed_header_bytes(
-    size_t set_max_uncompressed_header_bytes) {
-  spdy_framer_visitor_->set_max_uncompressed_header_bytes(
-      set_max_uncompressed_header_bytes);
-}
-
-void QuicHeadersStream::OnHeaders(SpdyStreamId stream_id,
-                                  bool has_priority,
-                                  SpdyPriority priority,
-                                  bool fin) {
-  if (has_priority) {
-    if (session()->perspective() == Perspective::IS_CLIENT) {
-      CloseConnectionWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
-                                 "Server must not send priorities.");
-      return;
-    }
-    spdy_session_->OnStreamHeadersPriority(stream_id, priority);
-  } else {
-    if (session()->perspective() == Perspective::IS_SERVER) {
-      CloseConnectionWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
-                                 "Client must send priorities.");
-      return;
-    }
-  }
-  DCHECK_EQ(kInvalidStreamId, stream_id_);
-  DCHECK_EQ(kInvalidStreamId, promised_stream_id_);
-  stream_id_ = stream_id;
-  fin_ = fin;
-}
-
-void QuicHeadersStream::OnPushPromise(SpdyStreamId stream_id,
-                                      SpdyStreamId promised_stream_id,
-                                      bool end) {
-  DCHECK_EQ(kInvalidStreamId, stream_id_);
-  DCHECK_EQ(kInvalidStreamId, promised_stream_id_);
-  stream_id_ = stream_id;
-  promised_stream_id_ = promised_stream_id;
-}
-
-void QuicHeadersStream::OnHeaderList(const QuicHeaderList& header_list) {
-  DVLOG(1) << "Received header list for stream " << stream_id_ << ": "
-           << header_list.DebugString();
-  if (prev_max_timestamp_ > cur_max_timestamp_) {
-    // prev_max_timestamp_ > cur_max_timestamp_ implies that
-    // headers from lower numbered streams actually came off the
-    // wire after headers for the current stream, hence there was
-    // HOL blocking.
-    QuicTime::Delta delta = prev_max_timestamp_ - cur_max_timestamp_;
-    DVLOG(1) << "stream " << stream_id_
-             << ": Net.QuicSession.HeadersHOLBlockedTime "
-             << delta.ToMilliseconds();
-    spdy_session_->OnHeadersHeadOfLineBlocking(delta);
-  }
-
-  prev_max_timestamp_ = std::max(prev_max_timestamp_, cur_max_timestamp_);
-  cur_max_timestamp_ = QuicTime::Zero();
-  if (promised_stream_id_ == kInvalidStreamId) {
-    spdy_session_->OnStreamHeaderList(stream_id_, fin_, frame_len_,
-                                      header_list);
-  } else {
-    spdy_session_->OnPromiseHeaderList(stream_id_, promised_stream_id_,
-                                       frame_len_, header_list);
-  }
-  // Reset state for the next frame.
-  promised_stream_id_ = kInvalidStreamId;
-  stream_id_ = kInvalidStreamId;
-  fin_ = false;
-  frame_len_ = 0;
-  uncompressed_frame_len_ = 0;
-}
-
-void QuicHeadersStream::OnCompressedFrameSize(size_t frame_len) {
-  frame_len_ += frame_len;
-}
-
-bool QuicHeadersStream::IsConnected() {
-  return session()->connection()->connected();
-}
-
-void QuicHeadersStream::DisableHpackDynamicTable() {
-  spdy_framer_.UpdateHeaderEncoderTableSize(0);
-}
-
-void QuicHeadersStream::SetHpackEncoderDebugVisitor(
-    std::unique_ptr<HpackDebugVisitor> visitor) {
-  spdy_framer_.SetEncoderHeaderTableDebugVisitor(
-      std::unique_ptr<HeaderTableDebugVisitor>(new HeaderTableDebugVisitor(
-          session()->connection()->helper()->GetClock(), std::move(visitor))));
-}
-
-void QuicHeadersStream::SetHpackDecoderDebugVisitor(
-    std::unique_ptr<HpackDebugVisitor> visitor) {
-  spdy_framer_.SetDecoderHeaderTableDebugVisitor(
-      std::unique_ptr<HeaderTableDebugVisitor>(new HeaderTableDebugVisitor(
-          session()->connection()->helper()->GetClock(), std::move(visitor))));
-}
-
-void QuicHeadersStream::UpdateHeaderEncoderTableSize(uint32_t value) {
-  spdy_framer_.UpdateHeaderEncoderTableSize(value);
-}
-
-void QuicHeadersStream::UpdateEnableServerPush(bool value) {
-  spdy_session_->set_server_push_enabled(value);
-}
-
 void QuicHeadersStream::MaybeReleaseSequencerBuffer() {
   if (FLAGS_quic_reloadable_flag_quic_headers_stream_release_sequencer_buffer &&
       spdy_session_->ShouldReleaseHeadersStreamSequencerBuffer()) {
@@ -586,48 +44,4 @@
   }
 }
 
-size_t QuicHeadersStream::SendMaxHeaderListSize(size_t value) {
-  SpdySettingsIR settings_frame;
-  settings_frame.AddSetting(SETTINGS_MAX_HEADER_LIST_SIZE, value);
-
-  SpdySerializedFrame frame(spdy_framer_.SerializeFrame(settings_frame));
-  WriteOrBufferData(StringPiece(frame.data(), frame.size()), false, nullptr);
-  return frame.size();
-}
-
-bool QuicHeadersStream::OnDataFrameHeader(QuicStreamId stream_id,
-                                          size_t length,
-                                          bool fin) {
-  if (!spdy_session_->force_hol_blocking()) {
-    return false;
-  }
-  if (!IsConnected()) {
-    return true;
-  }
-  DVLOG(1) << "DATA frame header for stream " << stream_id << " length "
-           << length << " fin " << fin;
-  fin_ = fin;
-  frame_len_ = length;
-  if (fin && length == 0) {
-    OnStreamFrameData(stream_id, "", 0);
-  }
-  return true;
-}
-
-bool QuicHeadersStream::OnStreamFrameData(QuicStreamId stream_id,
-                                          const char* data,
-                                          size_t len) {
-  if (!spdy_session_->force_hol_blocking()) {
-    return false;
-  }
-  if (!IsConnected()) {
-    return true;
-  }
-  frame_len_ -= len;
-  // Ignore fin_ while there is more data coming, if frame_len_ > 0.
-  spdy_session_->OnStreamFrameData(stream_id, data, len,
-                                   frame_len_ > 0 ? false : fin_);
-  return true;
-}
-
 }  // namespace net
diff --git a/net/quic/core/quic_headers_stream.h b/net/quic/core/quic_headers_stream.h
index 6c66b11..40684a020 100644
--- a/net/quic/core/quic_headers_stream.h
+++ b/net/quic/core/quic_headers_stream.h
@@ -28,149 +28,23 @@
 // allocate an instance of QuicHeadersStream to send and receive headers.
 class QUIC_EXPORT_PRIVATE QuicHeadersStream : public QuicStream {
  public:
-  class QUIC_EXPORT_PRIVATE HpackDebugVisitor {
-   public:
-    HpackDebugVisitor();
-
-    virtual ~HpackDebugVisitor();
-
-    // For each HPACK indexed representation processed, |elapsed| is
-    // the time since the corresponding entry was added to the dynamic
-    // table.
-    virtual void OnUseEntry(QuicTime::Delta elapsed) = 0;
-
-   private:
-    DISALLOW_COPY_AND_ASSIGN(HpackDebugVisitor);
-  };
-
   explicit QuicHeadersStream(QuicSpdySession* session);
   ~QuicHeadersStream() override;
 
-  // Writes |headers| for |stream_id| in an HTTP/2 HEADERS frame to the peer.
-  // If |fin| is true, the fin flag will be set on the HEADERS frame.  Returns
-  // the size, in bytes, of the resulting HEADERS frame.
-  virtual size_t WriteHeaders(
-      QuicStreamId stream_id,
-      SpdyHeaderBlock headers,
-      bool fin,
-      SpdyPriority priority,
-      QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
-
-  // Write |headers| for |promised_stream_id| on |original_stream_id| in a
-  // PUSH_PROMISE frame to peer.
-  // Return the size, in bytes, of the resulting PUSH_PROMISE frame.
-  virtual size_t WritePushPromise(QuicStreamId original_stream_id,
-                                  QuicStreamId promised_stream_id,
-                                  SpdyHeaderBlock headers);
-
-  // For forcing HOL blocking.  This encapsulates data from other
-  // streams into HTTP/2 data frames on the headers stream.
-  QuicConsumedData WritevStreamData(
-      QuicStreamId id,
-      QuicIOVector iov,
-      QuicStreamOffset offset,
-      bool fin,
-      QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
-
   // QuicStream implementation
   void OnDataAvailable() override;
 
-  bool supports_push_promise() { return supports_push_promise_; }
-
-  // Experimental: force HPACK to use static table and huffman coding
-  // only.  Part of exploring improvements related to headers stream
-  // induced HOL blocking in QUIC.
-  void DisableHpackDynamicTable();
-
-  // Optional, enables instrumentation related to go/quic-hpack.
-  void SetHpackEncoderDebugVisitor(std::unique_ptr<HpackDebugVisitor> visitor);
-  void SetHpackDecoderDebugVisitor(std::unique_ptr<HpackDebugVisitor> visitor);
-
-  // Sets the maximum size of the header compression table spdy_framer_ is
-  // willing to use to decode header blocks.
-  void UpdateHeaderEncoderTableSize(uint32_t value);
-
-  // Called when SETTINGS_ENABLE_PUSH is received, only supported on
-  // server side.
-  void UpdateEnableServerPush(bool value);
-
   // Release underlying buffer if allowed.
   void MaybeReleaseSequencerBuffer();
 
-  // Sends SETTINGS_MAX_HEADER_LIST_SIZE SETTINGS frame.
-  size_t SendMaxHeaderListSize(size_t value);
-
-  // Sets how much encoded data the hpack decoder of spdy_framer_ is willing to
-  // buffer.
-  void set_max_decode_buffer_size_bytes(size_t max_decode_buffer_size_bytes) {
-    spdy_framer_.set_max_decode_buffer_size_bytes(max_decode_buffer_size_bytes);
-  }
-
-  void set_max_uncompressed_header_bytes(
-      size_t set_max_uncompressed_header_bytes);
-
  private:
   friend class test::QuicHeadersStreamPeer;
 
-  class SpdyFramerVisitor;
-
-  // The following methods are called by the SimpleVisitor.
-
-  // Called when a HEADERS frame has been received.
-  void OnHeaders(SpdyStreamId stream_id,
-                 bool has_priority,
-                 SpdyPriority priority,
-                 bool fin);
-
-  // Called when a PUSH_PROMISE frame has been received.
-  void OnPushPromise(SpdyStreamId stream_id,
-                     SpdyStreamId promised_stream_id,
-                     bool end);
-
-  // Called when the complete list of headers is available.
-  void OnHeaderList(const QuicHeaderList& header_list);
-
-  // Called when the size of the compressed frame payload is available.
-  void OnCompressedFrameSize(size_t frame_len);
-
-  // For force HOL blocking, where stream frames from all streams are
-  // plumbed through headers stream as HTTP/2 data frames.
-  // The following two return false if force_hol_blocking_ is false.
-  bool OnDataFrameHeader(QuicStreamId stream_id, size_t length, bool fin);
-  bool OnStreamFrameData(QuicStreamId stream_id, const char* data, size_t len);
-  // Helper for |WritevStreamData()|.
-  void WriteDataFrame(
-      QuicStreamId stream_id,
-      base::StringPiece data,
-      bool fin,
-      QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
-
   // Returns true if the session is still connected.
   bool IsConnected();
 
   QuicSpdySession* spdy_session_;
 
-  // Data about the stream whose headers are being processed.
-  QuicStreamId stream_id_;
-  QuicStreamId promised_stream_id_;
-  bool fin_;
-  size_t frame_len_;
-  size_t uncompressed_frame_len_;
-
-  bool supports_push_promise_;
-
-  // Timestamps used to measure HOL blocking, these are recorded by
-  // the sequencer approximate to the time of arrival off the wire.
-  // |cur_max_timestamp_| tracks the most recent arrival time of
-  // frames for current (at the headers stream level) processed
-  // stream's headers, and |prev_max_timestamp_| tracks the most
-  // recent arrival time of lower numbered streams.
-  QuicTime cur_max_timestamp_;
-  QuicTime prev_max_timestamp_;
-
-  SpdyFramer spdy_framer_;
-  std::unique_ptr<SpdyFramerVisitor> spdy_framer_visitor_;
-
   DISALLOW_COPY_AND_ASSIGN(QuicHeadersStream);
 };
 
diff --git a/net/quic/core/quic_headers_stream_test.cc b/net/quic/core/quic_headers_stream_test.cc
index e3e0524..76c49ce 100644
--- a/net/quic/core/quic_headers_stream_test.cc
+++ b/net/quic/core/quic_headers_stream_test.cc
@@ -16,7 +16,6 @@
 #include "net/quic/core/spdy_utils.h"
 #include "net/quic/platform/api/quic_str_cat.h"
 #include "net/quic/test_tools/quic_connection_peer.h"
-#include "net/quic/test_tools/quic_headers_stream_peer.h"
 #include "net/quic/test_tools/quic_spdy_session_peer.h"
 #include "net/quic/test_tools/quic_stream_peer.h"
 #include "net/quic/test_tools/quic_test_utils.h"
@@ -46,14 +45,14 @@
 namespace net {
 namespace test {
 
-class MockHpackDebugVisitor : public QuicHeadersStream::HpackDebugVisitor {
+class MockQuicHpackDebugVisitor : public QuicHpackDebugVisitor {
  public:
-  MockHpackDebugVisitor() : HpackDebugVisitor() {}
+  MockQuicHpackDebugVisitor() : QuicHpackDebugVisitor() {}
 
   MOCK_METHOD1(OnUseEntry, void(QuicTime::Delta elapsed));
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(MockHpackDebugVisitor);
+  DISALLOW_COPY_AND_ASSIGN(MockQuicHpackDebugVisitor);
 };
 
 namespace {
@@ -228,8 +227,8 @@
         session_(connection_),
         headers_stream_(QuicSpdySessionPeer::GetHeadersStream(&session_)),
         body_("hello world"),
-        hpack_encoder_visitor_(new StrictMock<MockHpackDebugVisitor>),
-        hpack_decoder_visitor_(new StrictMock<MockHpackDebugVisitor>),
+        hpack_encoder_visitor_(new StrictMock<MockQuicHpackDebugVisitor>),
+        hpack_decoder_visitor_(new StrictMock<MockQuicHpackDebugVisitor>),
         stream_frame_(kHeadersStreamId, /*fin=*/false, /*offset=*/0, ""),
         next_promised_stream_id_(2) {
     headers_[":version"] = "HTTP/1.1";
@@ -327,8 +326,8 @@
     EXPECT_CALL(session_,
                 WritevData(headers_stream_, kHeadersStreamId, _, _, false, _))
         .WillOnce(WithArgs<2>(Invoke(this, &QuicHeadersStreamTest::SaveIov)));
-    headers_stream_->WriteHeaders(stream_id, headers_.Clone(), fin, priority,
-                                  nullptr);
+    QuicSpdySessionPeer::WriteHeadersImpl(
+        &session_, stream_id, headers_.Clone(), fin, priority, nullptr);
 
     // Parse the outgoing data and check that it matches was was written.
     if (is_request) {
@@ -398,8 +397,8 @@
   string saved_payloads_;
   std::unique_ptr<SpdyFramer> framer_;
   StrictMock<MockVisitor> visitor_;
-  std::unique_ptr<StrictMock<MockHpackDebugVisitor>> hpack_encoder_visitor_;
-  std::unique_ptr<StrictMock<MockHpackDebugVisitor>> hpack_decoder_visitor_;
+  std::unique_ptr<StrictMock<MockQuicHpackDebugVisitor>> hpack_encoder_visitor_;
+  std::unique_ptr<StrictMock<MockQuicHpackDebugVisitor>> hpack_decoder_visitor_;
   QuicStreamFrame stream_frame_;
   QuicStreamId next_promised_stream_id_;
 };
@@ -446,8 +445,8 @@
       EXPECT_CALL(session_,
                   WritevData(headers_stream_, kHeadersStreamId, _, _, false, _))
           .WillOnce(WithArgs<2>(Invoke(this, &QuicHeadersStreamTest::SaveIov)));
-      headers_stream_->WritePushPromise(stream_id, promised_stream_id,
-                                        headers_.Clone());
+      session_.WritePushPromise(stream_id, promised_stream_id,
+                                headers_.Clone());
 
       // Parse the outgoing data and check that it matches was was written.
       EXPECT_CALL(visitor_,
@@ -462,8 +461,8 @@
       CheckHeaders();
       saved_data_.clear();
     } else {
-      EXPECT_QUIC_BUG(headers_stream_->WritePushPromise(
-                          stream_id, promised_stream_id, headers_.Clone()),
+      EXPECT_QUIC_BUG(session_.WritePushPromise(stream_id, promised_stream_id,
+                                                headers_.Clone()),
                       "Client shouldn't send PUSH_PROMISE");
     }
   }
@@ -629,7 +628,7 @@
 }
 
 TEST_P(QuicHeadersStreamTest, ProcessLargeRawData) {
-  headers_stream_->set_max_uncompressed_header_bytes(256 * 1024);
+  QuicSpdySessionPeer::SetMaxUncompressedHeaderBytes(&session_, 256 * 1024);
   // We want to create a frame that is more than the SPDY Framer's max control
   // frame size, which is 16K, but less than the HPACK decoders max decode
   // buffer size, which is 32K.
@@ -758,7 +757,7 @@
   stream_frame_.data_length = frame.size();
   headers_stream_->OnStreamFrame(stream_frame_);
   EXPECT_EQ(kTestHeaderTableSize,
-            QuicHeadersStreamPeer::GetSpdyFramer(headers_stream_)
+            QuicSpdySessionPeer::GetSpdyFramer(&session_)
                 .header_encoder_table_size());
 }
 
@@ -849,13 +848,10 @@
 }
 
 TEST_P(QuicHeadersStreamTest, HpackDecoderDebugVisitor) {
-  if (FLAGS_use_nested_spdy_framer_decoder)
-    return;
-
-  StrictMock<MockHpackDebugVisitor>* hpack_decoder_visitor =
+  StrictMock<MockQuicHpackDebugVisitor>* hpack_decoder_visitor =
       hpack_decoder_visitor_.get();
-  headers_stream_->SetHpackDecoderDebugVisitor(
-      std::move(hpack_decoder_visitor_));
+  QuicSpdySessionPeer::SetHpackDecoderDebugVisitor(
+      &session_, std::move(hpack_decoder_visitor_));
 
   // Create some headers we expect to generate entries in HPACK's
   // dynamic table, in addition to content-length.
@@ -904,10 +900,10 @@
 }
 
 TEST_P(QuicHeadersStreamTest, HpackEncoderDebugVisitor) {
-  StrictMock<MockHpackDebugVisitor>* hpack_encoder_visitor =
+  StrictMock<MockQuicHpackDebugVisitor>* hpack_encoder_visitor =
       hpack_encoder_visitor_.get();
-  headers_stream_->SetHpackEncoderDebugVisitor(
-      std::move(hpack_encoder_visitor_));
+  QuicSpdySessionPeer::SetHpackEncoderDebugVisitor(
+      &session_, std::move(hpack_encoder_visitor_));
 
   if (perspective() == Perspective::IS_SERVER) {
     InSequence seq;
@@ -964,7 +960,7 @@
           .WillRepeatedly(WithArgs<2, 5>(Invoke(
               this, &QuicHeadersStreamTest::SaveIovAndNotifyAckListener)));
 
-      QuicConsumedData consumed_data = headers_stream_->WritevStreamData(
+      QuicConsumedData consumed_data = session_.WritevStreamData(
           id, MakeIOVector(data, &iov), offset, fin, ack_listener);
 
       EXPECT_EQ(consumed_data.bytes_consumed, data_len);
@@ -1002,7 +998,7 @@
       .WillOnce(WithArgs<2, 5>(
           Invoke(this, &QuicHeadersStreamTest::SaveIovAndNotifyAckListener)));
 
-  QuicConsumedData consumed_data = headers_stream_->WritevStreamData(
+  QuicConsumedData consumed_data = session_.WritevStreamData(
       kClientDataStreamId1, MakeIOVector(data, &iov), 0, true, nullptr);
 
   EXPECT_EQ(consumed_data.bytes_consumed, 0u);
@@ -1034,7 +1030,7 @@
       .WillOnce(
           WithArgs<2>(Invoke(this, &QuicHeadersStreamTest::SaveIovShort)));
 
-  QuicConsumedData consumed_data = headers_stream_->WritevStreamData(
+  QuicConsumedData consumed_data = session_.WritevStreamData(
       id, MakeIOVector(data, &iov), offset, fin, nullptr);
 
   // bytes_consumed is max HTTP/2 data frame size minus the HTTP/2
@@ -1044,8 +1040,8 @@
   EXPECT_EQ(consumed_data.fin_consumed, false);
 
   // If session already blocked, then bytes_consumed should be zero.
-  consumed_data = headers_stream_->WritevStreamData(
-      id, MakeIOVector(data, &iov), offset, fin, nullptr);
+  consumed_data = session_.WritevStreamData(id, MakeIOVector(data, &iov),
+                                            offset, fin, nullptr);
 
   EXPECT_EQ(consumed_data.bytes_consumed, 0u);
   EXPECT_EQ(consumed_data.fin_consumed, false);
diff --git a/net/quic/core/quic_session_test.cc b/net/quic/core/quic_session_test.cc
index a77ec64..960f292b9 100644
--- a/net/quic/core/quic_session_test.cc
+++ b/net/quic/core/quic_session_test.cc
@@ -22,7 +22,6 @@
 #include "net/quic/test_tools/quic_config_peer.h"
 #include "net/quic/test_tools/quic_connection_peer.h"
 #include "net/quic/test_tools/quic_flow_controller_peer.h"
-#include "net/quic/test_tools/quic_headers_stream_peer.h"
 #include "net/quic/test_tools/quic_session_peer.h"
 #include "net/quic/test_tools/quic_spdy_session_peer.h"
 #include "net/quic/test_tools/quic_spdy_stream_peer.h"
@@ -923,12 +922,12 @@
     EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
     headers["header"] = QuicStrCat("", base::RandUint64(), base::RandUint64(),
                                    base::RandUint64());
-    headers_stream->WriteHeaders(stream_id, headers.Clone(), true, 0, nullptr);
+    session_.WriteHeaders(stream_id, headers.Clone(), true, 0, nullptr);
     stream_id += 2;
   }
   // Write once more to ensure that the headers stream has buffered data. The
   // random headers may have exactly filled the flow control window.
-  headers_stream->WriteHeaders(stream_id, std::move(headers), true, 0, nullptr);
+  session_.WriteHeaders(stream_id, std::move(headers), true, 0, nullptr);
   EXPECT_TRUE(headers_stream->HasBufferedData());
 
   EXPECT_TRUE(headers_stream->flow_controller()->IsBlocked());
@@ -1303,9 +1302,9 @@
   copt.push_back(kDHDT);
   QuicConfigPeer::SetConnectionOptionsToSend(session_.config(), copt);
   session_.OnConfigNegotiated();
-  EXPECT_EQ(QuicHeadersStreamPeer::GetSpdyFramer(session_.headers_stream())
-                .header_encoder_table_size(),
-            0UL);
+  EXPECT_EQ(
+      QuicSpdySessionPeer::GetSpdyFramer(&session_).header_encoder_table_size(),
+      0UL);
 }
 
 TEST_P(QuicSessionTestClient, EnableFHOLThroughConfigOption) {
diff --git a/net/quic/core/quic_spdy_session.cc b/net/quic/core/quic_spdy_session.cc
index e3fd1ea8..b0945f6 100644
--- a/net/quic/core/quic_spdy_session.cc
+++ b/net/quic/core/quic_spdy_session.cc
@@ -7,19 +7,324 @@
 #include <utility>
 
 #include "net/quic/core/quic_bug_tracker.h"
+#include "net/quic/core/quic_flags.h"
 #include "net/quic/core/quic_headers_stream.h"
+#include "net/quic/platform/api/quic_str_cat.h"
 
 using base::StringPiece;
 using std::string;
 
 namespace net {
 
+namespace {
+
+class HeaderTableDebugVisitor
+    : public HpackHeaderTable::DebugVisitorInterface {
+ public:
+  HeaderTableDebugVisitor(const QuicClock* clock,
+                          std::unique_ptr<QuicHpackDebugVisitor> visitor)
+      : clock_(clock), headers_stream_hpack_visitor_(std::move(visitor)) {}
+
+  int64_t OnNewEntry(const HpackEntry& entry) override {
+    DVLOG(1) << entry.GetDebugString();
+    return (clock_->ApproximateNow() - QuicTime::Zero()).ToMicroseconds();
+  }
+
+  void OnUseEntry(const HpackEntry& entry) override {
+    const QuicTime::Delta elapsed(
+        clock_->ApproximateNow() -
+        QuicTime::Delta::FromMicroseconds(entry.time_added()) -
+        QuicTime::Zero());
+    DVLOG(1) << entry.GetDebugString() << " " << elapsed.ToMilliseconds()
+             << " ms";
+    headers_stream_hpack_visitor_->OnUseEntry(elapsed);
+  }
+
+ private:
+  const QuicClock* clock_;
+  std::unique_ptr<QuicHpackDebugVisitor> headers_stream_hpack_visitor_;
+
+  DISALLOW_COPY_AND_ASSIGN(HeaderTableDebugVisitor);
+};
+
+// When forced HOL blocking is enabled, extra bytes in the form of
+// HTTP/2 DATA frame headers are inserted on the way down to the
+// session layer.  |ForceAckListener| filters the |OnPacketAcked()|
+// notifications generated by the session layer to not count the extra
+// bytes.  Otherwise, code that is using ack listener on streams might
+// consider it an error if more bytes are acked than were written to
+// the stream, it is the case with some internal stats gathering code.
+class ForceHolAckListener : public QuicAckListenerInterface {
+ public:
+  // |extra_bytes| should be initialized to the size of the HTTP/2
+  // DATA frame header inserted when forced HOL blocking is enabled.
+  ForceHolAckListener(
+      QuicReferenceCountedPointer<QuicAckListenerInterface> stream_ack_listener,
+      int extra_bytes)
+      : stream_ack_listener_(std::move(stream_ack_listener)),
+        extra_bytes_(extra_bytes) {
+    DCHECK_GE(extra_bytes, 0);
+  }
+
+  void OnPacketAcked(int acked_bytes, QuicTime::Delta ack_delay_time) override {
+    if (extra_bytes_ > 0) {
+      // Don't count the added HTTP/2 DATA frame header bytes
+      int delta = std::min(extra_bytes_, acked_bytes);
+      extra_bytes_ -= delta;
+      acked_bytes -= delta;
+    }
+    stream_ack_listener_->OnPacketAcked(acked_bytes, ack_delay_time);
+  }
+
+  void OnPacketRetransmitted(int retransmitted_bytes) override {
+    stream_ack_listener_->OnPacketRetransmitted(retransmitted_bytes);
+  }
+
+ protected:
+  ~ForceHolAckListener() override {}
+
+ private:
+  QuicReferenceCountedPointer<QuicAckListenerInterface> stream_ack_listener_;
+  int extra_bytes_;
+
+  DISALLOW_COPY_AND_ASSIGN(ForceHolAckListener);
+};
+
+}  // namespace
+
+// A SpdyFramerVisitor that passes HEADERS frames to the QuicSpdyStream, and
+// closes the connection if any unexpected frames are received.
+class QuicSpdySession::SpdyFramerVisitor
+    : public SpdyFramerVisitorInterface,
+      public SpdyFramerDebugVisitorInterface {
+ public:
+  explicit SpdyFramerVisitor(QuicSpdySession* session) : session_(session) {}
+
+  SpdyHeadersHandlerInterface* OnHeaderFrameStart(
+      SpdyStreamId /* stream_id */) override {
+    return &header_list_;
+  }
+
+  void OnHeaderFrameEnd(SpdyStreamId /* stream_id */,
+                        bool end_headers) override {
+    if (end_headers) {
+      if (session_->IsConnected()) {
+        session_->OnHeaderList(header_list_);
+      }
+      header_list_.Clear();
+    }
+  }
+
+  void OnStreamFrameData(SpdyStreamId stream_id,
+                         const char* data,
+                         size_t len) override {
+    if (session_->OnStreamFrameData(stream_id, data, len)) {
+      return;
+    }
+    CloseConnection("SPDY DATA frame received.");
+  }
+
+  void OnStreamEnd(SpdyStreamId stream_id) override {
+    // The framer invokes OnStreamEnd after processing a frame that had the fin
+    // bit set.
+  }
+
+  void OnStreamPadding(SpdyStreamId stream_id, size_t len) override {
+    CloseConnection("SPDY frame padding received.");
+  }
+
+  void OnError(SpdyFramer* framer) override {
+    CloseConnection(
+        QuicStrCat("SPDY framing error: ",
+                   SpdyFramer::ErrorCodeToString(framer->error_code())));
+  }
+
+  void OnDataFrameHeader(SpdyStreamId stream_id,
+                         size_t length,
+                         bool fin) override {
+    if (session_->OnDataFrameHeader(stream_id, length, fin)) {
+      return;
+    }
+    CloseConnection("SPDY DATA frame received.");
+  }
+
+  void OnRstStream(SpdyStreamId stream_id,
+                   SpdyRstStreamStatus status) override {
+    CloseConnection("SPDY RST_STREAM frame received.");
+  }
+
+  void OnSetting(SpdySettingsIds id, uint32_t value) override {
+    if (!FLAGS_quic_reloadable_flag_quic_respect_http2_settings_frame) {
+      CloseConnection("SPDY SETTINGS frame received.");
+      return;
+    }
+    switch (id) {
+      case SETTINGS_HEADER_TABLE_SIZE:
+        session_->UpdateHeaderEncoderTableSize(value);
+        break;
+      case SETTINGS_ENABLE_PUSH:
+        if (FLAGS_quic_reloadable_flag_quic_enable_server_push_by_default &&
+            session_->perspective() == Perspective::IS_SERVER) {
+          // See rfc7540, Section 6.5.2.
+          if (value > 1) {
+            CloseConnection(
+                QuicStrCat("Invalid value for SETTINGS_ENABLE_PUSH: ", value));
+            return;
+          }
+          session_->UpdateEnableServerPush(value > 0);
+          break;
+        } else {
+          CloseConnection(
+              QuicStrCat("Unsupported field of HTTP/2 SETTINGS frame: ", id));
+        }
+        break;
+      // TODO(fayang): Need to support SETTINGS_MAX_HEADER_LIST_SIZE when
+      // clients are actually sending it.
+      case SETTINGS_MAX_HEADER_LIST_SIZE:
+        if (FLAGS_quic_reloadable_flag_quic_send_max_header_list_size) {
+          break;
+        }
+      default:
+        CloseConnection(
+            QuicStrCat("Unsupported field of HTTP/2 SETTINGS frame: ", id));
+    }
+  }
+
+  void OnSettingsAck() override {
+    if (!FLAGS_quic_reloadable_flag_quic_respect_http2_settings_frame) {
+      CloseConnection("SPDY SETTINGS frame received.");
+    }
+  }
+
+  void OnSettingsEnd() override {
+    if (!FLAGS_quic_reloadable_flag_quic_respect_http2_settings_frame) {
+      CloseConnection("SPDY SETTINGS frame received.");
+    }
+  }
+
+  void OnPing(SpdyPingId unique_id, bool is_ack) override {
+    CloseConnection("SPDY PING frame received.");
+  }
+
+  void OnGoAway(SpdyStreamId last_accepted_stream_id,
+                SpdyGoAwayStatus status) override {
+    CloseConnection("SPDY GOAWAY frame received.");
+  }
+
+  void OnHeaders(SpdyStreamId stream_id,
+                 bool has_priority,
+                 int weight,
+                 SpdyStreamId /*parent_stream_id*/,
+                 bool /*exclusive*/,
+                 bool fin,
+                 bool end) override {
+    if (!session_->IsConnected()) {
+      return;
+    }
+
+    // TODO(mpw): avoid down-conversion and plumb SpdyStreamPrecedence through
+    // QuicHeadersStream.
+    SpdyPriority priority =
+        has_priority ? Http2WeightToSpdy3Priority(weight) : 0;
+    session_->OnHeaders(stream_id, has_priority, priority, fin);
+  }
+
+  void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) override {
+    CloseConnection("SPDY WINDOW_UPDATE frame received.");
+  }
+
+  void OnPushPromise(SpdyStreamId stream_id,
+                     SpdyStreamId promised_stream_id,
+                     bool end) override {
+    if (!session_->supports_push_promise()) {
+      CloseConnection("PUSH_PROMISE not supported.");
+      return;
+    }
+    if (!session_->IsConnected()) {
+      return;
+    }
+    session_->OnPushPromise(stream_id, promised_stream_id, end);
+  }
+
+  void OnContinuation(SpdyStreamId stream_id, bool end) override {}
+
+  void OnPriority(SpdyStreamId stream_id,
+                  SpdyStreamId parent_id,
+                  int weight,
+                  bool exclusive) override {
+    CloseConnection("SPDY PRIORITY frame received.");
+  }
+
+  bool OnUnknownFrame(SpdyStreamId stream_id, int frame_type) override {
+    CloseConnection("Unknown frame type received.");
+    return false;
+  }
+
+  // SpdyFramerDebugVisitorInterface implementation
+  void OnSendCompressedFrame(SpdyStreamId stream_id,
+                             SpdyFrameType type,
+                             size_t payload_len,
+                             size_t frame_len) override {
+    if (payload_len == 0) {
+      QUIC_BUG << "Zero payload length.";
+      return;
+    }
+    int compression_pct = 100 - (100 * frame_len) / payload_len;
+    DVLOG(1) << "Net.QuicHpackCompressionPercentage: " << compression_pct;
+  }
+
+  void OnReceiveCompressedFrame(SpdyStreamId stream_id,
+                                SpdyFrameType type,
+                                size_t frame_len) override {
+    if (session_->IsConnected()) {
+      session_->OnCompressedFrameSize(frame_len);
+    }
+  }
+
+  void set_max_uncompressed_header_bytes(
+      size_t set_max_uncompressed_header_bytes) {
+    header_list_.set_max_uncompressed_header_bytes(
+        set_max_uncompressed_header_bytes);
+  }
+
+ private:
+  void CloseConnection(const string& details) {
+    if (session_->IsConnected()) {
+      session_->CloseConnectionWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+                                           details);
+    }
+  }
+
+ private:
+  QuicSpdySession* session_;
+  QuicHeaderList header_list_;
+
+  DISALLOW_COPY_AND_ASSIGN(SpdyFramerVisitor);
+};
+
+QuicHpackDebugVisitor::QuicHpackDebugVisitor() {}
+
+QuicHpackDebugVisitor::~QuicHpackDebugVisitor() {}
+
 QuicSpdySession::QuicSpdySession(QuicConnection* connection,
                                  QuicSession::Visitor* visitor,
                                  const QuicConfig& config)
     : QuicSession(connection, visitor, config),
       force_hol_blocking_(false),
-      server_push_enabled_(false) {}
+      server_push_enabled_(false),
+      stream_id_(kInvalidStreamId),
+      promised_stream_id_(kInvalidStreamId),
+      fin_(false),
+      frame_len_(0),
+      uncompressed_frame_len_(0),
+      supports_push_promise_(perspective() == Perspective::IS_CLIENT),
+      cur_max_timestamp_(QuicTime::Zero()),
+      prev_max_timestamp_(QuicTime::Zero()),
+      spdy_framer_(SpdyFramer::ENABLE_COMPRESSION),
+      spdy_framer_visitor_(new SpdyFramerVisitor(this)) {
+  spdy_framer_.set_visitor(spdy_framer_visitor_.get());
+  spdy_framer_.set_debug_visitor(spdy_framer_visitor_.get());
+}
 
 QuicSpdySession::~QuicSpdySession() {
   // Set the streams' session pointers in closed and dynamic stream lists
@@ -69,6 +374,14 @@
   stream->OnStreamHeaderList(fin, frame_len, header_list);
 }
 
+size_t QuicSpdySession::ProcessHeaderData(const struct iovec& iov,
+                                          QuicTime timestamp) {
+  DCHECK(timestamp.IsInitialized());
+  UpdateCurMaxTimeStamp(timestamp);
+  return spdy_framer_.ProcessInput(static_cast<char*>(iov.iov_base),
+                                   iov.iov_len);
+}
+
 size_t QuicSpdySession::WriteHeaders(
     QuicStreamId id,
     SpdyHeaderBlock headers,
@@ -76,8 +389,132 @@
     SpdyPriority priority,
     QuicReferenceCountedPointer<QuicAckListenerInterface>
         ack_notifier_delegate) {
-  return headers_stream_->WriteHeaders(id, std::move(headers), fin, priority,
-                                       std::move(ack_notifier_delegate));
+  return WriteHeadersImpl(id, std::move(headers), fin, priority,
+                          std::move(ack_notifier_delegate));
+}
+
+size_t QuicSpdySession::WriteHeadersImpl(
+    QuicStreamId id,
+    SpdyHeaderBlock headers,
+    bool fin,
+    SpdyPriority priority,
+    QuicReferenceCountedPointer<QuicAckListenerInterface>
+        ack_notifier_delegate) {
+  SpdyHeadersIR headers_frame(id, std::move(headers));
+  headers_frame.set_fin(fin);
+  if (perspective() == Perspective::IS_CLIENT) {
+    headers_frame.set_has_priority(true);
+    headers_frame.set_weight(Spdy3PriorityToHttp2Weight(priority));
+  }
+  SpdySerializedFrame frame(spdy_framer_.SerializeFrame(headers_frame));
+  headers_stream_->WriteOrBufferData(StringPiece(frame.data(), frame.size()),
+                                     false, std::move(ack_notifier_delegate));
+  return frame.size();
+}
+
+size_t QuicSpdySession::WritePushPromise(QuicStreamId original_stream_id,
+                                         QuicStreamId promised_stream_id,
+                                         SpdyHeaderBlock headers) {
+  if (perspective() == Perspective::IS_CLIENT) {
+    QUIC_BUG << "Client shouldn't send PUSH_PROMISE";
+    return 0;
+  }
+
+  SpdyPushPromiseIR push_promise(original_stream_id, promised_stream_id,
+                                 std::move(headers));
+  // PUSH_PROMISE must not be the last frame sent out, at least followed by
+  // response headers.
+  push_promise.set_fin(false);
+
+  SpdySerializedFrame frame(spdy_framer_.SerializeFrame(push_promise));
+  headers_stream_->WriteOrBufferData(StringPiece(frame.data(), frame.size()),
+                                     false, nullptr);
+  return frame.size();
+}
+
+void QuicSpdySession::WriteDataFrame(
+    QuicStreamId id,
+    StringPiece data,
+    bool fin,
+    QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
+  SpdyDataIR spdy_data(id, data);
+  spdy_data.set_fin(fin);
+  SpdySerializedFrame frame(spdy_framer_.SerializeFrame(spdy_data));
+  QuicReferenceCountedPointer<ForceHolAckListener> force_hol_ack_listener;
+  if (ack_listener != nullptr) {
+    force_hol_ack_listener = new ForceHolAckListener(
+        std::move(ack_listener), frame.size() - data.length());
+  }
+  // Use buffered writes so that coherence of framing is preserved
+  // between streams.
+  headers_stream_->WriteOrBufferData(StringPiece(frame.data(), frame.size()),
+                                     false, std::move(force_hol_ack_listener));
+}
+
+QuicConsumedData QuicSpdySession::WritevStreamData(
+    QuicStreamId id,
+    QuicIOVector iov,
+    QuicStreamOffset offset,
+    bool fin,
+    QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
+  const size_t max_len =
+      kSpdyInitialFrameSizeLimit - kDataFrameMinimumSize;
+
+  QuicConsumedData result(0, false);
+  size_t total_length = iov.total_length;
+
+  if (total_length == 0 && fin) {
+    WriteDataFrame(id, StringPiece(), true, std::move(ack_listener));
+    result.fin_consumed = true;
+    return result;
+  }
+
+  // Encapsulate the data into HTTP/2 DATA frames.  The outer loop
+  // handles each element of the source iov, the inner loop handles
+  // the possibility of fragmenting each of those into multiple DATA
+  // frames, as the DATA frames have a max size of 16KB.
+  for (int i = 0; i < iov.iov_count; i++) {
+    size_t src_iov_offset = 0;
+    const struct iovec* src_iov = &iov.iov[i];
+    do {
+      if (headers_stream_->queued_data_bytes() > 0) {
+        // Limit the amount of buffering to the minimum needed to
+        // preserve framing.
+        return result;
+      }
+      size_t len = std::min(
+          std::min(src_iov->iov_len - src_iov_offset, max_len), total_length);
+      char* data = static_cast<char*>(src_iov->iov_base) + src_iov_offset;
+      src_iov_offset += len;
+      offset += len;
+      // fin handling, only set it for the final HTTP/2 DATA frame.
+      bool last_iov = i == iov.iov_count - 1;
+      bool last_fragment_within_iov = src_iov_offset >= src_iov->iov_len;
+      bool frame_fin = (last_iov && last_fragment_within_iov) ? fin : false;
+      WriteDataFrame(id, StringPiece(data, len), frame_fin, ack_listener);
+      result.bytes_consumed += len;
+      if (frame_fin) {
+        result.fin_consumed = true;
+      }
+      DCHECK_GE(total_length, len);
+      total_length -= len;
+      if (total_length <= 0) {
+        return result;
+      }
+    } while (src_iov_offset < src_iov->iov_len);
+  }
+
+  return result;
+}
+
+size_t QuicSpdySession::SendMaxHeaderListSize(size_t value) {
+  SpdySettingsIR settings_frame;
+  settings_frame.AddSetting(SETTINGS_MAX_HEADER_LIST_SIZE, value);
+
+  SpdySerializedFrame frame(spdy_framer_.SerializeFrame(settings_frame));
+  headers_stream_->WriteOrBufferData(StringPiece(frame.data(), frame.size()),
+                                     false, nullptr);
+  return frame.size();
 }
 
 void QuicSpdySession::OnHeadersHeadOfLineBlocking(QuicTime::Delta delta) {
@@ -107,7 +544,7 @@
   QuicSession::OnCryptoHandshakeEvent(event);
   if (FLAGS_quic_reloadable_flag_quic_send_max_header_list_size &&
       event == HANDSHAKE_CONFIRMED && config()->SupportMaxHeaderListSize()) {
-    headers_stream()->SendMaxHeaderListSize(kDefaultMaxUncompressedHeaderSize);
+    SendMaxHeaderListSize(kDefaultMaxUncompressedHeaderSize);
   }
 }
 
@@ -124,7 +561,7 @@
 void QuicSpdySession::OnConfigNegotiated() {
   QuicSession::OnConfigNegotiated();
   if (config()->HasClientSentConnectionOption(kDHDT, perspective())) {
-    headers_stream_->DisableHpackDynamicTable();
+    DisableHpackDynamicTable();
   }
   const QuicVersion version = connection()->version();
   if (FLAGS_quic_reloadable_flag_quic_enable_force_hol_blocking &&
@@ -167,4 +604,143 @@
   return false;
 }
 
+void QuicSpdySession::OnHeaders(SpdyStreamId stream_id,
+                                bool has_priority,
+                                SpdyPriority priority,
+                                bool fin) {
+  if (has_priority) {
+    if (perspective() == Perspective::IS_CLIENT) {
+      CloseConnectionWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+                                 "Server must not send priorities.");
+      return;
+    }
+    OnStreamHeadersPriority(stream_id, priority);
+  } else {
+    if (perspective() == Perspective::IS_SERVER) {
+      CloseConnectionWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+                                 "Client must send priorities.");
+      return;
+    }
+  }
+  DCHECK_EQ(kInvalidStreamId, stream_id_);
+  DCHECK_EQ(kInvalidStreamId, promised_stream_id_);
+  stream_id_ = stream_id;
+  fin_ = fin;
+}
+
+void QuicSpdySession::OnPushPromise(SpdyStreamId stream_id,
+                                    SpdyStreamId promised_stream_id,
+                                    bool end) {
+  DCHECK_EQ(kInvalidStreamId, stream_id_);
+  DCHECK_EQ(kInvalidStreamId, promised_stream_id_);
+  stream_id_ = stream_id;
+  promised_stream_id_ = promised_stream_id;
+}
+
+void QuicSpdySession::OnHeaderList(const QuicHeaderList& header_list) {
+  DVLOG(1) << "Received header list for stream " << stream_id_ << ": "
+           << header_list.DebugString();
+  if (prev_max_timestamp_ > cur_max_timestamp_) {
+    // prev_max_timestamp_ > cur_max_timestamp_ implies that
+    // headers from lower numbered streams actually came off the
+    // wire after headers for the current stream, hence there was
+    // HOL blocking.
+    QuicTime::Delta delta = prev_max_timestamp_ - cur_max_timestamp_;
+    DVLOG(1) << "stream " << stream_id_
+               << ": Net.QuicSession.HeadersHOLBlockedTime "
+               << delta.ToMilliseconds();
+    OnHeadersHeadOfLineBlocking(delta);
+  }
+  prev_max_timestamp_ = std::max(prev_max_timestamp_, cur_max_timestamp_);
+  cur_max_timestamp_ = QuicTime::Zero();
+  if (promised_stream_id_ == kInvalidStreamId) {
+    OnStreamHeaderList(stream_id_, fin_, frame_len_, header_list);
+  } else {
+    OnPromiseHeaderList(stream_id_, promised_stream_id_, frame_len_,
+                        header_list);
+  }
+  // Reset state for the next frame.
+  promised_stream_id_ = kInvalidStreamId;
+  stream_id_ = kInvalidStreamId;
+  fin_ = false;
+  frame_len_ = 0;
+  uncompressed_frame_len_ = 0;
+}
+
+void QuicSpdySession::OnCompressedFrameSize(size_t frame_len) {
+  frame_len_ += frame_len;
+}
+
+void QuicSpdySession::DisableHpackDynamicTable() {
+  spdy_framer_.UpdateHeaderEncoderTableSize(0);
+}
+
+void QuicSpdySession::SetHpackEncoderDebugVisitor(
+    std::unique_ptr<QuicHpackDebugVisitor> visitor) {
+  spdy_framer_.SetEncoderHeaderTableDebugVisitor(
+      std::unique_ptr<HeaderTableDebugVisitor>(new HeaderTableDebugVisitor(
+          connection()->helper()->GetClock(), std::move(visitor))));
+}
+
+void QuicSpdySession::SetHpackDecoderDebugVisitor(
+    std::unique_ptr<QuicHpackDebugVisitor> visitor) {
+  spdy_framer_.SetDecoderHeaderTableDebugVisitor(
+      std::unique_ptr<HeaderTableDebugVisitor>(new HeaderTableDebugVisitor(
+          connection()->helper()->GetClock(), std::move(visitor))));
+}
+
+void QuicSpdySession::UpdateHeaderEncoderTableSize(uint32_t value) {
+  spdy_framer_.UpdateHeaderEncoderTableSize(value);
+}
+
+void QuicSpdySession::UpdateEnableServerPush(bool value) {
+  set_server_push_enabled(value);
+}
+
+bool QuicSpdySession::OnDataFrameHeader(QuicStreamId stream_id,
+                                        size_t length,
+                                        bool fin) {
+  if (!force_hol_blocking()) {
+    return false;
+  }
+  if (!IsConnected()) {
+    return true;
+  }
+  DVLOG(1) << "DATA frame header for stream " << stream_id << " length "
+           << length << " fin " << fin;
+  fin_ = fin;
+  frame_len_ = length;
+  if (fin && length == 0) {
+    OnStreamFrameData(stream_id, "", 0);
+  }
+  return true;
+}
+
+bool QuicSpdySession::OnStreamFrameData(QuicStreamId stream_id,
+                                        const char* data,
+                                        size_t len) {
+  if (!force_hol_blocking()) {
+    return false;
+  }
+  if (!IsConnected()) {
+    return true;
+  }
+  frame_len_ -= len;
+  // Ignore fin_ while there is more data coming, if frame_len_ > 0.
+  OnStreamFrameData(stream_id, data, len, frame_len_ > 0 ? false : fin_);
+  return true;
+}
+
+void QuicSpdySession::set_max_uncompressed_header_bytes(
+    size_t set_max_uncompressed_header_bytes) {
+  spdy_framer_visitor_->set_max_uncompressed_header_bytes(
+      set_max_uncompressed_header_bytes);
+}
+
+void QuicSpdySession::CloseConnectionWithDetails(QuicErrorCode error,
+                                                 const string& details) {
+  connection()->CloseConnection(
+      error, details, ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+}
+
 }  // namespace net
diff --git a/net/quic/core/quic_spdy_session.h b/net/quic/core/quic_spdy_session.h
index 73e5405..d0066b59 100644
--- a/net/quic/core/quic_spdy_session.h
+++ b/net/quic/core/quic_spdy_session.h
@@ -22,6 +22,26 @@
 class QuicSpdySessionPeer;
 }  // namespace test
 
+// QuicHpackDebugVisitor gathers data used for understanding HPACK HoL
+// dynamics.  Specifically, it is to help predict the compression
+// penalty of avoiding HoL by chagning how the dynamic table is used.
+// In chromium, the concrete instance populates an UMA
+// histogram with the data.
+class QUIC_EXPORT_PRIVATE QuicHpackDebugVisitor {
+ public:
+  QuicHpackDebugVisitor();
+
+  virtual ~QuicHpackDebugVisitor();
+
+  // For each HPACK indexed representation processed, |elapsed| is
+  // the time since the corresponding entry was added to the dynamic
+  // table.
+  virtual void OnUseEntry(QuicTime::Delta elapsed) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(QuicHpackDebugVisitor);
+};
+
 // A QUIC session with a headers stream.
 class QUIC_EXPORT_PRIVATE QuicSpdySession : public QuicSession {
  public:
@@ -56,6 +76,9 @@
                                    size_t frame_len,
                                    const QuicHeaderList& header_list);
 
+  // Sends contents of |iov| to spdy_framer_, returns number of bytes processd.
+  size_t ProcessHeaderData(const struct iovec& iov, QuicTime timestamp);
+
   // Writes |headers| for the stream |id| to the dedicated headers stream.
   // If |fin| is true, then no more data will be sent for the stream |id|.
   // If provided, |ack_notifier_delegate| will be registered to be notified when
@@ -67,6 +90,25 @@
       SpdyPriority priority,
       QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
 
+  // Write |headers| for |promised_stream_id| on |original_stream_id| in a
+  // PUSH_PROMISE frame to peer.
+  // Return the size, in bytes, of the resulting PUSH_PROMISE frame.
+  virtual size_t WritePushPromise(QuicStreamId original_stream_id,
+                                  QuicStreamId promised_stream_id,
+                                  SpdyHeaderBlock headers);
+
+  // For forcing HOL blocking.  This encapsulates data from other
+  // streams into HTTP/2 data frames on the headers stream.
+  QuicConsumedData WritevStreamData(
+      QuicStreamId id,
+      QuicIOVector iov,
+      QuicStreamOffset offset,
+      bool fin,
+      QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
+
+  // Sends SETTINGS_MAX_HEADER_LIST_SIZE SETTINGS frame.
+  size_t SendMaxHeaderListSize(size_t value);
+
   QuicHeadersStream* headers_stream() { return headers_stream_.get(); }
 
   // Called when Head of Line Blocking happens in the headers stream.
@@ -94,6 +136,10 @@
 
   bool server_push_enabled() const { return server_push_enabled_; }
 
+  void UpdateCurMaxTimeStamp(QuicTime timestamp) {
+    cur_max_timestamp_ = std::max(timestamp, cur_max_timestamp_);
+  }
+
   // Called by |QuicHeadersStream::UpdateEnableServerPush()| with
   // value from SETTINGS_ENABLE_PUSH.
   void set_server_push_enabled(bool enable) { server_push_enabled_ = enable; }
@@ -102,6 +148,17 @@
   // aggressively.
   virtual bool ShouldReleaseHeadersStreamSequencerBuffer();
 
+  SpdyFramer* spdy_framer() { return &spdy_framer_; }
+
+  void CloseConnectionWithDetails(QuicErrorCode error,
+                                  const std::string& details);
+
+  // Sets how much encoded data the hpack decoder of spdy_framer_ is willing to
+  // buffer.
+  void set_max_decode_buffer_size_bytes(size_t max_decode_buffer_size_bytes) {
+    spdy_framer_.set_max_decode_buffer_size_bytes(max_decode_buffer_size_bytes);
+  }
+
  protected:
   // Override CreateIncomingDynamicStream() and CreateOutgoingDynamicStream()
   // with QuicSpdyStream return type to make sure that all data streams are
@@ -120,9 +177,79 @@
 
   void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) override;
 
+  bool supports_push_promise() { return supports_push_promise_; }
+
+  // Experimental: force HPACK to use static table and huffman coding
+  // only.  Part of exploring improvements related to headers stream
+  // induced HOL blocking in QUIC.
+  void DisableHpackDynamicTable();
+
+  // Optional, enables instrumentation related to go/quic-hpack.
+  void SetHpackEncoderDebugVisitor(
+      std::unique_ptr<QuicHpackDebugVisitor> visitor);
+  void SetHpackDecoderDebugVisitor(
+      std::unique_ptr<QuicHpackDebugVisitor> visitor);
+
+  // Sets the maximum size of the header compression table spdy_framer_ is
+  // willing to use to decode header blocks.
+  void UpdateHeaderEncoderTableSize(uint32_t value);
+
+  // Called when SETTINGS_ENABLE_PUSH is received, only supported on
+  // server side.
+  void UpdateEnableServerPush(bool value);
+
+  void set_max_uncompressed_header_bytes(
+      size_t set_max_uncompressed_header_bytes);
+
+  bool IsConnected() { return connection()->connected(); }
+
  private:
   friend class test::QuicSpdySessionPeer;
 
+  class SpdyFramerVisitor;
+
+  // The following methods are called by the SimpleVisitor.
+
+  // Called when a HEADERS frame has been received.
+  void OnHeaders(SpdyStreamId stream_id,
+                 bool has_priority,
+                 SpdyPriority priority,
+                 bool fin);
+
+  // Called when a PUSH_PROMISE frame has been received.
+  void OnPushPromise(SpdyStreamId stream_id,
+                     SpdyStreamId promised_stream_id,
+                     bool end);
+
+  // Called when the complete list of headers is available.
+  void OnHeaderList(const QuicHeaderList& header_list);
+
+  // Called when the size of the compressed frame payload is available.
+  void OnCompressedFrameSize(size_t frame_len);
+
+  // For force HOL blocking, where stream frames from all streams are
+  // plumbed through headers stream as HTTP/2 data frames.
+  // The following two return false if force_hol_blocking_ is false.
+  bool OnDataFrameHeader(QuicStreamId stream_id, size_t length, bool fin);
+  bool OnStreamFrameData(QuicStreamId stream_id, const char* data, size_t len);
+
+  // Helper for |WritevStreamData()|.
+  void WriteDataFrame(
+      QuicStreamId stream_id,
+      base::StringPiece data,
+      bool fin,
+      QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
+
+  // This was formerly QuicHeadersStream::WriteHeaders.  Needs to be
+  // separate from QuicSpdySession::WriteHeaders because tests call
+  // this but mock the latter.
+  size_t WriteHeadersImpl(
+      QuicStreamId id,
+      SpdyHeaderBlock headers,
+      bool fin,
+      SpdyPriority priority,
+      QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
+
   std::unique_ptr<QuicHeadersStream> headers_stream_;
 
   // If set, redirect all data through the headers stream in order to
@@ -134,6 +261,27 @@
   // headers will be pushed.
   bool server_push_enabled_;
 
+  // Data about the stream whose headers are being processed.
+  QuicStreamId stream_id_;
+  QuicStreamId promised_stream_id_;
+  bool fin_;
+  size_t frame_len_;
+  size_t uncompressed_frame_len_;
+
+  bool supports_push_promise_;
+
+  // Timestamps used to measure HOL blocking, these are recorded by
+  // the sequencer approximate to the time of arrival off the wire.
+  // |cur_max_timestamp_| tracks the most recent arrival time of
+  // frames for current (at the headers stream level) processed
+  // stream's headers, and |prev_max_timestamp_| tracks the most
+  // recent arrival time of lower numbered streams.
+  QuicTime cur_max_timestamp_;
+  QuicTime prev_max_timestamp_;
+
+  SpdyFramer spdy_framer_;
+  std::unique_ptr<SpdyFramerVisitor> spdy_framer_visitor_;
+
   DISALLOW_COPY_AND_ASSIGN(QuicSpdySession);
 };
 
diff --git a/net/quic/core/quic_spdy_stream.cc b/net/quic/core/quic_spdy_stream.cc
index 9913738..74a45fa 100644
--- a/net/quic/core/quic_spdy_stream.cc
+++ b/net/quic/core/quic_spdy_stream.cc
@@ -335,8 +335,8 @@
     QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
   if (spdy_session_->headers_stream() != nullptr &&
       spdy_session_->force_hol_blocking()) {
-    return spdy_session_->headers_stream()->WritevStreamData(
-        id(), iov, offset, fin, std::move(ack_listener));
+    return spdy_session_->WritevStreamData(id(), iov, offset, fin,
+                                           std::move(ack_listener));
   }
   return QuicStream::WritevDataInner(iov, offset, fin, std::move(ack_listener));
 }
diff --git a/net/quic/core/quic_stream.h b/net/quic/core/quic_stream.h
index 3b3ca6ad0d..5e5cfb0 100644
--- a/net/quic/core/quic_stream.h
+++ b/net/quic/core/quic_stream.h
@@ -179,7 +179,6 @@
   // Get peer IP of the lastest packet which connection is dealing/delt with.
   virtual const QuicSocketAddress& PeerAddressOfLatestPacket() const;
 
- protected:
   // Sends as much of 'data' to the connection as the connection will consume,
   // and then buffers any remaining data in queued_data_.
   // If fin is true: if it is immediately passed on to the session,
@@ -189,6 +188,7 @@
       bool fin,
       QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
 
+ protected:
   // Sends as many bytes in the first |count| buffers of |iov| to the connection
   // as the connection will consume.
   // If |ack_listener| is provided, then it will be notified once all
diff --git a/net/quic/test_tools/quic_headers_stream_peer.cc b/net/quic/test_tools/quic_headers_stream_peer.cc
deleted file mode 100644
index 1e8e65b..0000000
--- a/net/quic/test_tools/quic_headers_stream_peer.cc
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "net/quic/test_tools/quic_headers_stream_peer.h"
-
-#include "net/quic/core/quic_headers_stream.h"
-
-namespace net {
-namespace test {
-
-// static
-const SpdyFramer& QuicHeadersStreamPeer::GetSpdyFramer(
-    QuicHeadersStream* stream) {
-  return stream->spdy_framer_;
-}
-
-}  // namespace test
-}  // namespace net
diff --git a/net/quic/test_tools/quic_headers_stream_peer.h b/net/quic/test_tools/quic_headers_stream_peer.h
deleted file mode 100644
index 998f3e9..0000000
--- a/net/quic/test_tools/quic_headers_stream_peer.h
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef NET_QUIC_TEST_TOOLS_QUIC_HEADERS_STREAM_PEER_H_
-#define NET_QUIC_TEST_TOOLS_QUIC_HEADERS_STREAM_PEER_H_
-
-#include "net/spdy/spdy_framer.h"
-
-namespace net {
-
-class QuicHeadersStream;
-
-namespace test {
-
-class QuicHeadersStreamPeer {
- public:
-  static const SpdyFramer& GetSpdyFramer(QuicHeadersStream* stream);
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(QuicHeadersStreamPeer);
-};
-
-}  // namespace test
-
-}  // namespace net
-
-#endif  // NET_QUIC_TEST_TOOLS_QUIC_HEADERS_STREAM_PEER_H_
diff --git a/net/quic/test_tools/quic_spdy_session_peer.cc b/net/quic/test_tools/quic_spdy_session_peer.cc
index ad760e62..37b76e1 100644
--- a/net/quic/test_tools/quic_spdy_session_peer.cc
+++ b/net/quic/test_tools/quic_spdy_session_peer.cc
@@ -28,5 +28,41 @@
   session->force_hol_blocking_ = value;
 }
 
+// static
+const SpdyFramer& QuicSpdySessionPeer::GetSpdyFramer(
+    QuicSpdySession* session) {
+  return session->spdy_framer_;
+}
+
+void QuicSpdySessionPeer::SetHpackEncoderDebugVisitor(
+    QuicSpdySession* session,
+    std::unique_ptr<QuicHpackDebugVisitor> visitor) {
+  session->SetHpackEncoderDebugVisitor(std::move(visitor));
+}
+
+void QuicSpdySessionPeer::SetHpackDecoderDebugVisitor(
+    QuicSpdySession* session,
+    std::unique_ptr<QuicHpackDebugVisitor> visitor) {
+  session->SetHpackDecoderDebugVisitor(std::move(visitor));
+}
+
+void QuicSpdySessionPeer::SetMaxUncompressedHeaderBytes(
+    QuicSpdySession* session,
+    size_t set_max_uncompressed_header_bytes) {
+  session->set_max_uncompressed_header_bytes(set_max_uncompressed_header_bytes);
+}
+
+// static
+size_t QuicSpdySessionPeer::WriteHeadersImpl(
+    QuicSpdySession* session,
+    QuicStreamId id,
+    SpdyHeaderBlock headers,
+    bool fin,
+    SpdyPriority priority,
+    QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
+  return session->WriteHeadersImpl(id, std::move(headers), fin, priority,
+                                   std::move(ack_listener));
+}
+
 }  // namespace test
 }  // namespace net
diff --git a/net/quic/test_tools/quic_spdy_session_peer.h b/net/quic/test_tools/quic_spdy_session_peer.h
index 238ba1bb..f06d579 100644
--- a/net/quic/test_tools/quic_spdy_session_peer.h
+++ b/net/quic/test_tools/quic_spdy_session_peer.h
@@ -8,11 +8,13 @@
 #include "base/macros.h"
 #include "net/quic/core/quic_packets.h"
 #include "net/quic/core/quic_write_blocked_list.h"
+#include "net/spdy/spdy_framer.h"
 
 namespace net {
 
 class QuicHeadersStream;
 class QuicSpdySession;
+class QuicHpackDebugVisitor;
 
 namespace test {
 
@@ -22,6 +24,23 @@
   static void SetHeadersStream(QuicSpdySession* session,
                                QuicHeadersStream* headers_stream);
   static void SetForceHolBlocking(QuicSpdySession* session, bool value);
+  static const SpdyFramer& GetSpdyFramer(QuicSpdySession* session);
+  static void SetHpackEncoderDebugVisitor(
+      QuicSpdySession* session,
+      std::unique_ptr<QuicHpackDebugVisitor> visitor);
+  static void SetHpackDecoderDebugVisitor(
+      QuicSpdySession* session,
+      std::unique_ptr<QuicHpackDebugVisitor> visitor);
+  static void SetMaxUncompressedHeaderBytes(
+      QuicSpdySession* session,
+      size_t set_max_uncompressed_header_bytes);
+  static size_t WriteHeadersImpl(
+      QuicSpdySession* session,
+      QuicStreamId id,
+      SpdyHeaderBlock headers,
+      bool fin,
+      SpdyPriority priority,
+      QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
 
  private:
   DISALLOW_COPY_AND_ASSIGN(QuicSpdySessionPeer);
diff --git a/net/tools/quic/quic_client_bin.cc b/net/tools/quic/quic_client_bin.cc
index 2f4ac4d8..31a1d69 100644
--- a/net/tools/quic/quic_client_bin.cc
+++ b/net/tools/quic/quic_client_bin.cc
@@ -53,7 +53,6 @@
 #include "net/quic/core/quic_flags.h"
 #include "net/quic/core/quic_packets.h"
 #include "net/quic/core/quic_server_id.h"
-#include "net/quic/core/quic_utils.h"
 #include "net/quic/platform/api/quic_socket_address.h"
 #include "net/quic/platform/api/quic_str_cat.h"
 #include "net/quic/platform/api/quic_text_utils.h"
@@ -70,13 +69,13 @@
 using net::MultiLogCTVerifier;
 using net::ProofVerifier;
 using net::ProofVerifierChromium;
+using net::QuicTextUtils;
 using net::SpdyHeaderBlock;
 using net::TransportSecurityState;
-using net::QuicTextUtils;
 using std::cout;
 using std::cerr;
-using std::string;
 using std::endl;
+using std::string;
 
 // The IP or hostname the quic client will connect to.
 string FLAGS_host = "";
@@ -260,7 +259,6 @@
   std::unique_ptr<CertVerifier> cert_verifier(CertVerifier::CreateDefault());
   std::unique_ptr<TransportSecurityState> transport_security_state(
       new TransportSecurityState);
-  transport_security_state.reset(new TransportSecurityState);
   std::unique_ptr<CTVerifier> ct_verifier(new MultiLogCTVerifier());
   std::unique_ptr<CTPolicyEnforcer> ct_policy_enforcer(new CTPolicyEnforcer());
   std::unique_ptr<ProofVerifier> proof_verifier;
@@ -321,6 +319,7 @@
 
   // Make sure to store the response, for later output.
   client.set_store_response(true);
+
   // Send the request.
   client.SendRequestAndWaitForResponse(header_block, body, /*fin=*/true);
 
diff --git a/net/tools/quic/quic_simple_client.cc b/net/tools/quic/quic_simple_client.cc
index 0695ff28..9d9528e4 100644
--- a/net/tools/quic/quic_simple_client.cc
+++ b/net/tools/quic/quic_simple_client.cc
@@ -34,32 +34,20 @@
 namespace net {
 
 QuicSimpleClient::QuicSimpleClient(
-    IPEndPoint server_address,
+    QuicSocketAddress server_address,
     const QuicServerId& server_id,
     const QuicVersionVector& supported_versions,
     std::unique_ptr<ProofVerifier> proof_verifier)
-    : QuicSimpleClient(server_address,
-                       server_id,
-                       supported_versions,
-                       QuicConfig(),
-                       std::move(proof_verifier)) {}
-
-QuicSimpleClient::QuicSimpleClient(
-    IPEndPoint server_address,
-    const QuicServerId& server_id,
-    const QuicVersionVector& supported_versions,
-    const QuicConfig& config,
-    std::unique_ptr<ProofVerifier> proof_verifier)
     : QuicClientBase(server_id,
                      supported_versions,
-                     config,
+                     QuicConfig(),
                      CreateQuicConnectionHelper(),
                      CreateQuicAlarmFactory(),
                      std::move(proof_verifier)),
       initialized_(false),
       packet_reader_started_(false),
       weak_factory_(this) {
-  set_server_address(QuicSocketAddress(QuicSocketAddressImpl(server_address)));
+  set_server_address(server_address);
 }
 
 QuicSimpleClient::~QuicSimpleClient() {
@@ -77,15 +65,12 @@
       new UDPClientSocket(DatagramSocket::DEFAULT_BIND, RandIntCallback(),
                           &net_log_, NetLogSource()));
 
-  int address_family =
-      server_address.impl().socket_address().GetSockAddrFamily();
-  if (bind_to_address.impl().ip_address().size() != 0) {
-    client_address_ =
-        IPEndPoint(bind_to_address.impl().ip_address(), bind_to_port);
-  } else if (address_family == AF_INET) {
-    client_address_ = IPEndPoint(IPAddress::IPv4AllZeros(), bind_to_port);
+  if (bind_to_address.IsInitialized()) {
+    client_address_ = QuicSocketAddress(bind_to_address, local_port());
+  } else if (server_address.host().address_family() == IpAddressFamily::IP_V4) {
+    client_address_ = QuicSocketAddress(QuicIpAddress::Any4(), bind_to_port);
   } else {
-    client_address_ = IPEndPoint(IPAddress::IPv6AllZeros(), bind_to_port);
+    client_address_ = QuicSocketAddress(QuicIpAddress::Any6(), bind_to_port);
   }
 
   int rc = socket->Connect(server_address.impl().socket_address());
@@ -106,11 +91,13 @@
     return false;
   }
 
-  rc = socket->GetLocalAddress(&client_address_);
+  IPEndPoint address;
+  rc = socket->GetLocalAddress(&address);
   if (rc != OK) {
     LOG(ERROR) << "GetLocalAddress failed: " << ErrorToShortString(rc);
     return false;
   }
+  client_address_ = QuicSocketAddress(QuicSocketAddressImpl(address));
 
   socket_.swap(socket);
   packet_reader_.reset(new QuicChromiumPacketReader(
@@ -164,7 +151,7 @@
 }
 
 QuicSocketAddress QuicSimpleClient::GetLatestClientAddress() const {
-  return QuicSocketAddress(QuicSocketAddressImpl(client_address_));
+  return client_address_;
 }
 
 bool QuicSimpleClient::OnPacket(const QuicReceivedPacket& packet,
diff --git a/net/tools/quic/quic_simple_client.h b/net/tools/quic/quic_simple_client.h
index ce5c403c..f0bc052b 100644
--- a/net/tools/quic/quic_simple_client.h
+++ b/net/tools/quic/quic_simple_client.h
@@ -40,17 +40,11 @@
 class QuicSimpleClient : public QuicClientBase,
                          public QuicChromiumPacketReader::Visitor {
  public:
-  // Create a quic client, which will have events managed by an externally owned
-  // EpollServer.
-  QuicSimpleClient(IPEndPoint server_address,
+  // Create a quic client, which will have events managed by the message loop.
+  QuicSimpleClient(QuicSocketAddress server_address,
                    const QuicServerId& server_id,
                    const QuicVersionVector& supported_versions,
                    std::unique_ptr<ProofVerifier> proof_verifier);
-  QuicSimpleClient(IPEndPoint server_address,
-                   const QuicServerId& server_id,
-                   const QuicVersionVector& supported_versions,
-                   const QuicConfig& config,
-                   std::unique_ptr<ProofVerifier> proof_verifier);
 
   ~QuicSimpleClient() override;
 
@@ -87,7 +81,7 @@
   QuicChromiumClock clock_;
 
   // Address of the client if the client is connected to the server.
-  IPEndPoint client_address_;
+  QuicSocketAddress client_address_;
 
   // UDP socket connected to the server.
   std::unique_ptr<UDPClientSocket> socket_;
diff --git a/net/tools/quic/quic_simple_client_bin.cc b/net/tools/quic/quic_simple_client_bin.cc
index 560d607b..ffa5e54 100644
--- a/net/tools/quic/quic_simple_client_bin.cc
+++ b/net/tools/quic/quic_simple_client_bin.cc
@@ -44,21 +44,17 @@
 #include "base/command_line.h"
 #include "base/logging.h"
 #include "base/message_loop/message_loop.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/strings/string_split.h"
-#include "base/strings/string_util.h"
-#include "net/base/ip_address.h"
-#include "net/base/ip_endpoint.h"
 #include "net/base/net_errors.h"
 #include "net/base/privacy_mode.h"
 #include "net/cert/cert_verifier.h"
 #include "net/cert/multi_log_ct_verifier.h"
-#include "net/http/http_request_info.h"
 #include "net/http/transport_security_state.h"
 #include "net/quic/chromium/crypto/proof_verifier_chromium.h"
 #include "net/quic/core/quic_error_codes.h"
 #include "net/quic/core/quic_packets.h"
 #include "net/quic/core/quic_server_id.h"
+#include "net/quic/platform/api/quic_socket_address.h"
+#include "net/quic/platform/api/quic_str_cat.h"
 #include "net/quic/platform/api/quic_text_utils.h"
 #include "net/spdy/spdy_header_block.h"
 #include "net/spdy/spdy_http_utils.h"
@@ -74,6 +70,7 @@
 using net::ProofVerifier;
 using net::ProofVerifierChromium;
 using net::QuicTextUtils;
+using net::SpdyHeaderBlock;
 using net::TransportSecurityState;
 using std::cout;
 using std::cerr;
@@ -223,10 +220,8 @@
   base::MessageLoopForIO message_loop;
 
   // Determine IP address to connect to from supplied hostname.
-  net::IPAddress ip_addr;
+  net::QuicIpAddress ip_addr;
 
-  // TODO(rtenneti): GURL's doesn't support default_protocol argument, thus
-  // protocol is required in the URL.
   GURL url(urls[0]);
   string host = FLAGS_host;
   if (host.empty()) {
@@ -236,7 +231,7 @@
   if (port == 0) {
     port = url.EffectiveIntPort();
   }
-  if (!ip_addr.AssignFromIPLiteral(host)) {
+  if (!ip_addr.FromString(host)) {
     net::AddressList addresses;
     int rv = net::SynchronousHostResolver::Resolve(host, &addresses);
     if (rv != net::OK) {
@@ -244,10 +239,11 @@
                  << "' : " << net::ErrorToShortString(rv);
       return 1;
     }
-    ip_addr = addresses[0].address();
+    ip_addr =
+        net::QuicIpAddress(net::QuicIpAddressImpl(addresses[0].address()));
   }
 
-  string host_port = net::IPAddressToStringWithPort(ip_addr, FLAGS_port);
+  string host_port = net::QuicStrCat(ip_addr.ToString(), ":", port);
   VLOG(1) << "Resolved " << host << " to " << host_port << endl;
 
   // Build the client, and try to connect.
@@ -272,7 +268,7 @@
         cert_verifier.get(), ct_policy_enforcer.get(),
         transport_security_state.get(), ct_verifier.get()));
   }
-  net::QuicSimpleClient client(net::IPEndPoint(ip_addr, port), server_id,
+  net::QuicSimpleClient client(net::QuicSocketAddress(ip_addr, port), server_id,
                                versions, std::move(proof_verifier));
   client.set_initial_max_packet_length(
       FLAGS_initial_mtu != 0 ? FLAGS_initial_mtu : net::kDefaultMaxPacketSize);
@@ -302,36 +298,28 @@
   }
 
   // Construct a GET or POST request for supplied URL.
-  net::HttpRequestInfo request;
-  request.method = body.empty() ? "GET" : "POST";
-  request.url = url;
+  SpdyHeaderBlock header_block;
+  header_block[":method"] = body.empty() ? "GET" : "POST";
+  header_block[":scheme"] = url.scheme();
+  header_block[":authority"] = url.host();
+  header_block[":path"] = url.path();
 
   // Append any additional headers supplied on the command line.
-  for (const std::string& header :
-       base::SplitString(FLAGS_headers, ";", base::KEEP_WHITESPACE,
-                         base::SPLIT_WANT_NONEMPTY)) {
-    string sp;
-    base::TrimWhitespaceASCII(header, base::TRIM_ALL, &sp);
+  for (StringPiece sp : QuicTextUtils::Split(FLAGS_headers, ';')) {
+    QuicTextUtils::RemoveLeadingAndTrailingWhitespace(&sp);
     if (sp.empty()) {
       continue;
     }
-    std::vector<string> kv =
-        base::SplitString(sp, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
-    CHECK_EQ(2u, kv.size());
-    string key;
-    base::TrimWhitespaceASCII(kv[0], base::TRIM_ALL, &key);
-    string value;
-    base::TrimWhitespaceASCII(kv[1], base::TRIM_ALL, &value);
-    request.extra_headers.SetHeader(key, value);
+    std::vector<StringPiece> kv = QuicTextUtils::Split(sp, ':');
+    QuicTextUtils::RemoveLeadingAndTrailingWhitespace(&kv[0]);
+    QuicTextUtils::RemoveLeadingAndTrailingWhitespace(&kv[1]);
+    header_block[kv[0]] = kv[1];
   }
 
   // Make sure to store the response, for later output.
   client.set_store_response(true);
 
   // Send the request.
-  net::SpdyHeaderBlock header_block;
-  net::CreateSpdyHeadersFromHttpRequest(request, request.extra_headers,
-                                        /*direct=*/true, &header_block);
   client.SendRequestAndWaitForResponse(header_block, body, /*fin=*/true);
 
   // Print request and response details.
@@ -356,6 +344,7 @@
     } else {
       cout << "body: " << response_body << endl;
     }
+    cout << "trailers: " << client.latest_response_trailers() << endl;
   }
 
   size_t response_code = client.latest_response_code();
diff --git a/net/tools/quic/quic_simple_client_test.cc b/net/tools/quic/quic_simple_client_test.cc
index 88a2c39a..64db2d2 100644
--- a/net/tools/quic/quic_simple_client_test.cc
+++ b/net/tools/quic/quic_simple_client_test.cc
@@ -13,7 +13,7 @@
 namespace test {
 
 TEST(QuicSimpleClientTest, Initialize) {
-  IPEndPoint server_address(IPEndPoint(net::test::Loopback4(), 80));
+  QuicSocketAddress server_address(QuicIpAddress::Loopback4(), 80);
   QuicServerId server_id("hostname", server_address.port(),
                          PRIVACY_MODE_DISABLED);
   QuicVersionVector versions = AllSupportedVersions();
diff --git a/net/tools/quic/quic_simple_server_session.cc b/net/tools/quic/quic_simple_server_session.cc
index fd9a98d8..a107746 100644
--- a/net/tools/quic/quic_simple_server_session.cc
+++ b/net/tools/quic/quic_simple_server_session.cc
@@ -178,9 +178,8 @@
                                               QuicStreamId promised_stream_id,
                                               SpdyHeaderBlock headers) {
   DVLOG(1) << "stream " << original_stream_id
-           << " send PUSH_PROMISE for promised stream " << promised_stream_id;
-  headers_stream()->WritePushPromise(original_stream_id, promised_stream_id,
-                                     std::move(headers));
+             << " send PUSH_PROMISE for promised stream " << promised_stream_id;
+  WritePushPromise(original_stream_id, promised_stream_id, std::move(headers));
 }
 
 void QuicSimpleServerSession::HandlePromisedPushRequests() {
diff --git a/net/tools/quic/quic_simple_server_session_test.cc b/net/tools/quic/quic_simple_server_session_test.cc
index d82973a..a808d23 100644
--- a/net/tools/quic/quic_simple_server_session_test.cc
+++ b/net/tools/quic/quic_simple_server_session_test.cc
@@ -36,9 +36,10 @@
 using std::string;
 using testing::StrictMock;
 using testing::_;
+using testing::AtLeast;
 using testing::InSequence;
 using testing::Return;
-using testing::AtLeast;
+using testing::StrictMock;
 
 namespace net {
 namespace test {
@@ -46,42 +47,39 @@
 typedef QuicSimpleServerSession::PromisedStreamInfo PromisedStreamInfo;
 }  // namespace
 
-class MockQuicHeadersStream : public QuicHeadersStream {
+class QuicSimpleServerSessionPeer {
  public:
-  explicit MockQuicHeadersStream(QuicSpdySession* session)
-      : QuicHeadersStream(session) {}
-
-  // Methods taking non-copyable types like SpdyHeaderBlock by value cannot be
-  // mocked directly.
-  size_t WritePushPromise(QuicStreamId original_stream_id,
-                          QuicStreamId promised_stream_id,
-                          SpdyHeaderBlock headers) override {
-    return WritePushPromiseMock(original_stream_id, promised_stream_id,
-                                headers);
+  static void SetCryptoStream(QuicSimpleServerSession* s,
+                              QuicCryptoServerStream* crypto_stream) {
+    s->crypto_stream_.reset(crypto_stream);
+    s->static_streams()[kCryptoStreamId] = crypto_stream;
   }
-  MOCK_METHOD3(WritePushPromiseMock,
-               size_t(QuicStreamId original_stream_id,
-                      QuicStreamId promised_stream_id,
-                      const SpdyHeaderBlock& headers));
 
-  size_t WriteHeaders(QuicStreamId stream_id,
-                      SpdyHeaderBlock headers,
-                      bool fin,
-                      SpdyPriority priority,
-                      QuicReferenceCountedPointer<QuicAckListenerInterface>
-                          ack_listener) override {
-    return WriteHeadersMock(stream_id, headers, fin, priority, ack_listener);
+  static QuicSpdyStream* CreateIncomingDynamicStream(QuicSimpleServerSession* s,
+                                                     QuicStreamId id) {
+    return s->CreateIncomingDynamicStream(id);
   }
-  MOCK_METHOD5(
-      WriteHeadersMock,
-      size_t(QuicStreamId stream_id,
-             const SpdyHeaderBlock& headers,
-             bool fin,
-             SpdyPriority priority,
-             const QuicReferenceCountedPointer<QuicAckListenerInterface>&
-                 ack_listener));
+
+  static QuicSimpleServerStream* CreateOutgoingDynamicStream(
+      QuicSimpleServerSession* s,
+      SpdyPriority priority) {
+    return s->CreateOutgoingDynamicStream(priority);
+  }
+
+  static std::deque<PromisedStreamInfo>* promised_streams(
+      QuicSimpleServerSession* s) {
+    return &(s->promised_streams_);
+  }
+
+  static QuicStreamId hightest_promised_stream_id(QuicSimpleServerSession* s) {
+    return s->highest_promised_stream_id_;
+  }
 };
 
+namespace {
+
+const size_t kMaxStreamsForTest = 10;
+
 class MockQuicCryptoServerStream : public QuicCryptoServerStream {
  public:
   explicit MockQuicCryptoServerStream(
@@ -130,39 +128,53 @@
           QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener));
 };
 
-class QuicSimpleServerSessionPeer {
+class MockQuicSimpleServerSession : public QuicSimpleServerSession {
  public:
-  static void SetCryptoStream(QuicSimpleServerSession* s,
-                              QuicCryptoServerStream* crypto_stream) {
-    s->crypto_stream_.reset(crypto_stream);
-    s->static_streams()[kCryptoStreamId] = crypto_stream;
+  MockQuicSimpleServerSession(const QuicConfig& config,
+                              QuicConnection* connection,
+                              QuicSession::Visitor* visitor,
+                              QuicCryptoServerStream::Helper* helper,
+                              const QuicCryptoServerConfig* crypto_config,
+                              QuicCompressedCertsCache* compressed_certs_cache,
+                              QuicHttpResponseCache* response_cache)
+      : QuicSimpleServerSession(config,
+                                connection,
+                                visitor,
+                                helper,
+                                crypto_config,
+                                compressed_certs_cache,
+                                response_cache) {}
+  // Methods taking non-copyable types like SpdyHeaderBlock by value cannot be
+  // mocked directly.
+  size_t WritePushPromise(QuicStreamId original_stream_id,
+                          QuicStreamId promised_stream_id,
+                          SpdyHeaderBlock headers) override {
+    return WritePushPromiseMock(original_stream_id, promised_stream_id,
+                                headers);
   }
+  MOCK_METHOD3(WritePushPromiseMock,
+               size_t(QuicStreamId original_stream_id,
+                      QuicStreamId promised_stream_id,
+                      const SpdyHeaderBlock& headers));
 
-  static QuicSpdyStream* CreateIncomingDynamicStream(QuicSimpleServerSession* s,
-                                                     QuicStreamId id) {
-    return s->CreateIncomingDynamicStream(id);
+  size_t WriteHeaders(QuicStreamId stream_id,
+                      SpdyHeaderBlock headers,
+                      bool fin,
+                      SpdyPriority priority,
+                      QuicReferenceCountedPointer<QuicAckListenerInterface>
+                          ack_listener) override {
+    return WriteHeadersMock(stream_id, headers, fin, priority, ack_listener);
   }
-
-  static QuicSimpleServerStream* CreateOutgoingDynamicStream(
-      QuicSimpleServerSession* s,
-      SpdyPriority priority) {
-    return s->CreateOutgoingDynamicStream(priority);
-  }
-
-  static std::deque<PromisedStreamInfo>* promised_streams(
-      QuicSimpleServerSession* s) {
-    return &(s->promised_streams_);
-  }
-
-  static QuicStreamId hightest_promised_stream_id(QuicSimpleServerSession* s) {
-    return s->highest_promised_stream_id_;
-  }
+  MOCK_METHOD5(
+      WriteHeadersMock,
+      size_t(QuicStreamId stream_id,
+             const SpdyHeaderBlock& headers,
+             bool fin,
+             SpdyPriority priority,
+             const QuicReferenceCountedPointer<QuicAckListenerInterface>&
+                 ack_listener));
 };
 
-namespace {
-
-const size_t kMaxStreamsForTest = 10;
-
 class QuicSimpleServerSessionTest
     : public ::testing::TestWithParam<QuicVersion> {
  protected:
@@ -184,7 +196,7 @@
     connection_ = new StrictMock<MockQuicConnectionWithSendStreamData>(
         &helper_, &alarm_factory_, Perspective::IS_SERVER,
         SupportedVersions(GetParam()));
-    session_.reset(new QuicSimpleServerSession(
+    session_.reset(new MockQuicSimpleServerSession(
         config_, connection_, &owner_, &stream_helper_, &crypto_config_,
         &compressed_certs_cache_, &response_cache_));
     MockClock clock;
@@ -193,8 +205,6 @@
         QuicCryptoServerConfig::ConfigOptions()));
     session_->Initialize();
     visitor_ = QuicConnectionPeer::GetVisitor(connection_);
-    headers_stream_ = new MockQuicHeadersStream(session_.get());
-    QuicSpdySessionPeer::SetHeadersStream(session_.get(), headers_stream_);
 
     session_->OnConfigNegotiated();
   }
@@ -208,10 +218,9 @@
   QuicCryptoServerConfig crypto_config_;
   QuicCompressedCertsCache compressed_certs_cache_;
   QuicHttpResponseCache response_cache_;
-  std::unique_ptr<QuicSimpleServerSession> session_;
+  std::unique_ptr<MockQuicSimpleServerSession> session_;
   std::unique_ptr<CryptoHandshakeMessage> handshake_message_;
   QuicConnectionVisitorInterface* visitor_;
-  MockQuicHeadersStream* headers_stream_;
 };
 
 INSTANTIATE_TEST_CASE_P(Tests,
@@ -424,7 +433,7 @@
     connection_ = new StrictMock<MockQuicConnectionWithSendStreamData>(
         &helper_, &alarm_factory_, Perspective::IS_SERVER,
         SupportedVersions(GetParam()));
-    session_.reset(new QuicSimpleServerSession(
+    session_.reset(new MockQuicSimpleServerSession(
         config_, connection_, &owner_, &stream_helper_, &crypto_config_,
         &compressed_certs_cache_, &response_cache_));
     session_->Initialize();
@@ -432,8 +441,6 @@
     session_->OnConfigNegotiated();
 
     visitor_ = QuicConnectionPeer::GetVisitor(connection_);
-    headers_stream_ = new MockQuicHeadersStream(session_.get());
-    QuicSpdySessionPeer::SetHeadersStream(session_.get(), headers_stream_);
 
     // Assume encryption already established.
     MockQuicCryptoServerStream* crypto_stream = new MockQuicCryptoServerStream(
@@ -472,11 +479,11 @@
       push_resources.push_back(QuicHttpResponseCache::ServerPushInfo(
           resource_url, SpdyHeaderBlock(), kDefaultPriority, body));
       // PUSH_PROMISED are sent for all the resources.
-      EXPECT_CALL(*headers_stream_,
+      EXPECT_CALL(*session_,
                   WritePushPromiseMock(kClientDataStreamId1, stream_id, _));
       if (i <= kMaxStreamsForTest) {
         // |kMaxStreamsForTest| promised responses should be sent.
-        EXPECT_CALL(*headers_stream_,
+        EXPECT_CALL(*session_,
                     WriteHeadersMock(stream_id, _, false, kDefaultPriority, _));
         // Since flow control window is smaller than response body, not the
         // whole body will be sent.
@@ -536,8 +543,8 @@
 
   // After an open stream is marked draining, a new stream is expected to be
   // created and a response sent on the stream.
-  EXPECT_CALL(*headers_stream_, WriteHeadersMock(next_out_going_stream_id, _,
-                                                 false, kDefaultPriority, _));
+  EXPECT_CALL(*session_, WriteHeadersMock(next_out_going_stream_id, _, false,
+                                          kDefaultPriority, _));
   EXPECT_CALL(*connection_,
               SendStreamData(next_out_going_stream_id, _, 0, false, _))
       .WillOnce(Return(QuicConsumedData(kStreamFlowControlWindowSize, false)));
@@ -573,12 +580,12 @@
   // only one queued resource will be sent out.
   QuicStreamId stream_not_reset = (kMaxStreamsForTest + 1) * 2;
   InSequence s;
-  EXPECT_CALL(*headers_stream_, WriteHeadersMock(stream_not_reset, _, false,
-                                                 kDefaultPriority, _));
+  EXPECT_CALL(*session_, WriteHeadersMock(stream_not_reset, _, false,
+                                          kDefaultPriority, _));
   EXPECT_CALL(*connection_, SendStreamData(stream_not_reset, _, 0, false, _))
       .WillOnce(Return(QuicConsumedData(kStreamFlowControlWindowSize, false)));
   EXPECT_CALL(*connection_, SendBlocked(stream_not_reset));
-  EXPECT_CALL(*headers_stream_,
+  EXPECT_CALL(*session_,
               WriteHeadersMock(stream_got_reset, _, false, kDefaultPriority, _))
       .Times(0);
 
@@ -602,7 +609,7 @@
   QuicStreamId stream_got_reset = 2;
   EXPECT_CALL(*connection_,
               SendRstStream(stream_got_reset, QUIC_RST_ACKNOWLEDGEMENT, _));
-  EXPECT_CALL(*headers_stream_,
+  EXPECT_CALL(*session_,
               WriteHeadersMock(stream_to_open, _, false, kDefaultPriority, _));
   EXPECT_CALL(*connection_, SendStreamData(stream_to_open, _, 0, false, _))
       .WillOnce(Return(QuicConsumedData(kStreamFlowControlWindowSize, false)));
diff --git a/net/tools/quic/quic_simple_server_stream_test.cc b/net/tools/quic/quic_simple_server_stream_test.cc
index 57eba3e..4eb335e 100644
--- a/net/tools/quic/quic_simple_server_stream_test.cc
+++ b/net/tools/quic/quic_simple_server_stream_test.cc
@@ -79,6 +79,8 @@
   }
 };
 
+namespace {
+
 class MockQuicSimpleServerSession : public QuicSimpleServerSession {
  public:
   const size_t kMaxStreamsForTest = 100;
@@ -172,8 +174,6 @@
   DISALLOW_COPY_AND_ASSIGN(MockQuicSimpleServerSession);
 };
 
-namespace {
-
 class QuicSimpleServerStreamTest
     : public ::testing::TestWithParam<QuicVersion> {
  public:
@@ -504,12 +504,14 @@
       .Times(1)
       .WillOnce(Return(QuicConsumedData(kBody.size(), true)));
   server_initiated_stream->PushResponse(std::move(headers));
-  EXPECT_EQ(kPath, QuicSimpleServerStreamPeer::headers(
-                       server_initiated_stream)[":path"]
-                       .as_string());
-  EXPECT_EQ("GET", QuicSimpleServerStreamPeer::headers(
-                       server_initiated_stream)[":method"]
-                       .as_string());
+  EXPECT_EQ(
+      kPath,
+      QuicSimpleServerStreamPeer::headers(server_initiated_stream)[":path"]
+          .as_string());
+  EXPECT_EQ(
+      "GET",
+      QuicSimpleServerStreamPeer::headers(server_initiated_stream)[":method"]
+          .as_string());
 }
 
 TEST_P(QuicSimpleServerStreamTest, TestSendErrorResponse) {
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/enable-browser-side-navigation b/third_party/WebKit/LayoutTests/FlagExpectations/enable-browser-side-navigation
index e01a7a0..cfe550d 100644
--- a/third_party/WebKit/LayoutTests/FlagExpectations/enable-browser-side-navigation
+++ b/third_party/WebKit/LayoutTests/FlagExpectations/enable-browser-side-navigation
@@ -163,12 +163,6 @@
 crbug.com/673748 virtual/mojo-loading/http/tests/security/cross-frame-access-parent-isolated-world.html [ Timeout ]
 crbug.com/673748 virtual/mojo-loading/http/tests/security/cross-frame-access-parent-explicit-domain-isolated-world.html [ Timeout ]
 
-# https://crbug.com/673751 Clicking on an href link with a javascript on click
-# navigation event should follow the href link
-crbug.com/673748 imported/wpt/html/browsers/browsing-the-web/navigating-across-documents/005.html [ Failure ]
-crbug.com/673748 imported/wpt/html/browsers/browsing-the-web/navigating-across-documents/006.html [ Failure ]
-crbug.com/673748 imported/wpt/html/browsers/browsing-the-web/navigating-across-documents/007.html [ Failure ]
-
 # This test seems to be partially failing without PlzNavigate as well.
 Bug(none) imported/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects-exceptions.html [ Failure ]
 
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/enable-slimming-paint-v2 b/third_party/WebKit/LayoutTests/FlagExpectations/enable-slimming-paint-v2
index e68c6bb..6d905b1 100644
--- a/third_party/WebKit/LayoutTests/FlagExpectations/enable-slimming-paint-v2
+++ b/third_party/WebKit/LayoutTests/FlagExpectations/enable-slimming-paint-v2
@@ -2132,6 +2132,8 @@
 Bug(none) fast/overflow/overflow-stacking.html [ Failure ]
 
 Bug(none) css3/blending/background-blend-mode-overlapping-accelerated-elements.html [ Failure ]
+Bug(none) css3/blending/mix-blend-mode-simple-text.html [ Failure ]
+Bug(none) css3/blending/mix-blend-mode-simple.html [ Failure ]
 Bug(none) css3/blending/effect-background-blend-mode-stacking.html [ Failure ]
 Bug(none) css3/blending/mix-blend-mode-with-masking.html [ Failure ]
 Bug(none) css3/blending/svg-blend-layer-mask.html [ Failure ]
diff --git a/third_party/WebKit/LayoutTests/compositing/layer-creation/fixed-position-change-out-of-view-in-view-expected.txt b/third_party/WebKit/LayoutTests/compositing/layer-creation/fixed-position-change-out-of-view-in-view-expected.txt
index ef0f983..ac2e3b93 100644
--- a/third_party/WebKit/LayoutTests/compositing/layer-creation/fixed-position-change-out-of-view-in-view-expected.txt
+++ b/third_party/WebKit/LayoutTests/compositing/layer-creation/fixed-position-change-out-of-view-in-view-expected.txt
@@ -18,7 +18,6 @@
     {
       "name": "LayoutBlockFlow (positioned) DIV id='fixed2'",
       "position": [100, 100],
-      "bounds": [10, 0],
       "contentsOpaque": true,
       "drawsContent": true,
       "backgroundColor": "#C0C0C0"
@@ -72,7 +71,6 @@
     {
       "name": "LayoutBlockFlow (positioned) DIV id='fixed2'",
       "position": [100, 100],
-      "bounds": [10, 0],
       "contentsOpaque": true,
       "drawsContent": true,
       "backgroundColor": "#C0C0C0"
diff --git a/third_party/WebKit/LayoutTests/compositing/overflow/reparented-scrollbars-non-sc-anc-expected.txt b/third_party/WebKit/LayoutTests/compositing/overflow/reparented-scrollbars-non-sc-anc-expected.txt
index 66e0b689..d178fd3 100644
--- a/third_party/WebKit/LayoutTests/compositing/overflow/reparented-scrollbars-non-sc-anc-expected.txt
+++ b/third_party/WebKit/LayoutTests/compositing/overflow/reparented-scrollbars-non-sc-anc-expected.txt
@@ -45,7 +45,7 @@
     {
       "name": "LayoutBlockFlow (relative positioned) DIV id='foreground'",
       "position": [8, 10008],
-      "bounds": [1200, 0]
+      "contentsOpaque": true
     },
     {
       "name": "Overflow Controls Ancestor Clipping Layer",
diff --git a/third_party/WebKit/LayoutTests/compositing/squashing/do-not-squash-non-self-painting-layer-expected.txt b/third_party/WebKit/LayoutTests/compositing/squashing/do-not-squash-non-self-painting-layer-expected.txt
index 63c4f32..181aff3 100644
--- a/third_party/WebKit/LayoutTests/compositing/squashing/do-not-squash-non-self-painting-layer-expected.txt
+++ b/third_party/WebKit/LayoutTests/compositing/squashing/do-not-squash-non-self-painting-layer-expected.txt
@@ -10,7 +10,7 @@
     {
       "name": "LayoutBlockFlow DIV id='composited-container'",
       "position": [8, 8],
-      "bounds": [784, 0],
+      "contentsOpaque": true,
       "drawsContent": true,
       "backfaceVisibility": "hidden"
     }
diff --git a/third_party/WebKit/LayoutTests/compositing/squashing/squash-compositing-hover-expected.txt b/third_party/WebKit/LayoutTests/compositing/squashing/squash-compositing-hover-expected.txt
index ee8f562..9e48f84 100644
--- a/third_party/WebKit/LayoutTests/compositing/squashing/squash-compositing-hover-expected.txt
+++ b/third_party/WebKit/LayoutTests/compositing/squashing/squash-compositing-hover-expected.txt
@@ -12,7 +12,8 @@
     {
       "name": "LayoutBlockFlow DIV class='composited'",
       "position": [8, 8],
-      "bounds": [784, 0]
+      "transformOrigin": [392, 0],
+      "contentsOpaque": true
     },
     {
       "name": "Squashing Containment Layer",
@@ -46,7 +47,8 @@
     {
       "name": "LayoutBlockFlow DIV class='composited'",
       "position": [8, 8],
-      "bounds": [784, 0]
+      "transformOrigin": [392, 0],
+      "contentsOpaque": true
     },
     {
       "name": "LayoutBlockFlow (positioned) DIV class='composited box behind'",
@@ -88,7 +90,8 @@
     {
       "name": "LayoutBlockFlow DIV class='composited'",
       "position": [8, 8],
-      "bounds": [784, 0]
+      "transformOrigin": [392, 0],
+      "contentsOpaque": true
     },
     {
       "name": "Squashing Containment Layer",
@@ -140,7 +143,8 @@
     {
       "name": "LayoutBlockFlow DIV class='composited'",
       "position": [8, 8],
-      "bounds": [784, 0]
+      "transformOrigin": [392, 0],
+      "contentsOpaque": true
     },
     {
       "name": "Squashing Containment Layer",
@@ -182,7 +186,8 @@
     {
       "name": "LayoutBlockFlow DIV class='composited'",
       "position": [8, 8],
-      "bounds": [784, 0]
+      "transformOrigin": [392, 0],
+      "contentsOpaque": true
     },
     {
       "name": "Squashing Containment Layer",
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/filtered-item-selection-dialog-filtering.js b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/filtered-item-selection-dialog-filtering.js
index 3d4a7ad..a8dcea9 100644
--- a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/filtered-item-selection-dialog-filtering.js
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/filtered-item-selection-dialog-filtering.js
@@ -41,10 +41,10 @@
         function dump()
         {
             TestRunner.addResult("Query:" + JSON.stringify(filteredSelectionDialog._value()));
-            var items = filteredSelectionDialog._filteredItems;
+            var list = filteredSelectionDialog._list;
             var output = [];
-            for (var i = 0; i < items.length; ++i)
-                output.push(delegate.itemKeyAt(items[i]));
+            for (var i = 0; i < list.length(); ++i)
+                output.push(delegate.itemKeyAt(list.itemAtIndex(i)));
             TestRunner.addResult("Output:" + JSON.stringify(output));
         }
 
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-fixed-height-expected.txt b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-fixed-height-expected.txt
new file mode 100644
index 0000000..84f8e10
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-fixed-height-expected.txt
@@ -0,0 +1,623 @@
+Test ListControl rendering and selection for fixed height case.
+Adding 0, 1, 2
+Creating element for 0
+Creating element for 1
+Creating element for 2
+----list[length=3][height=73]----
+ [0] top
+*[0] 0
+*[10] 1
+*[20] 2
+*[30] bottom
+
+Scrolling to 0
+----list[length=3][height=73]----
+ [0] top
+*[0] 0
+*[10] 1
+*[20] 2
+*[30] bottom
+
+Scrolling to 2
+----list[length=3][height=73]----
+ [0] top
+*[0] 0
+*[10] 1
+*[20] 2
+*[30] bottom
+
+ArrowDown
+Selection changed from null to 0
+----list[length=3][height=73]----
+ [0] top
+*[0] 0 (selected)
+*[10] 1
+*[20] 2
+*[30] bottom
+
+Selecting 2
+Selection changed from 0 to 2
+----list[length=3][height=73]----
+ [0] top
+*[0] 0
+*[10] 1
+*[20] 2 (selected)
+*[30] bottom
+
+PageUp
+Selection changed from 2 to 0
+----list[length=3][height=73]----
+ [0] top
+*[0] 0 (selected)
+*[10] 1
+*[20] 2
+*[30] bottom
+
+PageDown
+Selection changed from 0 to 2
+----list[length=3][height=73]----
+ [0] top
+*[0] 0
+*[10] 1
+*[20] 2 (selected)
+*[30] bottom
+
+ArrowDown
+Selection changed from 2 to 0
+----list[length=3][height=73]----
+ [0] top
+*[0] 0 (selected)
+*[10] 1
+*[20] 2
+*[30] bottom
+
+Replacing 0 with 5, 6, 7
+Creating element for 5
+Creating element for 6
+Creating element for 7
+Selection changed from 0 to 2
+----list[length=5][height=73]----
+ [0] top
+*[0] 5
+*[10] 6
+*[20] 7
+*[30] 1
+*[40] 2 (selected)
+*[50] bottom
+
+ArrowUp
+Selection changed from 2 to 7
+----list[length=5][height=73]----
+ [0] top
+*[0] 5
+*[10] 6
+*[20] 7 (selected)
+*[30] 1
+*[40] 2
+*[50] bottom
+
+Pushing 10
+Creating element for 10
+----list[length=6][height=73]----
+ [0] top
+*[0] 5
+*[10] 6
+*[20] 7 (selected)
+*[30] 1
+*[40] 2
+*[50] 10
+*[60] bottom
+
+Selecting 10
+Selection changed from 7 to 10
+----list[length=6][height=73]----
+ [0] top
+*[0] 5
+*[10] 6
+*[20] 7
+*[30] 1
+*[40] 2
+*[50] 10 (selected)
+*[60] bottom
+
+Popping 10
+Selection changed from 10 to 2
+----list[length=5][height=73]----
+ [0] top
+*[0] 5
+*[10] 6
+*[20] 7
+*[30] 1
+*[40] 2 (selected)
+*[50] bottom
+
+Removing 2
+Selection changed from 2 to 7
+----list[length=4][height=73]----
+ [0] top
+*[0] 5
+*[10] 6
+*[20] 7 (selected)
+*[30] 1
+*[40] bottom
+
+Inserting 8
+Creating element for 8
+----list[length=5][height=73]----
+ [0] top
+*[0] 5
+*[10] 8
+*[20] 6
+*[30] 7 (selected)
+*[40] 1
+*[50] bottom
+
+Replacing with 0...20
+Creating element for 0
+Creating element for 1
+Creating element for 2
+Creating element for 3
+Creating element for 4
+Creating element for 5
+Creating element for 6
+Creating element for 7
+Creating element for 8
+Creating element for 9
+Creating element for 10
+Creating element for 11
+Creating element for 12
+Creating element for 13
+Creating element for 14
+Selection changed from 7 to null
+----list[length=20][height=73]----
+ [0] top
+*[0] 0
+*[10] 1
+*[20] 2
+*[30] 3
+*[40] 4
+*[50] 5
+*[60] 6
++[70] 7
+ [80] 8
+ [90] 9
+ [100] 10
+ [110] 11
+ [120] 12
+ [130] 13
+ [140] 14
+ [150] bottom
+
+Resizing
+Creating element for 15
+Creating element for 16
+----list[length=20][height=84]----
+ [0] top
+*[0] 0
+*[10] 1
+*[20] 2
+*[30] 3
+*[40] 4
+*[50] 5
+*[60] 6
+*[70] 7
++[80] 8
+ [90] 9
+ [100] 10
+ [110] 11
+ [120] 12
+ [130] 13
+ [140] 14
+ [150] 15
+ [160] 16
+ [170] bottom
+
+Scrolling to 19
+Creating element for 17
+Creating element for 18
+Creating element for 19
+----list[length=20][height=84]----
+ [-116] top
+ [-86] 3
+ [-76] 4
+ [-66] 5
+ [-56] 6
+ [-46] 7
+ [-36] 8
+ [-26] 9
+ [-16] 10
++[-6] 11
+*[4] 12
+*[14] 13
+*[24] 14
+*[34] 15
+*[44] 16
+*[54] 17
+*[64] 18
+*[74] 19
+ [84] bottom
+
+Scrolling to 5
+----list[length=20][height=84]----
+ [-50] top
+ [-50] 0
+ [-40] 1
+ [-30] 2
+ [-20] 3
+ [-10] 4
+*[0] 5
+*[10] 6
+*[20] 7
+*[30] 8
+*[40] 9
+*[50] 10
+*[60] 11
+*[70] 12
++[80] 13
+ [90] 14
+ [100] 15
+ [110] 16
+ [120] 17
+ [130] 18
+ [140] 19
+ [150] bottom
+
+Scrolling to 12
+----list[length=20][height=84]----
+ [-50] top
+ [-50] 0
+ [-40] 1
+ [-30] 2
+ [-20] 3
+ [-10] 4
+*[0] 5
+*[10] 6
+*[20] 7
+*[30] 8
+*[40] 9
+*[50] 10
+*[60] 11
+*[70] 12
++[80] 13
+ [90] 14
+ [100] 15
+ [110] 16
+ [120] 17
+ [130] 18
+ [140] 19
+ [150] bottom
+
+Scrolling to 13
+----list[length=20][height=84]----
+ [-56] top
+ [-56] 0
+ [-46] 1
+ [-36] 2
+ [-26] 3
+ [-16] 4
++[-6] 5
+*[4] 6
+*[14] 7
+*[24] 8
+*[34] 9
+*[44] 10
+*[54] 11
+*[64] 12
+*[74] 13
+ [84] 14
+ [94] 15
+ [104] 16
+ [114] 17
+ [124] 18
+ [134] 19
+ [144] bottom
+
+Changing the item height, switching to measure
+Creating element for 0
+Creating element for 1
+Creating element for 2
+Creating element for 3
+Creating element for 4
+Creating element for 5
+Creating element for 6
+Creating element for 7
+Creating element for 8
+Creating element for 9
+Creating element for 10
+Creating element for 11
+Creating element for 12
+Creating element for 13
+Creating element for 14
+----list[length=20][height=84]----
+ [-56] top
+ [-56] 0
+ [-41] 1
+ [-26] 2
++[-11] 3
+*[4] 4
+*[19] 5
+*[34] 6
+*[49] 7
+*[64] 8
++[79] 9
+ [94] 10
+ [109] 11
+ [124] 12
+ [139] 13
+ [154] 14
+ [169] bottom
+
+Selecting 7
+Selection changed from null to 7
+----list[length=20][height=84]----
+ [-56] top
+ [-56] 0
+ [-41] 1
+ [-26] 2
++[-11] 3
+*[4] 4
+*[19] 5
+*[34] 6
+*[49] 7 (selected)
+*[64] 8
++[79] 9
+ [94] 10
+ [109] 11
+ [124] 12
+ [139] 13
+ [154] 14
+ [169] bottom
+
+Replacing 7 with 27
+Creating element for 27
+Selection changed from 7 to 10
+----list[length=20][height=84]----
+ [-56] top
+ [-56] 0
+ [-41] 1
+ [-26] 2
++[-11] 3
+*[4] 4
+*[19] 5
+*[34] 6
+*[49] 27
+*[64] 8
++[79] 9
+ [94] 10 (selected)
+ [109] 11
+ [124] 12
+ [139] 13
+ [154] 14
+ [169] bottom
+
+Replacing 18, 19 with 28, 29
+----list[length=20][height=84]----
+ [-56] top
+ [-56] 0
+ [-41] 1
+ [-26] 2
++[-11] 3
+*[4] 4
+*[19] 5
+*[34] 6
+*[49] 27
+*[64] 8
++[79] 9
+ [94] 10 (selected)
+ [109] 11
+ [124] 12
+ [139] 13
+ [154] 14
+ [169] bottom
+
+PageDown
+Creating element for 15
+Creating element for 16
+Creating element for 17
+Creating element for 28
+Creating element for 29
+Selection changed from 10 to 17
+----list[length=20][height=84]----
+ [-186] top
+ [-96] 6
+ [-81] 27
+ [-66] 8
+ [-51] 9
+ [-36] 10
+ [-21] 11
++[-6] 12
+*[9] 13
+*[24] 14
+*[39] 15
+*[54] 16
+*[69] 17 (selected)
+ [84] 28
+ [99] 29
+ [114] bottom
+
+Replacing 1, 2, 3 with [31-43]
+----list[length=30][height=84]----
+ [-336] top
+ [-96] 6
+ [-81] 27
+ [-66] 8
+ [-51] 9
+ [-36] 10
+ [-21] 11
++[-6] 12
+*[9] 13
+*[24] 14
+*[39] 15
+*[54] 16
+*[69] 17 (selected)
+ [84] 28
+ [99] 29
+ [114] bottom
+
+ArrowUp
+Selection changed from 17 to 15
+----list[length=30][height=84]----
+ [-336] top
+ [-96] 6
+ [-81] 27
+ [-66] 8
+ [-51] 9
+ [-36] 10
+ [-21] 11
++[-6] 12
+*[9] 13
+*[24] 14
+*[39] 15 (selected)
+*[54] 16
+*[69] 17
+ [84] 28
+ [99] 29
+ [114] bottom
+
+Selecting -1
+Selection changed from 15 to null
+----list[length=30][height=84]----
+ [-336] top
+ [-96] 6
+ [-81] 27
+ [-66] 8
+ [-51] 9
+ [-36] 10
+ [-21] 11
++[-6] 12
+*[9] 13
+*[24] 14
+*[39] 15
+*[54] 16
+*[69] 17
+ [84] 28
+ [99] 29
+ [114] bottom
+
+ArrowUp
+Selection changed from null to 17
+----list[length=30][height=84]----
+ [-336] top
+ [-96] 6
+ [-81] 27
+ [-66] 8
+ [-51] 9
+ [-36] 10
+ [-21] 11
++[-6] 12
+*[9] 13
+*[24] 14
+*[39] 15
+*[54] 16
+*[69] 17 (selected)
+ [84] 28
+ [99] 29
+ [114] bottom
+
+Selecting -1
+Selection changed from 17 to null
+----list[length=30][height=84]----
+ [-336] top
+ [-96] 6
+ [-81] 27
+ [-66] 8
+ [-51] 9
+ [-36] 10
+ [-21] 11
++[-6] 12
+*[9] 13
+*[24] 14
+*[39] 15
+*[54] 16
+*[69] 17
+ [84] 28
+ [99] 29
+ [114] bottom
+
+ArrowDown
+Creating element for 41
+Creating element for 40
+Creating element for 39
+Creating element for 38
+Creating element for 37
+Creating element for 36
+Creating element for 35
+Creating element for 34
+Creating element for 33
+Creating element for 32
+Creating element for 31
+Selection changed from null to 0
+----list[length=30][height=84]----
+ [0] top
+*[0] 0 (selected)
+*[15] 31
+*[30] 32
+*[45] 33
+*[60] 34
++[75] 35
+ [90] 36
+ [105] 37
+ [120] 38
+ [135] 39
+ [150] 40
+ [165] 41
+ [180] bottom
+
+Selecting -1
+Selection changed from 0 to null
+----list[length=30][height=84]----
+ [0] top
+*[0] 0
+*[15] 31
+*[30] 32
+*[45] 33
+*[60] 34
++[75] 35
+ [90] 36
+ [105] 37
+ [120] 38
+ [135] 39
+ [150] 40
+ [165] 41
+ [180] bottom
+
+PageUp
+Creating element for 42
+Creating element for 43
+Selection changed from null to 12
+----list[length=30][height=84]----
+ [-261] top
+ [-96] 41
+ [-81] 42
+ [-66] 43
+ [-51] 4
+ [-36] 5
+ [-21] 6
++[-6] 27
+*[9] 8
+*[24] 9
+*[39] 10
+*[54] 11
+*[69] 12 (selected)
+ [84] 13
+ [99] 14
+ [114] 15
+ [129] 16
+ [144] 17
+ [159] 28
+ [174] bottom
+
+Replacing all but 29 with []
+Selection changed from 12 to null
+----list[length=1][height=84]----
+ [0] top
+*[0] 29
+*[15] bottom
+
+ArrowDown
+----list[length=1][height=84]----
+ [0] top
+*[0] 29
+*[15] bottom
+
+
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-fixed-height.js b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-fixed-height.js
new file mode 100644
index 0000000..05920c68
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-fixed-height.js
@@ -0,0 +1,201 @@
+TestRunner.addResult('Test ListControl rendering and selection for fixed height case.');
+
+class Delegate {
+  constructor() {
+    this.height = 10;
+  }
+
+  createElementForItem(item) {
+    TestRunner.addResult('Creating element for ' + item);
+    var element = document.createElement('div');
+    element.style.height = this.height + 'px';
+    element.textContent = item;
+    return element;
+  }
+
+  heightForItem(item) {
+    return this.height;
+  }
+
+  isItemSelectable(item) {
+    return (item % 5 === 0) || (item % 5 === 2);
+  }
+
+  selectedItemChanged(from, to, fromElement, toElement) {
+    TestRunner.addResult('Selection changed from ' + from + ' to ' + to);
+    if (fromElement)
+      fromElement.classList.remove('selected');
+    if (toElement)
+      toElement.classList.add('selected');
+  }
+}
+
+var delegate = new Delegate();
+var list = new UI.ListControl(delegate);
+list.setHeightMode(UI.ListHeightMode.Fixed);
+list.element.style.height = '73px';
+UI.inspectorView.element.appendChild(list.element);
+
+function dumpList()
+{
+  var height = list.element.offsetHeight;
+  TestRunner.addResult(`----list[length=${list.length()}][height=${height}]----`);
+  for (var child of list.element.children) {
+    var offsetTop = child.getBoundingClientRect().top - list.element.getBoundingClientRect().top;
+    var offsetBottom = child.getBoundingClientRect().bottom - list.element.getBoundingClientRect().top;
+    var visible = (offsetBottom <= 0 || offsetTop >= height) ? ' ' :
+        (offsetTop >= 0 && offsetBottom <= height ? '*' : '+');
+    var selected = child.classList.contains('selected') ? ' (selected)' : '';
+    var text = child === list._topElement ? 'top' : (child === list._bottomElement ? 'bottom' : child.textContent);
+    TestRunner.addResult(`${visible}[${offsetTop}] ${text}${selected}`);
+  }
+  TestRunner.addResult('');
+}
+
+TestRunner.addResult('Adding 0, 1, 2');
+list.replaceAllItems([0, 1, 2]);
+dumpList();
+
+TestRunner.addResult('Scrolling to 0');
+list.scrollItemAtIndexIntoView(0);
+dumpList();
+
+TestRunner.addResult('Scrolling to 2');
+list.scrollItemAtIndexIntoView(2);
+dumpList();
+
+TestRunner.addResult('ArrowDown');
+list.onKeyDown(TestRunner.createKeyEvent('ArrowDown'));
+dumpList();
+
+TestRunner.addResult('Selecting 2');
+list.selectItemAtIndex(2);
+dumpList();
+
+TestRunner.addResult('PageUp');
+list.onKeyDown(TestRunner.createKeyEvent('PageUp'));
+dumpList();
+
+TestRunner.addResult('PageDown');
+list.onKeyDown(TestRunner.createKeyEvent('PageDown'));
+dumpList();
+
+TestRunner.addResult('ArrowDown');
+list.onKeyDown(TestRunner.createKeyEvent('ArrowDown'));
+dumpList();
+
+TestRunner.addResult('Replacing 0 with 5, 6, 7');
+list.replaceItemsInRange(0, 1, [5, 6, 7]);
+dumpList();
+
+TestRunner.addResult('ArrowUp');
+list.onKeyDown(TestRunner.createKeyEvent('ArrowUp'));
+dumpList();
+
+TestRunner.addResult('Pushing 10');
+list.pushItem(10);
+dumpList();
+
+TestRunner.addResult('Selecting 10');
+list.selectItemAtIndex(5);
+dumpList();
+
+TestRunner.addResult('Popping 10');
+list.popItem();
+dumpList();
+
+TestRunner.addResult('Removing 2');
+list.removeItemAtIndex(4);
+dumpList();
+
+TestRunner.addResult('Inserting 8');
+list.insertItemAtIndex(1, 8);
+dumpList();
+
+TestRunner.addResult('Replacing with 0...20');
+list.replaceAllItems([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]);
+dumpList();
+
+TestRunner.addResult('Resizing');
+list.element.style.height = '84px';
+list.viewportResized();
+dumpList();
+
+TestRunner.addResult('Scrolling to 19');
+list.scrollItemAtIndexIntoView(19);
+dumpList();
+
+TestRunner.addResult('Scrolling to 5');
+list.scrollItemAtIndexIntoView(5);
+dumpList();
+
+TestRunner.addResult('Scrolling to 12');
+list.scrollItemAtIndexIntoView(12);
+dumpList();
+
+TestRunner.addResult('Scrolling to 13');
+list.scrollItemAtIndexIntoView(13);
+dumpList();
+
+TestRunner.addResult('Changing the item height, switching to measure');
+delegate.height = 15;
+list.setHeightMode(UI.ListHeightMode.Measured);
+dumpList();
+
+TestRunner.addResult('Selecting 7');
+list.selectItemAtIndex(7);
+dumpList();
+
+TestRunner.addResult('Replacing 7 with 27');
+list.replaceItemsInRange(7, 8, [27]);
+dumpList();
+
+TestRunner.addResult('Replacing 18, 19 with 28, 29');
+list.replaceItemsInRange(18, 20, [28, 29]);
+dumpList();
+
+TestRunner.addResult('PageDown');
+list.onKeyDown(TestRunner.createKeyEvent('PageDown'));
+dumpList();
+
+TestRunner.addResult('Replacing 1, 2, 3 with [31-43]');
+list.replaceItemsInRange(1, 4, [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43]);
+dumpList();
+
+TestRunner.addResult('ArrowUp');
+list.onKeyDown(TestRunner.createKeyEvent('ArrowUp'));
+dumpList();
+
+TestRunner.addResult('Selecting -1');
+list.selectItemAtIndex(-1);
+dumpList();
+
+TestRunner.addResult('ArrowUp');
+list.onKeyDown(TestRunner.createKeyEvent('ArrowUp'));
+dumpList();
+
+TestRunner.addResult('Selecting -1');
+list.selectItemAtIndex(-1);
+dumpList();
+
+TestRunner.addResult('ArrowDown');
+list.onKeyDown(TestRunner.createKeyEvent('ArrowDown'));
+dumpList();
+
+TestRunner.addResult('Selecting -1');
+list.selectItemAtIndex(-1);
+dumpList();
+
+TestRunner.addResult('PageUp');
+list.onKeyDown(TestRunner.createKeyEvent('PageUp'));
+dumpList();
+
+TestRunner.addResult('Replacing all but 29 with []');
+list.replaceItemsInRange(0, 29, []);
+dumpList();
+
+TestRunner.addResult('ArrowDown');
+list.onKeyDown(TestRunner.createKeyEvent('ArrowDown'));
+dumpList();
+
+TestRunner.completeTest();
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-variable-height-expected.txt b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-variable-height-expected.txt
new file mode 100644
index 0000000..2e58a516
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-variable-height-expected.txt
@@ -0,0 +1,325 @@
+Test ListControl rendering for variable height case.
+Adding 0, 1, 2
+Creating element for 0
+Creating element for 1
+Creating element for 2
+----list[length=3][height=73]----
+ [0] top
+*[0] 0
+*[7] 1
+*[15] 2
+*[24] bottom
+offsets: 0 7 15 24
+
+Scrolling to 0
+----list[length=3][height=73]----
+ [0] top
+*[0] 0
+*[7] 1
+*[15] 2
+*[24] bottom
+offsets: 0 7 15 24
+
+Scrolling to 2
+----list[length=3][height=73]----
+ [0] top
+*[0] 0
+*[7] 1
+*[15] 2
+*[24] bottom
+offsets: 0 7 15 24
+
+Adding 3-20
+Creating element for 3
+Creating element for 4
+Creating element for 5
+Creating element for 6
+Creating element for 7
+Creating element for 8
+Creating element for 9
+Creating element for 10
+Creating element for 11
+Creating element for 12
+Creating element for 13
+Creating element for 14
+----list[length=20][height=73]----
+ [0] top
+*[0] 0
+*[7] 1
+*[15] 2
+*[24] 3
+*[34] 4
+*[45] 5
+*[57] 6
++[70] 7
+ [84] 8
+ [99] 9
+ [115] 10
+ [122] 11
+ [130] 12
+ [139] 13
+ [149] 14
+ [160] bottom
+offsets: 0 7 15 24 34 45 57 70 84 99 115 122 130 139 149 160 172 185 199 214 230
+
+Scrolling to 11
+Creating element for 15
+Creating element for 16
+Creating element for 17
+Creating element for 18
+Creating element for 19
+----list[length=20][height=73]----
+ [-57] top
+ [-57] 0
+ [-50] 1
+ [-42] 2
+ [-33] 3
+ [-23] 4
+ [-12] 5
+*[0] 6
+*[13] 7
+*[27] 8
+*[42] 9
+*[58] 10
+*[65] 11
+ [73] 12
+ [82] 13
+ [92] 14
+ [103] 15
+ [115] 16
+ [128] 17
+ [142] 18
+ [157] 19
+ [173] bottom
+offsets: 0 7 15 24 34 45 57 70 84 99 115 122 130 139 149 160 172 185 199 214 230
+
+Scrolling to 19
+----list[length=20][height=73]----
+ [-157] top
+ [-73] 8
+ [-58] 9
+ [-42] 10
+ [-35] 11
+ [-27] 12
+ [-18] 13
++[-8] 14
+*[3] 15
+*[15] 16
+*[28] 17
+*[42] 18
+*[57] 19
+ [73] bottom
+offsets: 0 7 15 24 34 45 57 70 84 99 115 122 130 139 149 160 172 185 199 214 230
+
+Scrolling to 16
+----list[length=20][height=73]----
+ [-157] top
+ [-73] 8
+ [-58] 9
+ [-42] 10
+ [-35] 11
+ [-27] 12
+ [-18] 13
++[-8] 14
+*[3] 15
+*[15] 16
+*[28] 17
+*[42] 18
+*[57] 19
+ [73] bottom
+offsets: 0 7 15 24 34 45 57 70 84 99 115 122 130 139 149 160 172 185 199 214 230
+
+Scrolling to 3
+----list[length=20][height=73]----
+ [-24] top
+ [-24] 0
+ [-17] 1
+ [-9] 2
+*[0] 3
+*[10] 4
+*[21] 5
+*[33] 6
+*[46] 7
++[60] 8
+ [75] 9
+ [91] 10
+ [98] 11
+ [106] 12
+ [115] 13
+ [125] 14
+ [136] 15
+ [148] 16
+ [161] bottom
+offsets: 0 7 15 24 34 45 57 70 84 99 115 122 130 139 149 160 172 185 199 214 230
+
+Replacing 0, 1 with 25-36
+Creating element for 25
+Creating element for 26
+Creating element for 27
+Creating element for 28
+Creating element for 29
+Creating element for 30
+Creating element for 31
+Creating element for 32
+Creating element for 33
+Creating element for 34
+Creating element for 35
+Creating element for 36
+----list[length=30][height=73]----
+ [-24] top
+ [-24] 25
++[-12] 26
+*[1] 27
+*[15] 28
+*[30] 29
+*[46] 30
+*[53] 31
+*[61] 32
++[70] 33
+ [80] 34
+ [91] 35
+ [103] 36
+ [116] 2
+ [125] 3
+ [135] 4
+ [146] 5
+ [158] bottom
+offsets: 0 12 25 39 54 70 77 85 94 104 115 127 140 149 159 170 182 195 209 224 240 247 255 264 274 285 297 310 324 339 355 0 0 0 0 0 0 0 0 0 0 0
+
+Scrolling to 18
+----list[length=30][height=73]----
+ [-266] top
+ [-71] 7
+ [-57] 8
+ [-42] 9
+ [-26] 10
+ [-19] 11
+ [-11] 12
++[-2] 13
+*[8] 14
+*[19] 15
+*[31] 16
+*[44] 17
+*[58] 18
+ [73] 19
+ [89] bottom
+offsets: 0 12 25 39 54 70 77 85 94 104 115 127 140 149 159 170 182 195 209 224 240 247 255 264 274 285 297 310 324 339 355 0 0 0 0 0 0 0 0 0 0 0
+
+Replacing 25-36 with 0-1
+----list[length=20][height=73]----
+ [-141] top
+ [-71] 7
+ [-57] 8
+ [-42] 9
+ [-26] 10
+ [-19] 11
+ [-11] 12
++[-2] 13
+*[8] 14
+*[19] 15
+*[31] 16
+*[44] 17
+*[58] 18
+ [73] 19
+ [89] bottom
+offsets: 0 7 15 24 34 45 57 70 84 99 115 122 130 139 149 160 172 185 199 214 230
+
+Replacing 16-18 with 45
+Creating element for 45
+----list[length=18][height=73]----
+ [-127] top
+ [-70] 6
+ [-57] 7
+ [-43] 8
+ [-28] 9
+ [-12] 10
++[-5] 11
+*[3] 12
+*[12] 13
+*[22] 14
+*[33] 15
+*[45] 45
+*[57] 19
+ [73] bottom
+offsets: 0 7 15 24 34 45 57 70 84 99 115 122 130 139 149 160 172 184 200 214 230
+
+Scrolling to 4
+Creating element for 1
+Creating element for 0
+----list[length=18][height=73]----
+ [-34] top
+ [-34] 0
+ [-27] 1
+ [-19] 2
+ [-10] 3
+*[0] 4
+*[11] 5
+*[23] 6
+*[36] 7
+*[50] 8
++[65] 9
+ [81] 10
+ [88] 11
+ [96] 12
+ [105] 13
+ [115] 14
+ [126] 15
+ [138] 45
+ [150] 19
+ [166] bottom
+offsets: 0 7 15 24 34 45 57 70 84 99 115 122 130 139 149 160 172 184 200 214 230
+
+Replacing 45 with 16-18
+Creating element for 16
+Creating element for 17
+----list[length=20][height=73]----
+ [-34] top
+ [-34] 0
+ [-27] 1
+ [-19] 2
+ [-10] 3
+*[0] 4
+*[11] 5
+*[23] 6
+*[36] 7
+*[50] 8
++[65] 9
+ [81] 10
+ [88] 11
+ [96] 12
+ [105] 13
+ [115] 14
+ [126] 15
+ [138] 16
+ [151] 17
+ [165] bottom
+offsets: 0 7 15 24 34 45 57 70 84 99 115 122 130 139 149 160 172 185 199 214 230
+
+Resizing
+Creating element for 18
+----list[length=20][height=190]----
+ [-34] top
+ [-34] 0
+ [-27] 1
+ [-19] 2
+ [-10] 3
+*[0] 4
+*[11] 5
+*[23] 6
+*[36] 7
+*[50] 8
+*[65] 9
+*[81] 10
+*[88] 11
+*[96] 12
+*[105] 13
+*[115] 14
+*[126] 15
+*[138] 16
+*[151] 17
+*[165] 18
++[180] 19
+ [196] bottom
+offsets: 0 7 15 24 34 45 57 70 84 99 115 122 130 139 149 160 172 185 199 214 230
+
+
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-variable-height.js b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-variable-height.js
new file mode 100644
index 0000000..bc49549b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/list-control-variable-height.js
@@ -0,0 +1,116 @@
+TestRunner.addResult('Test ListControl rendering for variable height case.');
+
+class Delegate {
+  constructor() {
+  }
+
+  createElementForItem(item) {
+    TestRunner.addResult('Creating element for ' + item);
+    var element = document.createElement('div');
+    element.style.height = this.heightForItem(item) + 'px';
+    element.textContent = item;
+    return element;
+  }
+
+  heightForItem(item) {
+    return 7 + item % 10;
+  }
+
+  isItemSelectable(item) {
+    return (item % 5 === 0) || (item % 5 === 2);
+  }
+
+  selectedItemChanged(from, to, fromElement, toElement) {
+    TestRunner.addResult('Selection changed from ' + from + ' to ' + to);
+    if (fromElement)
+      fromElement.classList.remove('selected');
+    if (toElement)
+      toElement.classList.add('selected');
+  }
+}
+
+var delegate = new Delegate();
+var list = new UI.ListControl(delegate);
+list.setHeightMode(UI.ListHeightMode.Variable);
+list.element.style.height = '73px';
+UI.inspectorView.element.appendChild(list.element);
+
+function dumpList()
+{
+  var height = list.element.offsetHeight;
+  TestRunner.addResult(`----list[length=${list.length()}][height=${height}]----`);
+  for (var child of list.element.children) {
+    var offsetTop = child.getBoundingClientRect().top - list.element.getBoundingClientRect().top;
+    var offsetBottom = child.getBoundingClientRect().bottom - list.element.getBoundingClientRect().top;
+    var visible = (offsetBottom <= 0 || offsetTop >= height) ? ' ' :
+        (offsetTop >= 0 && offsetBottom <= height ? '*' : '+');
+    var selected = child.classList.contains('selected') ? ' (selected)' : '';
+    var text = child === list._topElement ? 'top' : (child === list._bottomElement ? 'bottom' : child.textContent);
+    TestRunner.addResult(`${visible}[${offsetTop}] ${text}${selected}`);
+  }
+  TestRunner.addResult('offsets: ' + list._variableOffsets.join(' '));
+  TestRunner.addResult('');
+}
+
+TestRunner.addResult('Adding 0, 1, 2');
+list.replaceAllItems([0, 1, 2]);
+dumpList();
+
+TestRunner.addResult('Scrolling to 0');
+list.scrollItemAtIndexIntoView(0);
+dumpList();
+
+TestRunner.addResult('Scrolling to 2');
+list.scrollItemAtIndexIntoView(2);
+dumpList();
+
+TestRunner.addResult('Adding 3-20');
+list.replaceItemsInRange(3, 3, [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]);
+dumpList();
+
+TestRunner.addResult('Scrolling to 11');
+list.scrollItemAtIndexIntoView(11);
+dumpList();
+
+TestRunner.addResult('Scrolling to 19');
+list.scrollItemAtIndexIntoView(19);
+dumpList();
+
+TestRunner.addResult('Scrolling to 16');
+list.scrollItemAtIndexIntoView(16);
+dumpList();
+
+TestRunner.addResult('Scrolling to 3');
+list.scrollItemAtIndexIntoView(3);
+dumpList();
+
+TestRunner.addResult('Replacing 0, 1 with 25-36');
+list.replaceItemsInRange(0, 2, [25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36]);
+dumpList();
+
+TestRunner.addResult('Scrolling to 18');
+list.scrollItemAtIndexIntoView(28);
+dumpList();
+
+TestRunner.addResult('Replacing 25-36 with 0-1');
+list.replaceItemsInRange(0, 12, [0, 1]);
+dumpList();
+
+TestRunner.addResult('Replacing 16-18 with 45');
+list.replaceItemsInRange(16, 19, [45]);
+dumpList();
+
+TestRunner.addResult('Scrolling to 4');
+list.scrollItemAtIndexIntoView(4);
+dumpList();
+
+TestRunner.addResult('Replacing 45 with 16-18');
+list.replaceItemsInRange(16, 17, [16, 17, 18]);
+dumpList();
+
+TestRunner.addResult('Resizing');
+list.element.style.height = '190px';
+list.viewportResized();
+dumpList();
+
+TestRunner.completeTest();
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/viewport-control-expected.txt b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/viewport-control-expected.txt
deleted file mode 100644
index cd00cc0..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/viewport-control-expected.txt
+++ /dev/null
@@ -1,30 +0,0 @@
-This tests if the ViewportControl works properly.
-First:0
-Last:6
-Active Items:11
-
-First:26
-Last:32
-Active Items:14
-
-First:27
-Last:33
-Active Items:15
-
-First:27
-Last:33
-Active Items:15
-
-First:30
-Last:36
-Active Items:14
-
-First:82
-Last:88
-Active Items:14
-
-First:83
-Last:88
-Active Items:11
-
-
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/viewport-control.js b/third_party/WebKit/LayoutTests/http/tests/inspector-unit/viewport-control.js
deleted file mode 100644
index 1a0b1fa..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/inspector-unit/viewport-control.js
+++ /dev/null
@@ -1,50 +0,0 @@
-TestRunner.addResult("This tests if the ViewportControl works properly.");
-
-var items = [];
-var heights = [];
-for (var i = 0; i < 100; i++){
-    items[i] = document.createElement("div");
-    items[i].style.height = (heights[i] = (i % 4) ? 50 : 28) + "px";
-    items[i].textContent = i;
-}
-var viewport = new UI.ViewportControl({
-    fastItemHeight: i => heights[i],
-    itemCount: _ => items.length,
-    itemElement: i => items[i]
-});
-viewport.element.style.height = "300px";
-UI.inspectorView.element.appendChild(viewport.element);
-
-viewport.refresh();
-dumpViewport();
-
-viewport.forceScrollItemToBeFirst(26);
-dumpViewport();
-
-viewport.scrollItemIntoView(33);
-dumpViewport();
-
-viewport.scrollItemIntoView(30);
-dumpViewport();
-
-viewport.forceScrollItemToBeFirst(30);
-dumpViewport();
-
-viewport.forceScrollItemToBeLast(88);
-dumpViewport();
-
-for (var i = 0; i < 100; i++)
-    items[i].style.height = (heights[i] = (i % 2) ? 55 : 63) + "px";
-viewport.refresh();
-viewport.forceScrollItemToBeLast(88);
-dumpViewport();
-
-TestRunner.completeTest();
-
-function dumpViewport()
-{
-    TestRunner.addResult("First:" + viewport.firstVisibleIndex());
-    TestRunner.addResult("Last:" + viewport.lastVisibleIndex());
-    TestRunner.addResult("Active Items:" + viewport._innerElement.children.length);
-    TestRunner.addResult("");
-}
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/inspector/layers/layer-tree-model-expected.txt b/third_party/WebKit/LayoutTests/inspector/layers/layer-tree-model-expected.txt
index 77d7187d..df0d5ed 100644
--- a/third_party/WebKit/LayoutTests/inspector/layers/layer-tree-model-expected.txt
+++ b/third_party/WebKit/LayoutTests/inspector/layers/layer-tree-model-expected.txt
@@ -26,7 +26,7 @@
         div#b2 110x140 (11)
             div#c 90x100 (12)
                 div#b1 100x150
-        div#b4 0x200
+        div#b4 0x0
 Updated layer geometry
 #document (9)
     iframe#frame (10)
@@ -40,5 +40,5 @@
         div#b2 110x140 (11)
             div#c 90x80 (12)
                 div#b1 100x150
-        div#b4 0x200
+        div#b4 0x0
 
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/compositing/should-not-repaint-composited-descendants-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/compositing/should-not-repaint-composited-descendants-expected.txt
index 14edfcd..ce5ebd7a 100644
--- a/third_party/WebKit/LayoutTests/paint/invalidation/compositing/should-not-repaint-composited-descendants-expected.txt
+++ b/third_party/WebKit/LayoutTests/paint/invalidation/compositing/should-not-repaint-composited-descendants-expected.txt
@@ -9,18 +9,14 @@
     {
       "name": "LayoutBlockFlow (positioned) DIV id='composited-box'",
       "position": [38, 38],
-      "bounds": [90, 90],
+      "bounds": [20, 70],
+      "contentsOpaque": true,
       "drawsContent": true,
       "backfaceVisibility": "hidden",
       "backgroundColor": "#008000",
       "paintInvalidations": [
         {
           "object": "LayoutBlockFlow (positioned) DIV id='composited-box'",
-          "rect": [0, 0, 70, 70],
-          "reason": "full"
-        },
-        {
-          "object": "LayoutBlockFlow (positioned) DIV id='composited-box'",
           "rect": [-30, -30, 50, 100],
           "reason": "subtree"
         },
@@ -28,6 +24,11 @@
           "object": "LayoutBlockFlow DIV class='child'",
           "rect": [-30, -30, 50, 50],
           "reason": "subtree"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='composited-box'",
+          "rect": [0, 0, 10, 60],
+          "reason": "full"
         }
       ]
     },
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/invalidate-cell-in-row-with-offset-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/invalidate-cell-in-row-with-offset-expected.txt
index 873a7ef..8643054e 100644
--- a/third_party/WebKit/LayoutTests/paint/invalidation/invalidate-cell-in-row-with-offset-expected.txt
+++ b/third_party/WebKit/LayoutTests/paint/invalidation/invalidate-cell-in-row-with-offset-expected.txt
@@ -9,7 +9,7 @@
     {
       "name": "LayoutTableRow TR",
       "position": [8, 18],
-      "bounds": [234, 0],
+      "transformOrigin": [117, 0],
       "drawsContent": true,
       "backgroundColor": "#0000FF"
     },
diff --git a/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
index d9674b2..93be7ce 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
@@ -38,7 +38,6 @@
     {
       "name": "LayoutInline (relative positioned) SPAN id='placeholder'",
       "position": [18, 0],
-      "bounds": [0, 19],
       "backfaceVisibility": "hidden"
     },
     {
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/spinvalidation/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/virtual/spinvalidation/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
new file mode 100644
index 0000000..93be7ce
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/spinvalidation/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
@@ -0,0 +1,80 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "drawsContent": true,
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) UL id='submenu'",
+          "rect": [48, 94, 40, 20],
+          "reason": "bounds change"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) UL id='submenu'",
+          "rect": [48, -156, 40, 20],
+          "reason": "bounds change"
+        },
+        {
+          "object": "LayoutListMarker (anonymous)",
+          "rect": [70, 94, 7, 19],
+          "reason": "bounds change"
+        },
+        {
+          "object": "LayoutListMarker (anonymous)",
+          "rect": [70, -156, 7, 19],
+          "reason": "bounds change"
+        }
+      ]
+    },
+    {
+      "name": "LayoutListItem (floating) LI id='watches'",
+      "position": [30, 44],
+      "transformOrigin": [18, 10],
+      "bounds": [25, 19],
+      "drawsContent": true
+    },
+    {
+      "name": "LayoutInline (relative positioned) SPAN id='placeholder'",
+      "position": [18, 0],
+      "backfaceVisibility": "hidden"
+    },
+    {
+      "name": "LayoutListItem (relative positioned) (floating) LI id='menu'",
+      "position": [30, 44],
+      "bounds": [18, 19],
+      "drawsContent": true,
+      "paintInvalidations": [
+        {
+          "object": "LayoutListMarker (anonymous)",
+          "rect": [0, 0, 7, 19],
+          "reason": "forced by layout"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutListMarker (anonymous)",
+      "reason": "forced by layout"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) UL id='submenu'",
+      "reason": "bounds change"
+    },
+    {
+      "object": "LayoutListItem LI",
+      "reason": "location change"
+    },
+    {
+      "object": "RootInlineBox",
+      "reason": "location change"
+    },
+    {
+      "object": "LayoutListMarker (anonymous)",
+      "reason": "bounds change"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
index 1cbc2db7..1b2ca7a 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
@@ -38,7 +38,6 @@
     {
       "name": "LayoutInline (relative positioned) SPAN id='placeholder'",
       "position": [17, 0],
-      "bounds": [0, 18],
       "backfaceVisibility": "hidden"
     },
     {
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/spinvalidation/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/virtual/spinvalidation/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
new file mode 100644
index 0000000..1b2ca7a
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/spinvalidation/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
@@ -0,0 +1,80 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "drawsContent": true,
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) UL id='submenu'",
+          "rect": [48, 92, 40, 18],
+          "reason": "bounds change"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) UL id='submenu'",
+          "rect": [48, -158, 40, 18],
+          "reason": "bounds change"
+        },
+        {
+          "object": "LayoutListMarker (anonymous)",
+          "rect": [71, 92, 7, 18],
+          "reason": "bounds change"
+        },
+        {
+          "object": "LayoutListMarker (anonymous)",
+          "rect": [71, -158, 7, 18],
+          "reason": "bounds change"
+        }
+      ]
+    },
+    {
+      "name": "LayoutListItem (floating) LI id='watches'",
+      "position": [31, 42],
+      "transformOrigin": [17, 9],
+      "bounds": [24, 18],
+      "drawsContent": true
+    },
+    {
+      "name": "LayoutInline (relative positioned) SPAN id='placeholder'",
+      "position": [17, 0],
+      "backfaceVisibility": "hidden"
+    },
+    {
+      "name": "LayoutListItem (relative positioned) (floating) LI id='menu'",
+      "position": [31, 42],
+      "bounds": [17, 18],
+      "drawsContent": true,
+      "paintInvalidations": [
+        {
+          "object": "LayoutListMarker (anonymous)",
+          "rect": [0, 0, 7, 18],
+          "reason": "forced by layout"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutListMarker (anonymous)",
+      "reason": "forced by layout"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) UL id='submenu'",
+      "reason": "bounds change"
+    },
+    {
+      "object": "LayoutListItem LI",
+      "reason": "location change"
+    },
+    {
+      "object": "RootInlineBox",
+      "reason": "location change"
+    },
+    {
+      "object": "LayoutListMarker (anonymous)",
+      "reason": "bounds change"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt b/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
index f87ab4c..b33d629 100644
--- a/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
@@ -38,7 +38,6 @@
     {
       "name": "LayoutInline (relative positioned) SPAN id='placeholder'",
       "position": [17, 0],
-      "bounds": [0, 17],
       "backfaceVisibility": "hidden"
     },
     {
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/spinvalidation/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt b/third_party/WebKit/LayoutTests/platform/win/virtual/spinvalidation/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
new file mode 100644
index 0000000..b33d629
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/spinvalidation/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
@@ -0,0 +1,80 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "drawsContent": true,
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) UL id='submenu'",
+          "rect": [48, 92, 40, 18],
+          "reason": "bounds change"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) UL id='submenu'",
+          "rect": [48, -158, 40, 18],
+          "reason": "bounds change"
+        },
+        {
+          "object": "LayoutListMarker (anonymous)",
+          "rect": [71, 92, 7, 17],
+          "reason": "bounds change"
+        },
+        {
+          "object": "LayoutListMarker (anonymous)",
+          "rect": [71, -158, 7, 17],
+          "reason": "bounds change"
+        }
+      ]
+    },
+    {
+      "name": "LayoutListItem (floating) LI id='watches'",
+      "position": [31, 42],
+      "transformOrigin": [17, 9],
+      "bounds": [24, 17],
+      "drawsContent": true
+    },
+    {
+      "name": "LayoutInline (relative positioned) SPAN id='placeholder'",
+      "position": [17, 0],
+      "backfaceVisibility": "hidden"
+    },
+    {
+      "name": "LayoutListItem (relative positioned) (floating) LI id='menu'",
+      "position": [31, 42],
+      "bounds": [17, 17],
+      "drawsContent": true,
+      "paintInvalidations": [
+        {
+          "object": "LayoutListMarker (anonymous)",
+          "rect": [0, 0, 7, 17],
+          "reason": "forced by layout"
+        }
+      ]
+    }
+  ],
+  "objectPaintInvalidations": [
+    {
+      "object": "LayoutListMarker (anonymous)",
+      "reason": "forced by layout"
+    },
+    {
+      "object": "LayoutBlockFlow (positioned) UL id='submenu'",
+      "reason": "bounds change"
+    },
+    {
+      "object": "LayoutListItem LI",
+      "reason": "location change"
+    },
+    {
+      "object": "RootInlineBox",
+      "reason": "location change"
+    },
+    {
+      "object": "LayoutListMarker (anonymous)",
+      "reason": "bounds change"
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/virtual/prefer_compositing_to_lcd_text/compositing/overflow/reparented-scrollbars-non-sc-anc-expected.txt b/third_party/WebKit/LayoutTests/virtual/prefer_compositing_to_lcd_text/compositing/overflow/reparented-scrollbars-non-sc-anc-expected.txt
index 66e0b689..d178fd3 100644
--- a/third_party/WebKit/LayoutTests/virtual/prefer_compositing_to_lcd_text/compositing/overflow/reparented-scrollbars-non-sc-anc-expected.txt
+++ b/third_party/WebKit/LayoutTests/virtual/prefer_compositing_to_lcd_text/compositing/overflow/reparented-scrollbars-non-sc-anc-expected.txt
@@ -45,7 +45,7 @@
     {
       "name": "LayoutBlockFlow (relative positioned) DIV id='foreground'",
       "position": [8, 10008],
-      "bounds": [1200, 0]
+      "contentsOpaque": true
     },
     {
       "name": "Overflow Controls Ancestor Clipping Layer",
diff --git a/third_party/WebKit/Source/core/layout/TextAutosizer.cpp b/third_party/WebKit/Source/core/layout/TextAutosizer.cpp
index e625ff7e..e3aa46b 100644
--- a/third_party/WebKit/Source/core/layout/TextAutosizer.cpp
+++ b/third_party/WebKit/Source/core/layout/TextAutosizer.cpp
@@ -1399,7 +1399,7 @@
 }
 
 void TextAutosizer::checkSuperclusterConsistency() {
-  HashSet<Supercluster*> potentiallyInconsistentSuperclusters =
+  HashSet<Supercluster*>& potentiallyInconsistentSuperclusters =
       m_fingerprintMapper.getPotentiallyInconsistentSuperclusters();
   if (potentiallyInconsistentSuperclusters.isEmpty())
     return;
diff --git a/third_party/WebKit/Source/core/layout/VisualRectMappingTest.cpp b/third_party/WebKit/Source/core/layout/VisualRectMappingTest.cpp
index c19c7e6..d305494 100644
--- a/third_party/WebKit/Source/core/layout/VisualRectMappingTest.cpp
+++ b/third_party/WebKit/Source/core/layout/VisualRectMappingTest.cpp
@@ -33,7 +33,7 @@
       PaintLayer::mapRectInPaintInvalidationContainerToBacking(
           paintInvalidationContainer, rect);
     }
-    EXPECT_EQ(rect, object.visualRect());
+    EXPECT_EQ(enclosingIntRect(rect), enclosingIntRect(object.visualRect()));
   }
 };
 
diff --git a/third_party/WebKit/Source/core/loader/FrameLoader.cpp b/third_party/WebKit/Source/core/loader/FrameLoader.cpp
index d927b74..e330262 100644
--- a/third_party/WebKit/Source/core/loader/FrameLoader.cpp
+++ b/third_party/WebKit/Source/core/loader/FrameLoader.cpp
@@ -1633,6 +1633,8 @@
     // Mark the frame as loading since the embedder is handling the navigation.
     m_progressTracker->progressStarted();
 
+    m_frame->navigationScheduler().cancel();
+
     // If this is a form submit, dispatch that a form is being submitted
     // since the embedder is handling the navigation.
     if (form)
diff --git a/third_party/WebKit/Source/core/paint/PaintLayer.cpp b/third_party/WebKit/Source/core/paint/PaintLayer.cpp
index a04ce99..93800eb5 100644
--- a/third_party/WebKit/Source/core/paint/PaintLayer.cpp
+++ b/third_party/WebKit/Source/core/paint/PaintLayer.cpp
@@ -2536,13 +2536,10 @@
   const_cast<PaintLayer*>(this)->stackingNode()->updateLayerListsIfNeeded();
 
   // If there is a clip applied by an ancestor to this PaintLayer but below or
-  // equal to |ancestorLayer|, use that clip as the bounds rather than the
-  // recursive bounding boxes, since the latter may be larger than the actual
-  // size. See https://bugs.webkit.org/show_bug.cgi?id=80372 for examples.
+  // equal to |ancestorLayer|, apply that clip.
   LayoutRect result = clipper().localClipRect(compositedLayer);
-  // TODO(chrishtr): avoid converting to IntRect and back.
-  if (result == LayoutRect(LayoutRect::infiniteIntRect()))
-    result = physicalBoundingBox(LayoutPoint());
+
+  result.intersect(physicalBoundingBox(LayoutPoint()));
 
   expandRectForStackingChildren(compositedLayer, result, options);
 
diff --git a/third_party/WebKit/Source/core/paint/PaintLayerScrollableArea.cpp b/third_party/WebKit/Source/core/paint/PaintLayerScrollableArea.cpp
index 02acdf8..7a7d3894 100644
--- a/third_party/WebKit/Source/core/paint/PaintLayerScrollableArea.cpp
+++ b/third_party/WebKit/Source/core/paint/PaintLayerScrollableArea.cpp
@@ -1762,7 +1762,8 @@
   // with the results.
   return !(layer->size().isEmpty() || layer->hasDescendantWithClipPath() ||
            layer->hasAncestorWithClipPath() ||
-           layer->layoutObject()->style()->hasBorderRadius());
+           layer->layoutObject()->style()->hasBorderRadius() ||
+           layer->layoutObject()->hasClip());
 }
 
 void PaintLayerScrollableArea::addStyleRelatedMainThreadScrollingReasons(
diff --git a/third_party/WebKit/Source/core/paint/PaintLayerScrollableAreaTest.cpp b/third_party/WebKit/Source/core/paint/PaintLayerScrollableAreaTest.cpp
index fe83637..e8cf030 100644
--- a/third_party/WebKit/Source/core/paint/PaintLayerScrollableAreaTest.cpp
+++ b/third_party/WebKit/Source/core/paint/PaintLayerScrollableAreaTest.cpp
@@ -462,4 +462,40 @@
   ASSERT_EQ(ScrollbarOverlayColorTheme::ScrollbarOverlayColorThemeLight,
             blackLayer->getScrollableArea()->getScrollbarOverlayColorTheme());
 }
+
+// Test that css clip applied to the scroller will cause the
+// scrolling contents layer to not be promoted.
+TEST_F(PaintLayerScrollableAreaTest,
+       OnlyAutoClippedScrollingContentsLayerPromoted) {
+  setBodyInnerHTML(
+      "<style>"
+      ".clip { clip: rect(0px,60px,50px,0px); }"
+      "#scroller { position: absolute; overflow: auto;"
+      "height: 100px; width: 100px; background: grey;"
+      "will-change:transform; }"
+      "#scrolled { height: 300px; }"
+      "</style>"
+      "<div id=\"scroller\"><div id=\"scrolled\"></div></div>");
+  document().view()->updateAllLifecyclePhases();
+
+  Element* scroller = document().getElementById("scroller");
+  PaintLayer* paintLayer =
+      toLayoutBoxModelObject(scroller->layoutObject())->layer();
+  ASSERT_TRUE(paintLayer);
+  EXPECT_TRUE(paintLayer->needsCompositedScrolling());
+
+  // Add clip to scroller.
+  scroller->setAttribute("class", "clip", ASSERT_NO_EXCEPTION);
+  document().view()->updateAllLifecyclePhases();
+  paintLayer = toLayoutBoxModelObject(scroller->layoutObject())->layer();
+  ASSERT_TRUE(paintLayer);
+  EXPECT_FALSE(paintLayer->needsCompositedScrolling());
+
+  // Change the scroller to be auto clipped again.
+  scroller->removeAttribute("class");
+  document().view()->updateAllLifecyclePhases();
+  paintLayer = toLayoutBoxModelObject(scroller->layoutObject())->layer();
+  ASSERT_TRUE(paintLayer);
+  EXPECT_TRUE(paintLayer->needsCompositedScrolling());
+}
 }
diff --git a/third_party/WebKit/Source/core/paint/PaintLayerTest.cpp b/third_party/WebKit/Source/core/paint/PaintLayerTest.cpp
index 0c5802f..5804a8c6 100644
--- a/third_party/WebKit/Source/core/paint/PaintLayerTest.cpp
+++ b/third_party/WebKit/Source/core/paint/PaintLayerTest.cpp
@@ -58,6 +58,26 @@
             parentLayer->boundingBoxForCompositing());
 }
 
+TEST_P(PaintLayerTest, CompositedBoundsTransformedChild) {
+  // TODO(chrishtr): fix this test for SPv2
+  if (RuntimeEnabledFeatures::slimmingPaintV2Enabled())
+    return;
+
+  setBodyInnerHTML(
+      "<div id=parent style='overflow: scroll; will-change: transform'>"
+      "  <div class='target'"
+      "       style='position: relative; transform: skew(-15deg);'>"
+      "  </div>"
+      "  <div style='width: 1000px; height: 500px; background: lightgray'>"
+      "  </div>"
+      "</div>");
+
+  PaintLayer* parentLayer =
+      toLayoutBoxModelObject(getLayoutObjectByElementId("parent"))->layer();
+  EXPECT_EQ(LayoutRect(0, 0, 784, 500),
+            parentLayer->boundingBoxForCompositing());
+}
+
 TEST_P(PaintLayerTest, PaintingExtentReflection) {
   setBodyInnerHTML(
       "<div id='target' style='background-color: blue; position: absolute;"
diff --git a/third_party/WebKit/Source/devtools/BUILD.gn b/third_party/WebKit/Source/devtools/BUILD.gn
index 49a500d..cf7a795 100644
--- a/third_party/WebKit/Source/devtools/BUILD.gn
+++ b/third_party/WebKit/Source/devtools/BUILD.gn
@@ -600,6 +600,7 @@
   "front_end/ui/InspectorView.js",
   "front_end/ui/inspectorViewTabbedPane.css",
   "front_end/ui/KeyboardShortcut.js",
+  "front_end/ui/ListControl.js",
   "front_end/ui/listWidget.css",
   "front_end/ui/ListWidget.js",
   "front_end/ui/module.json",
@@ -645,7 +646,6 @@
   "front_end/ui/UIUtils.js",
   "front_end/ui/View.js",
   "front_end/ui/viewContainers.css",
-  "front_end/ui/ViewportControl.js",
   "front_end/ui/Widget.js",
   "front_end/ui/ZoomManager.js",
   "front_end/unit_test_runner.js",
diff --git a/third_party/WebKit/Source/devtools/front_end/externs.js b/third_party/WebKit/Source/devtools/front_end/externs.js
index eb14878..636f486f 100644
--- a/third_party/WebKit/Source/devtools/front_end/externs.js
+++ b/third_party/WebKit/Source/devtools/front_end/externs.js
@@ -91,19 +91,23 @@
 /**
  * @param {!S} object
  * @param {function(!S,!T):number=} comparator
+ * @param {number=} left
+ * @param {number=} right
  * @return {number}
  * @this {Array.<T>}
  * @template S
  */
-Array.prototype.lowerBound = function(object, comparator) {};
+Array.prototype.lowerBound = function(object, comparator, left, right) {};
 /**
  * @param {!S} object
  * @param {function(!S,!T):number=} comparator
+ * @param {number=} left
+ * @param {number=} right
  * @return {number}
  * @this {Array.<T>}
  * @template S
  */
-Array.prototype.upperBound = function(object, comparator) {};
+Array.prototype.upperBound = function(object, comparator, left, right) {};
 /**
  * @param {!S} value
  * @param {function(!S,!T):number} comparator
@@ -182,6 +186,15 @@
  */
 Array.prototype.mergeOrdered = function(array, comparator) {};
 
+/**
+ * @param {number} object
+ * @param {function(number, number):number=} comparator
+ * @param {number=} left
+ * @param {number=} right
+ * @return {number}
+ */
+Int32Array.prototype.lowerBound = function(object, comparator, left, right) {};
+
 // File System API
 /**
  * @constructor
diff --git a/third_party/WebKit/Source/devtools/front_end/platform/utilities.js b/third_party/WebKit/Source/devtools/front_end/platform/utilities.js
index 23ed78c..1edf9b9 100644
--- a/third_party/WebKit/Source/devtools/front_end/platform/utilities.js
+++ b/third_party/WebKit/Source/devtools/front_end/platform/utilities.js
@@ -718,6 +718,10 @@
 
 Object.defineProperty(Uint32Array.prototype, 'upperBound', {value: Array.prototype.upperBound});
 
+Object.defineProperty(Int32Array.prototype, 'lowerBound', {value: Array.prototype.lowerBound});
+
+Object.defineProperty(Int32Array.prototype, 'upperBound', {value: Array.prototype.upperBound});
+
 Object.defineProperty(Float64Array.prototype, 'lowerBound', {value: Array.prototype.lowerBound});
 
 Object.defineProperty(Array.prototype, 'binaryIndexOf', {
diff --git a/third_party/WebKit/Source/devtools/front_end/sources/StyleSheetOutlineDialog.js b/third_party/WebKit/Source/devtools/front_end/sources/StyleSheetOutlineDialog.js
index c8b9802d..803b687 100644
--- a/third_party/WebKit/Source/devtools/front_end/sources/StyleSheetOutlineDialog.js
+++ b/third_party/WebKit/Source/devtools/front_end/sources/StyleSheetOutlineDialog.js
@@ -100,10 +100,12 @@
 
   /**
    * @override
-   * @param {number} itemIndex
+   * @param {?number} itemIndex
    * @param {string} promptValue
    */
   selectItem(itemIndex, promptValue) {
+    if (itemIndex === null)
+      return;
     var rule = this._rules[itemIndex];
     var lineNumber = rule.lineNumber;
     if (!isNaN(lineNumber) && lineNumber >= 0)
diff --git a/third_party/WebKit/Source/devtools/front_end/test_runner/TestRunner.js b/third_party/WebKit/Source/devtools/front_end/test_runner/TestRunner.js
index a69ae52..bf1dff2 100644
--- a/third_party/WebKit/Source/devtools/front_end/test_runner/TestRunner.js
+++ b/third_party/WebKit/Source/devtools/front_end/test_runner/TestRunner.js
@@ -118,10 +118,10 @@
 
 /**
  * @param {string} key
- * @param {boolean} ctrlKey
- * @param {boolean} altKey
- * @param {boolean} shiftKey
- * @param {boolean} metaKey
+ * @param {boolean=} ctrlKey
+ * @param {boolean=} altKey
+ * @param {boolean=} shiftKey
+ * @param {boolean=} metaKey
  * @return {!KeyboardEvent}
  */
 TestRunner.createKeyEvent = function(key, ctrlKey, altKey, shiftKey, metaKey) {
@@ -129,10 +129,10 @@
     key: key,
     bubbles: true,
     cancelable: true,
-    ctrlKey: ctrlKey,
-    altKey: altKey,
-    shiftKey: shiftKey,
-    metaKey: metaKey
+    ctrlKey: !!ctrlKey,
+    altKey: !!altKey,
+    shiftKey: !!shiftKey,
+    metaKey: !!metaKey
   });
 };
 
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/ListControl.js b/third_party/WebKit/Source/devtools/front_end/ui/ListControl.js
new file mode 100644
index 0000000..a76022f
--- /dev/null
+++ b/third_party/WebKit/Source/devtools/front_end/ui/ListControl.js
@@ -0,0 +1,557 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @template T
+ * @interface
+ */
+UI.ListDelegate = function() {};
+
+UI.ListDelegate.prototype = {
+  /**
+   * @param {T} item
+   * @return {!Element}
+   */
+  createElementForItem(item) {},
+
+  /**
+   * @param {T} item
+   * @return {number}
+   */
+  heightForItem(item) {},
+
+  /**
+   * @param {T} item
+   * @return {boolean}
+   */
+  isItemSelectable(item) {},
+
+  /**
+   * @param {?T} from
+   * @param {?T} to
+   * @param {?Element} fromElement
+   * @param {?Element} toElement
+   */
+  selectedItemChanged(from, to, fromElement, toElement) {},
+};
+
+/** @enum {symbol} */
+UI.ListHeightMode = {
+  Fixed: Symbol('UI.ListHeightMode.Fixed'),
+  Measured: Symbol('UI.ListHeightMode.Measured'),
+  Variable: Symbol('UI.ListHeightMode.Variable')
+};
+
+/**
+ * @template T
+ */
+UI.ListControl = class {
+  /**
+   * @param {!UI.ListDelegate<T>} delegate
+   */
+  constructor(delegate) {
+    this.element = createElement('div');
+    this.element.style.overflow = 'auto';
+    this._topElement = this.element.createChild('div');
+    this._bottomElement = this.element.createChild('div');
+    this._firstIndex = 0;
+    this._lastIndex = 0;
+    this._renderedHeight = 0;
+    this._topHeight = 0;
+    this._bottomHeight = 0;
+    this._clearViewport();
+
+    /** @type {!Array<T>} */
+    this._items = [];
+    /** @type {!Map<T, !Element>} */
+    this._itemToElement = new Map();
+    this._selectedIndex = -1;
+
+    this._boundKeyDown = event => {
+      if (this.onKeyDown(event))
+        event.consume(true);
+    };
+    this._boundClick = event => {
+      if (this.onClick(event))
+        event.consume(true);
+    };
+
+    this._delegate = delegate;
+    this._heightMode = UI.ListHeightMode.Measured;
+    this._fixedHeight = 0;
+    this._variableOffsets = new Int32Array(0);
+
+    this.element.addEventListener('scroll', this._onScroll.bind(this), false);
+  }
+
+  /**
+   * @param {!UI.ListHeightMode} mode
+   */
+  setHeightMode(mode) {
+    this._heightMode = mode;
+    this._fixedHeight = 0;
+    if (this._items.length) {
+      this._itemToElement.clear();
+      this._invalidate(0, this._items.length, this._items.length);
+    }
+  }
+
+  /**
+   * @param {boolean} handleInput
+   */
+  setHandleInput(handleInput) {
+    if (handleInput) {
+      this.element.addEventListener('keydown', this._boundKeyDown, false);
+      this.element.addEventListener('click', this._boundClick, false);
+    } else {
+      this.element.removeEventListener('keydown', this._boundKeyDown, false);
+      this.element.removeEventListener('click', this._boundClick, false);
+    }
+  }
+
+  /**
+   * @return {number}
+   */
+  length() {
+    return this._items.length;
+  }
+
+  /**
+   * @param {number} index
+   * @return {T}
+   */
+  itemAtIndex(index) {
+    return this._items[index];
+  }
+
+  /**
+   * @param {T} item
+   */
+  pushItem(item) {
+    this.replaceItemsInRange(this._items.length, this._items.length, [item]);
+  }
+
+  /**
+   * @return {T}
+   */
+  popItem() {
+    return this.removeItemAtIndex(this._items.length - 1);
+  }
+
+  /**
+   * @param {number} index
+   * @param {T} item
+   */
+  insertItemAtIndex(index, item) {
+    this.replaceItemsInRange(index, index, [item]);
+  }
+
+  /**
+   * @param {number} index
+   * @return {T}
+   */
+  removeItemAtIndex(index) {
+    var result = this._items[index];
+    this.replaceItemsInRange(index, index + 1, []);
+    return result;
+  }
+
+  /**
+   * @param {number} from
+   * @param {number} to
+   * @param {!Array<T>} items
+   */
+  replaceItemsInRange(from, to, items) {
+    var oldSelectedItem = this._selectedIndex !== -1 ? this._items[this._selectedIndex] : null;
+    var oldSelectedElement = oldSelectedItem ? (this._itemToElement.get(oldSelectedItem) || null) : null;
+
+    for (var i = from; i < to; i++)
+      this._itemToElement.delete(this._items[i]);
+    if (items.length < 10000) {
+      this._items.splice.bind(this._items, from, to - from).apply(null, items);
+    } else {
+      // Splice may fail with too many arguments.
+      var before = this._items.slice(0, from);
+      var after = this._items.slice(to);
+      this._items = [].concat(before, items, after);
+    }
+    this._invalidate(from, to, items.length);
+
+    if (this._selectedIndex >= to) {
+      this._selectedIndex += items.length - (to - from);
+    } else if (this._selectedIndex >= from) {
+      var index = this._findClosestSelectable(from + items.length, +1, 0, false);
+      if (index === -1)
+        index = this._findClosestSelectable(from - 1, -1, 0, false);
+      this._select(index, oldSelectedItem, oldSelectedElement);
+    }
+  }
+
+  /**
+   * @param {!Array<T>} items
+   */
+  replaceAllItems(items) {
+    this.replaceItemsInRange(0, this._items.length, items);
+  }
+
+  /**
+   * @param {number} from
+   * @param {number} to
+   */
+  invalidateRange(from, to) {
+    this._invalidate(from, to, to - from);
+  }
+
+  viewportResized() {
+    // TODO(dgozman): try to keep the visible scrollTop the same
+    // when invalidating after firstIndex but before first visible element.
+    var scrollTop = this.element.scrollTop;
+    var viewportHeight = this.element.offsetHeight;
+    this._clearViewport();
+    this._updateViewport(Number.constrain(scrollTop, 0, this._totalHeight() - viewportHeight), viewportHeight);
+  }
+
+  /**
+   * @param {number} index
+   */
+  scrollItemAtIndexIntoView(index) {
+    var top = this._offsetAtIndex(index);
+    var bottom = this._offsetAtIndex(index + 1);
+    var scrollTop = this.element.scrollTop;
+    var viewportHeight = this.element.offsetHeight;
+    if (top < scrollTop)
+      this._updateViewport(top, viewportHeight);
+    else if (bottom > scrollTop + viewportHeight)
+      this._updateViewport(bottom - viewportHeight, viewportHeight);
+  }
+
+  /**
+   * @param {number} index
+   * @param {boolean=} scrollIntoView
+   */
+  selectItemAtIndex(index, scrollIntoView) {
+    if (index !== -1 && !this._delegate.isItemSelectable(this._items[index]))
+      throw 'Attempt to select non-selectable item';
+    this._select(index);
+    if (index !== -1 && !!scrollIntoView)
+      this.scrollItemAtIndexIntoView(index);
+  }
+
+  /**
+   * @return {number}
+   */
+  selectedIndex() {
+    return this._selectedIndex;
+  }
+
+  /**
+   * @return {?T}
+   */
+  selectedItem() {
+    return this._selectedIndex === -1 ? null : this._items[this._selectedIndex];
+  }
+
+  /**
+   * @param {!Event} event
+   * @return {boolean}
+   */
+  onKeyDown(event) {
+    var index = -1;
+    switch (event.key) {
+      case 'ArrowUp':
+        index = this._selectedIndex === -1 ? this._findClosestSelectable(this._items.length - 1, -1, 0, true) :
+                                             this._findClosestSelectable(this._selectedIndex, -1, 1, true);
+        break;
+      case 'ArrowDown':
+        index = this._selectedIndex === -1 ? this._findClosestSelectable(0, +1, 0, true) :
+                                             this._findClosestSelectable(this._selectedIndex, +1, 1, true);
+        break;
+      case 'PageUp':
+        index = this._selectedIndex === -1 ? this._items.length - 1 : this._selectedIndex;
+        // Compensate for zoom rounding errors with -1.
+        index = this._findClosestSelectable(index, -1, this.element.offsetHeight - 1, false);
+        break;
+      case 'PageDown':
+        index = this._selectedIndex === -1 ? 0 : this._selectedIndex;
+        // Compensate for zoom rounding errors with -1.
+        index = this._findClosestSelectable(index, +1, this.element.offsetHeight - 1, false);
+        break;
+      default:
+        return false;
+    }
+    if (index !== -1) {
+      this.scrollItemAtIndexIntoView(index);
+      this._select(index);
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * @param {!Event} event
+   * @return {boolean}
+   */
+  onClick(event) {
+    var node = event.target;
+    while (node && node.parentNodeOrShadowHost() !== this.element)
+      node = node.parentNodeOrShadowHost();
+    if (!node || node.nodeType !== Node.ELEMENT_NODE)
+      return false;
+    var offset = /** @type {!Element} */ (node).getBoundingClientRect().top;
+    offset -= this.element.getBoundingClientRect().top;
+    var index = this._indexAtOffset(offset + this.element.scrollTop);
+    if (index === -1 || !this._delegate.isItemSelectable(this._items[index]))
+      return false;
+    this._select(index);
+    return true;
+  }
+
+  /**
+   * @return {number}
+   */
+  _totalHeight() {
+    return this._offsetAtIndex(this._items.length);
+  }
+
+  /**
+   * @param {number} offset
+   * @return {number}
+   */
+  _indexAtOffset(offset) {
+    if (!this._items.length || offset < 0)
+      return 0;
+    if (this._heightMode === UI.ListHeightMode.Variable) {
+      return Math.min(
+          this._items.length - 1, this._variableOffsets.lowerBound(offset, undefined, 0, this._items.length));
+    }
+    if (!this._fixedHeight)
+      this._measureHeight();
+    return Math.min(this._items.length - 1, Math.floor(offset / this._fixedHeight));
+  }
+
+  /**
+   * @param {number} index
+   * @return {!Element}
+   */
+  _elementAtIndex(index) {
+    var item = this._items[index];
+    var element = this._itemToElement.get(item);
+    if (!element) {
+      element = this._delegate.createElementForItem(item);
+      this._itemToElement.set(item, element);
+    }
+    return element;
+  }
+
+  /**
+   * @param {number} index
+   * @return {number}
+   */
+  _offsetAtIndex(index) {
+    if (!this._items.length)
+      return 0;
+    if (this._heightMode === UI.ListHeightMode.Variable)
+      return this._variableOffsets[index];
+    if (!this._fixedHeight)
+      this._measureHeight();
+    return index * this._fixedHeight;
+  }
+
+  _measureHeight() {
+    if (this._heightMode === UI.ListHeightMode.Measured)
+      this._fixedHeight = UI.measurePreferredSize(this._elementAtIndex(0), this.element).height;
+    else
+      this._fixedHeight = this._delegate.heightForItem(this._items[0]);
+  }
+
+  /**
+   * @param {number} index
+   * @param {?T=} oldItem
+   * @param {?Element=} oldElement
+   */
+  _select(index, oldItem, oldElement) {
+    if (oldItem === undefined)
+      oldItem = this._selectedIndex !== -1 ? this._items[this._selectedIndex] : null;
+    if (oldElement === undefined)
+      oldElement = this._itemToElement.get(oldItem) || null;
+    this._selectedIndex = index;
+    var newItem = this._selectedIndex !== -1 ? this._items[this._selectedIndex] : null;
+    var newElement = this._itemToElement.get(newItem) || null;
+    this._delegate.selectedItemChanged(oldItem, newItem, /** @type {?Element} */ (oldElement), newElement);
+  }
+
+  /**
+   * @param {number} index
+   * @param {number} direction
+   * @param {number} minSkippedHeight
+   * @param {boolean} canWrap
+   * @return {number}
+   */
+  _findClosestSelectable(index, direction, minSkippedHeight, canWrap) {
+    var length = this._items.length;
+    if (!length)
+      return -1;
+
+    var lastSelectable = -1;
+    var start = -1;
+    var startOffset = this._offsetAtIndex(index);
+    while (true) {
+      if (index < 0 || index >= length) {
+        if (!canWrap)
+          return lastSelectable;
+        index = (index + length) % length;
+      }
+
+      // Handle full wrap-around.
+      if (index === start)
+        return lastSelectable;
+      if (start === -1) {
+        start = index;
+        startOffset = this._offsetAtIndex(index);
+      }
+
+      if (this._delegate.isItemSelectable(this._items[index])) {
+        if (Math.abs(this._offsetAtIndex(index) - startOffset) >= minSkippedHeight)
+          return index;
+        lastSelectable = index;
+      }
+
+      index += direction;
+    }
+  }
+
+  /**
+   * @param {number} length
+   * @param {number} copyTo
+   */
+  _reallocateVariableOffsets(length, copyTo) {
+    if (this._variableOffsets.length < length) {
+      var variableOffsets = new Int32Array(Math.max(length, this._variableOffsets.length * 2));
+      variableOffsets.set(this._variableOffsets.slice(0, copyTo), 0);
+      this._variableOffsets = variableOffsets;
+    } else if (this._variableOffsets.length >= 2 * length) {
+      var variableOffsets = new Int32Array(length);
+      variableOffsets.set(this._variableOffsets.slice(0, copyTo), 0);
+      this._variableOffsets = variableOffsets;
+    }
+  }
+
+  /**
+   * @param {number} from
+   * @param {number} to
+   * @param {number} inserted
+   */
+  _invalidate(from, to, inserted) {
+    if (this._heightMode === UI.ListHeightMode.Variable) {
+      this._reallocateVariableOffsets(this._items.length + 1, from + 1);
+      for (var i = from + 1; i <= this._items.length; i++)
+        this._variableOffsets[i] = this._variableOffsets[i - 1] + this._delegate.heightForItem(this._items[i - 1]);
+    }
+
+    var viewportHeight = this.element.offsetHeight;
+    var totalHeight = this._totalHeight();
+    var scrollTop = this.element.scrollTop;
+
+    if (this._renderedHeight < viewportHeight || totalHeight < viewportHeight) {
+      this._clearViewport();
+      this._updateViewport(Number.constrain(scrollTop, 0, totalHeight - viewportHeight), viewportHeight);
+      return;
+    }
+
+    var heightDelta = totalHeight - this._renderedHeight;
+    if (to <= this._firstIndex) {
+      var topHeight = this._topHeight + heightDelta;
+      this._topElement.style.height = topHeight + 'px';
+      this.element.scrollTop = scrollTop + heightDelta;
+      this._topHeight = topHeight;
+      this._renderedHeight = totalHeight;
+      var indexDelta = inserted - (to - from);
+      this._firstIndex += indexDelta;
+      this._lastIndex += indexDelta;
+      return;
+    }
+
+    if (from >= this._lastIndex) {
+      var bottomHeight = this._bottomHeight + heightDelta;
+      this._bottomElement.style.height = bottomHeight + 'px';
+      this._bottomHeight = bottomHeight;
+      this._renderedHeight = totalHeight;
+      return;
+    }
+
+    // TODO(dgozman): try to keep the visible scrollTop the same
+    // when invalidating after firstIndex but before first visible element.
+    this._clearViewport();
+    this._updateViewport(Number.constrain(scrollTop, 0, totalHeight - viewportHeight), viewportHeight);
+  }
+
+  _clearViewport() {
+    this._firstIndex = 0;
+    this._lastIndex = 0;
+    this._renderedHeight = 0;
+    this._topHeight = 0;
+    this._bottomHeight = 0;
+    this._topElement.style.height = '0';
+    this._bottomElement.style.height = '0';
+    this.element.removeChildren();
+    this.element.appendChild(this._topElement);
+    this.element.appendChild(this._bottomElement);
+  }
+
+  _onScroll() {
+    this._updateViewport(this.element.scrollTop, this.element.offsetHeight);
+  }
+
+  /**
+   * @param {number} scrollTop
+   * @param {number} viewportHeight
+   */
+  _updateViewport(scrollTop, viewportHeight) {
+    // Note: this method should not force layout. Be careful.
+
+    var totalHeight = this._totalHeight();
+    if (!totalHeight) {
+      this._firstIndex = 0;
+      this._lastIndex = 0;
+      this._topHeight = 0;
+      this._bottomHeight = 0;
+      this._renderedHeight = 0;
+      this._topElement.style.height = '0';
+      this._bottomElement.style.height = '0';
+      return;
+    }
+
+    var firstIndex = this._indexAtOffset(scrollTop - viewportHeight);
+    var lastIndex = this._indexAtOffset(scrollTop + 2 * viewportHeight) + 1;
+
+    while (this._firstIndex < Math.min(firstIndex, this._lastIndex)) {
+      this._elementAtIndex(this._firstIndex).remove();
+      this._firstIndex++;
+    }
+    while (this._lastIndex > Math.max(lastIndex, this._firstIndex)) {
+      this._elementAtIndex(this._lastIndex - 1).remove();
+      this._lastIndex--;
+    }
+
+    this._firstIndex = Math.min(this._firstIndex, lastIndex);
+    this._lastIndex = Math.max(this._lastIndex, firstIndex);
+    for (var index = this._firstIndex - 1; index >= firstIndex; index--) {
+      var element = this._elementAtIndex(index);
+      this.element.insertBefore(element, this._topElement.nextSibling);
+    }
+    for (var index = this._lastIndex; index < lastIndex; index++) {
+      var element = this._elementAtIndex(index);
+      this.element.insertBefore(element, this._bottomElement);
+    }
+
+    this._firstIndex = firstIndex;
+    this._lastIndex = lastIndex;
+    this._topHeight = this._offsetAtIndex(firstIndex);
+    this._topElement.style.height = this._topHeight + 'px';
+    this._bottomHeight = totalHeight - this._offsetAtIndex(lastIndex);
+    this._bottomElement.style.height = this._bottomHeight + 'px';
+    this._renderedHeight = totalHeight;
+    this.element.scrollTop = scrollTop;
+  }
+};
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js b/third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js
index 1b20de2..2492af9 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui/SuggestBox.js
@@ -46,8 +46,8 @@
 };
 
 /**
- * @implements {UI.ViewportControl.Provider}
  * @unrestricted
+ * @implements {UI.ListDelegate}
  */
 UI.SuggestBox = class {
   /**
@@ -57,32 +57,26 @@
    */
   constructor(suggestBoxDelegate, maxItemsHeight, captureEnter) {
     this._suggestBoxDelegate = suggestBoxDelegate;
-    this._length = 0;
-    this._selectedIndex = -1;
-    this._selectedElement = null;
     this._maxItemsHeight = maxItemsHeight;
     this._maybeHideBound = this._maybeHide.bind(this);
     this._container = createElementWithClass('div', 'suggest-box-container');
-    this._viewport = new UI.ViewportControl(this);
-    this._element = this._viewport.element;
+    this._rowHeight = 17;
+    /** @type {!UI.ListControl<!UI.SuggestBox.Suggestion>} */
+    this._list = new UI.ListControl(this);
+    this._element = this._list.element;
     this._element.classList.add('suggest-box');
     this._container.appendChild(this._element);
     this._element.addEventListener('mousedown', this._onBoxMouseDown.bind(this), true);
     this._detailsPopup = this._container.createChild('div', 'suggest-box details-popup monospace');
     this._detailsPopup.classList.add('hidden');
     this._asyncDetailsCallback = null;
-    /** @type {!Map<number, !Promise<{detail: string, description: string}>>} */
+    /** @type {!Map<!UI.SuggestBox.Suggestion, !Promise<{detail: string, description: string}>>} */
     this._asyncDetailsPromises = new Map();
     this._userInteracted = false;
     this._captureEnter = captureEnter;
-    /** @type {!Array<!Element>} */
-    this._elementList = [];
-    this._rowHeight = 17;
     this._viewportWidth = '100vw';
     this._hasVerticalScroll = false;
     this._userEnteredText = '';
-    /** @type {!UI.SuggestBox.Suggestions} */
-    this._items = [];
   }
 
   /**
@@ -96,17 +90,18 @@
    * @param {!AnchorBox} anchorBox
    */
   setPosition(anchorBox) {
-    this._updateBoxPosition(anchorBox);
+    this._updateBoxPosition(anchorBox, this._list.length());
   }
 
   /**
    * @param {!AnchorBox} anchorBox
+   * @param {number} length
    */
-  _updateBoxPosition(anchorBox) {
+  _updateBoxPosition(anchorBox, length) {
     console.assert(this._overlay);
-    if (this._lastAnchorBox && this._lastAnchorBox.equals(anchorBox) && this._lastItemCount === this.itemCount())
+    if (this._lastAnchorBox && this._lastAnchorBox.equals(anchorBox) && this._lastItemCount === length)
       return;
-    this._lastItemCount = this.itemCount();
+    this._lastItemCount = length;
     this._lastAnchorBox = anchorBox;
 
     // Position relative to main DevTools element.
@@ -128,24 +123,28 @@
     var maxHeight = Math.min(
         Math.max(underHeight, aboveHeight) - spacer,
         this._maxItemsHeight ? this._maxItemsHeight * this._rowHeight : Infinity);
-    var height = this._rowHeight * this._items.length;
+    var height = this._rowHeight * length;
     this._hasVerticalScroll = height > maxHeight;
     this._element.style.height = Math.min(maxHeight, height) + 'px';
   }
 
-  _updateWidth() {
+  /**
+   * @param {!UI.SuggestBox.Suggestions} items
+   */
+  _updateWidth(items) {
     if (this._hasVerticalScroll) {
       this._element.style.width = '100vw';
       return;
     }
+    if (!items.length)
+      return;
     // If there are no scrollbars, set the width to the width of the largest row.
-    var maxIndex = 0;
-    for (var i = 0; i < this._items.length; i++) {
-      if (this._items[i].title.length > this._items[maxIndex].title.length)
-        maxIndex = i;
+    var maxItem = items[0];
+    for (var i = 1; i < items.length; i++) {
+      if (items[i].title.length > maxItem.title.length)
+        maxItem = items[i];
     }
-    var element = /** @type {!Element} */ (this.itemElement(maxIndex));
-    this._element.style.width = UI.measurePreferredSize(element, this._element).width + 'px';
+    this._element.style.width = UI.measurePreferredSize(this.createElementForItem(maxItem), this._element).width + 'px';
   }
 
   /**
@@ -175,10 +174,8 @@
     this._bodyElement.addEventListener('mousedown', this._maybeHideBound, true);
     this._overlay = new UI.SuggestBox.Overlay();
     this._overlay.setContentElement(this._container);
-    var measuringElement = this._createItemElement('1', '12');
-    this._viewport.element.appendChild(measuringElement);
-    this._rowHeight = measuringElement.getBoundingClientRect().height;
-    measuringElement.remove();
+    this._rowHeight =
+        UI.measurePreferredSize(this.createElementForItem({title: '1', subtitle: '12'}), this._element).height;
   }
 
   hide() {
@@ -191,8 +188,6 @@
     this._container.remove();
     this._overlay.dispose();
     delete this._overlay;
-    delete this._selectedElement;
-    this._selectedIndex = -1;
     delete this._lastAnchorBox;
   }
 
@@ -210,10 +205,10 @@
       return true;
     }
 
-    if (!this.visible() || !this._selectedElement)
+    if (!this.visible() || !this._list.selectedItem())
       return false;
 
-    var suggestion = this._selectedElement.__fullValue;
+    var suggestion = this._list.selectedItem().title;
     if (!suggestion)
       return false;
 
@@ -236,57 +231,21 @@
   }
 
   /**
-   * @param {number} shift
-   * @param {boolean=} isCircular
-   * @return {boolean} is changed
-   */
-  _selectClosest(shift, isCircular) {
-    if (!this._length)
-      return false;
-
-    this._userInteracted = true;
-
-    if (this._selectedIndex === -1 && shift < 0)
-      shift += 1;
-
-    var index = this._selectedIndex + shift;
-
-    if (isCircular)
-      index = (this._length + index) % this._length;
-    else
-      index = Number.constrain(index, 0, this._length - 1);
-
-    this._selectItem(index);
-    return true;
-  }
-
-  /**
-   * @param {!Event} event
-   */
-  _onItemMouseDown(event) {
-    this._selectedElement = event.currentTarget;
-    this.acceptSuggestion();
-    event.consume(true);
-  }
-
-  /**
-   * @param {string} query
-   * @param {string} title
-   * @param {string=} subtitle
-   * @param {string=} iconType
-   * @param {boolean=} isSecondary
+   * @override
+   * @param {!UI.SuggestBox.Suggestion} item
    * @return {!Element}
    */
-  _createItemElement(query, title, subtitle, iconType, isSecondary) {
+  createElementForItem(item) {
+    var query = this._userEnteredText;
     var element = createElementWithClass('div', 'suggest-box-content-item source-code');
-    if (iconType) {
-      var icon = UI.Icon.create(iconType, 'suggestion-icon');
+    if (item.iconType) {
+      var icon = UI.Icon.create(item.iconType, 'suggestion-icon');
       element.appendChild(icon);
     }
-    if (isSecondary)
+    if (item.isSecondary)
       element.classList.add('secondary');
     element.tabIndex = -1;
-    var displayText = title.trimEnd(50 + query.length);
+    var displayText = item.title.trimEnd(50 + query.length);
 
     var titleElement = element.createChild('span', 'suggestion-title');
     var index = displayText.toLowerCase().indexOf(query.toLowerCase());
@@ -296,41 +255,75 @@
       titleElement.createChild('span', 'query').textContent = displayText.substring(index, index + query.length);
     titleElement.createChild('span').textContent = displayText.substring(index > -1 ? index + query.length : 0);
     titleElement.createChild('span', 'spacer');
-    if (subtitle) {
+    if (item.subtitle) {
       var subtitleElement = element.createChild('span', 'suggestion-subtitle');
-      subtitleElement.textContent = subtitle.trimEnd(15);
+      subtitleElement.textContent = item.subtitle.trimEnd(15);
     }
-    element.__fullValue = title;
     element.addEventListener('mousedown', this._onItemMouseDown.bind(this), false);
     return element;
   }
 
   /**
-   * @param {!UI.SuggestBox.Suggestions} items
-   * @param {string} userEnteredText
-   * @param {function(number): !Promise<{detail:string, description:string}>=} asyncDetails
+   * @override
+   * @param {!UI.SuggestBox.Suggestion} item
+   * @return {number}
    */
-  _updateItems(items, userEnteredText, asyncDetails) {
-    this._length = items.length;
-    this._asyncDetailsPromises.clear();
-    this._asyncDetailsCallback = asyncDetails;
-    this._elementList = [];
-    delete this._selectedElement;
-
-    this._userEnteredText = userEnteredText;
-    this._items = items;
+  heightForItem(item) {
+    return this._rowHeight;
   }
 
   /**
-   * @param {number} index
+   * @override
+   * @param {!UI.SuggestBox.Suggestion} item
+   * @return {boolean}
+   */
+  isItemSelectable(item) {
+    return true;
+  }
+
+  /**
+   * @override
+   * @param {?UI.SuggestBox.Suggestion} from
+   * @param {?UI.SuggestBox.Suggestion} to
+   * @param {?Element} fromElement
+   * @param {?Element} toElement
+   */
+  selectedItemChanged(from, to, fromElement, toElement) {
+    if (fromElement)
+      fromElement.classList.remove('selected', 'force-white-icons');
+    if (toElement)
+      toElement.classList.add('selected', 'force-white-icons');
+    if (!to)
+      return;
+    this._detailsPopup.classList.add('hidden');
+    this._asyncDetails(to).then(details => {
+      if (this._list.selectedItem() === to)
+        this._showDetailsPopup(details);
+    });
+    this._applySuggestion(true);
+  }
+
+  /**
+   * @param {!Event} event
+   */
+  _onItemMouseDown(event) {
+    if (!this._list.onClick(event))
+      return;
+    this._userInteracted = true;
+    this.acceptSuggestion();
+    event.consume(true);
+  }
+
+  /**
+   * @param {!UI.SuggestBox.Suggestion} item
    * @return {!Promise<?{detail: string, description: string}>}
    */
-  _asyncDetails(index) {
+  _asyncDetails(item) {
     if (!this._asyncDetailsCallback)
       return Promise.resolve(/** @type {?{description: string, detail: string}} */ (null));
-    if (!this._asyncDetailsPromises.has(index))
-      this._asyncDetailsPromises.set(index, this._asyncDetailsCallback(index));
-    return /** @type {!Promise<?{detail: string, description: string}>} */ (this._asyncDetailsPromises.get(index));
+    if (!this._asyncDetailsPromises.has(item))
+      this._asyncDetailsPromises.set(item, this._asyncDetailsCallback(item));
+    return /** @type {!Promise<?{detail: string, description: string}>} */ (this._asyncDetailsPromises.get(item));
   }
 
   /**
@@ -346,39 +339,6 @@
   }
 
   /**
-   * @param {number} index
-   */
-  _selectItem(index) {
-    if (this._selectedElement) {
-      this._selectedElement.classList.remove('selected');
-      this._selectedElement.classList.remove('force-white-icons');
-    }
-
-    this._selectedIndex = index;
-    if (index < 0)
-      return;
-
-    this._selectedElement = this.itemElement(index);
-    this._selectedElement.classList.add('selected');
-    this._selectedElement.classList.add('force-white-icons');
-    this._detailsPopup.classList.add('hidden');
-    var elem = this._selectedElement;
-    this._asyncDetails(index).then(showDetails.bind(this), function() {});
-
-    this._viewport.scrollItemIntoView(index);
-    this._applySuggestion(true);
-
-    /**
-     * @param {?{detail: string, description: string}} details
-     * @this {UI.SuggestBox}
-     */
-    function showDetails(details) {
-      if (elem === this._selectedElement)
-        this._showDetailsPopup(details);
-    }
-  }
-
-  /**
    * @param {!UI.SuggestBox.Suggestions} completions
    * @param {boolean} canShowForSingleItem
    * @param {string} userEnteredText
@@ -398,15 +358,6 @@
     return canShowForSingleItem && completions[0].title !== userEnteredText;
   }
 
-  _ensureRowCountPerViewport() {
-    if (this._rowCountPerViewport)
-      return;
-    if (!this._items.length)
-      return;
-
-    this._rowCountPerViewport = Math.floor(this._element.getBoundingClientRect().height / this._rowHeight);
-  }
-
   /**
    * @param {!AnchorBox} anchorBox
    * @param {!UI.SuggestBox.Suggestions} completions
@@ -424,11 +375,20 @@
       asyncDetails) {
     delete this._onlyCompletion;
     if (this._canShowBox(completions, canShowForSingleItem, userEnteredText)) {
-      this._updateItems(completions, userEnteredText, asyncDetails);
+      this._asyncDetailsPromises.clear();
+      if (asyncDetails)
+        this._asyncDetailsCallback = item => asyncDetails(completions.indexOf(item));
+      else
+        this._asyncDetailsCallback = null;
+      this._userEnteredText = userEnteredText;
+
       this._show();
-      this._updateBoxPosition(anchorBox);
-      this._updateWidth();
-      this._viewport.refresh();
+      this._updateBoxPosition(anchorBox, completions.length);
+      this._updateWidth(completions);
+
+      this._list.setHeightMode(UI.ListHeightMode.Fixed);
+      this._list.replaceAllItems(completions);
+
       var highestPriorityItem = -1;
       if (selectHighestPriority) {
         var highestPriority = -Infinity;
@@ -440,8 +400,7 @@
           }
         }
       }
-      this._selectItem(highestPriorityItem);
-      delete this._rowCountPerViewport;
+      this._list.selectItemAtIndex(highestPriorityItem, true);
     } else {
       if (completions.length === 1) {
         this._onlyCompletion = completions[0].title;
@@ -456,100 +415,38 @@
    * @return {boolean}
    */
   keyPressed(event) {
-    switch (event.key) {
-      case 'ArrowUp':
-        return this.upKeyPressed();
-      case 'ArrowDown':
-        return this.downKeyPressed();
-      case 'PageUp':
-        return this.pageUpKeyPressed();
-      case 'PageDown':
-        return this.pageDownKeyPressed();
-      case 'Enter':
-        return this.enterKeyPressed();
+    if (this._list.onKeyDown(event)) {
+      this._userInteracted = true;
+      return true;
     }
+    if (event.key === 'Enter')
+      return this.enterKeyPressed();
     return false;
   }
 
   /**
    * @return {boolean}
    */
-  upKeyPressed() {
-    return this._selectClosest(-1, true);
-  }
-
-  /**
-   * @return {boolean}
-   */
-  downKeyPressed() {
-    return this._selectClosest(1, true);
-  }
-
-  /**
-   * @return {boolean}
-   */
-  pageUpKeyPressed() {
-    this._ensureRowCountPerViewport();
-    return this._selectClosest(-this._rowCountPerViewport, false);
-  }
-
-  /**
-   * @return {boolean}
-   */
-  pageDownKeyPressed() {
-    this._ensureRowCountPerViewport();
-    return this._selectClosest(this._rowCountPerViewport, false);
-  }
-
-  /**
-   * @return {boolean}
-   */
   enterKeyPressed() {
     if (!this._userInteracted && this._captureEnter)
       return false;
 
-    var hasSelectedItem = !!this._selectedElement || this._onlyCompletion;
+    var hasSelectedItem = !!this._list.selectedItem() || this._onlyCompletion;
     this.acceptSuggestion();
 
     // Report the event as non-handled if there is no selected item,
     // to commit the input or handle it otherwise.
     return hasSelectedItem;
   }
-
-  /**
-   * @override
-   * @param {number} index
-   * @return {number}
-   */
-  fastItemHeight(index) {
-    return this._rowHeight;
-  }
-
-  /**
-   * @override
-   * @return {number}
-   */
-  itemCount() {
-    return this._items.length;
-  }
-
-  /**
-   * @override
-   * @param {number} index
-   * @return {?Element}
-   */
-  itemElement(index) {
-    if (!this._elementList[index]) {
-      this._elementList[index] = this._createItemElement(
-          this._userEnteredText, this._items[index].title, this._items[index].subtitle, this._items[index].iconType,
-          this._items[index].isSecondary);
-    }
-    return this._elementList[index];
-  }
 };
 
 /**
- * @typedef {!Array.<{title: string, subtitle: (string|undefined), iconType: (string|undefined), priority: (number|undefined), isSecondary: (boolean|undefined)}>}
+ * @typedef {!{title: string, subtitle: (string|undefined), iconType: (string|undefined), priority: (number|undefined), isSecondary: (boolean|undefined)}}
+ */
+UI.SuggestBox.Suggestion;
+
+/**
+ * @typedef {!Array<!UI.SuggestBox.Suggestion>}
  */
 UI.SuggestBox.Suggestions;
 
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/UIUtils.js b/third_party/WebKit/Source/devtools/front_end/ui/UIUtils.js
index 18305b0..53f32100 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/UIUtils.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui/UIUtils.js
@@ -998,14 +998,14 @@
   containerElement = containerElement || element.ownerDocument.body;
   containerElement.appendChild(element);
   element.positionAt(0, 0);
-  var result = new Size(element.offsetWidth, element.offsetHeight);
+  var result = element.getBoundingClientRect();
 
   element.positionAt(undefined, undefined);
   if (oldParent)
     oldParent.insertBefore(element, oldNextSibling);
   else
     element.remove();
-  return result;
+  return new Size(result.width, result.height);
 };
 
 /**
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/ViewportControl.js b/third_party/WebKit/Source/devtools/front_end/ui/ViewportControl.js
deleted file mode 100644
index dbd3600..0000000
--- a/third_party/WebKit/Source/devtools/front_end/ui/ViewportControl.js
+++ /dev/null
@@ -1,169 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-/**
- * @unrestricted
- */
-UI.ViewportControl = class {
-  /**
-   * @param {!UI.ViewportControl.Provider} provider
-   */
-  constructor(provider) {
-    this.element = createElement('div');
-    this.element.style.overflow = 'auto';
-    this._innerElement = this.element.createChild('div');
-    this._innerElement.style.height = '0px';
-    this._innerElement.style.position = 'relative';
-    this._innerElement.style.overflow = 'hidden';
-
-    this._provider = provider;
-    this.element.addEventListener('scroll', this._update.bind(this), false);
-    this._itemCount = 0;
-    this._indexSymbol = Symbol('UI.ViewportControl._indexSymbol');
-  }
-
-  refresh() {
-    this._itemCount = this._provider.itemCount();
-    this._innerElement.removeChildren();
-
-    var height = 0;
-    this._cumulativeHeights = new Int32Array(this._itemCount);
-    for (var i = 0; i < this._itemCount; ++i) {
-      height += this._provider.fastItemHeight(i);
-      this._cumulativeHeights[i] = height;
-    }
-    this._innerElement.style.height = height + 'px';
-
-    this._update();
-  }
-
-  _update() {
-    if (!this._cumulativeHeights) {
-      this.refresh();
-      return;
-    }
-
-    var visibleHeight = this._visibleHeight();
-    var visibleFrom = this.element.scrollTop;
-    var activeHeight = visibleHeight * 2;
-    var firstActiveIndex = Math.max(
-        Array.prototype.lowerBound.call(this._cumulativeHeights, visibleFrom + 1 - (activeHeight - visibleHeight) / 2),
-        0);
-    var lastActiveIndex = Math.min(
-        Array.prototype.lowerBound.call(
-            this._cumulativeHeights, visibleFrom + visibleHeight + (activeHeight - visibleHeight) / 2),
-        this._itemCount - 1);
-
-    var children = this._innerElement.children;
-    for (var i = children.length - 1; i >= 0; --i) {
-      var element = children[i];
-      if (element[this._indexSymbol] < firstActiveIndex || element[this._indexSymbol] > lastActiveIndex)
-        element.remove();
-    }
-
-    for (var i = firstActiveIndex; i <= lastActiveIndex; ++i)
-      this._insertElement(i);
-  }
-
-  /**
-   * @param {number} index
-   */
-  _insertElement(index) {
-    var element = this._provider.itemElement(index);
-    if (!element || element.parentElement === this._innerElement)
-      return;
-
-    element.style.position = 'absolute';
-    element.style.top = (this._cumulativeHeights[index - 1] || 0) + 'px';
-    element.style.left = '0';
-    element.style.right = '0';
-    element[this._indexSymbol] = index;
-    this._innerElement.appendChild(element);
-  }
-
-  /**
-   * @return {number}
-   */
-  firstVisibleIndex() {
-    return Math.max(Array.prototype.lowerBound.call(this._cumulativeHeights, this.element.scrollTop + 1), 0);
-  }
-
-  /**
-   * @return {number}
-   */
-  lastVisibleIndex() {
-    return Math.min(
-        Array.prototype.lowerBound.call(this._cumulativeHeights, this.element.scrollTop + this._visibleHeight()),
-        this._itemCount);
-  }
-
-  /**
-   * @param {number} index
-   * @param {boolean=} makeLast
-   */
-  scrollItemIntoView(index, makeLast) {
-    var firstVisibleIndex = this.firstVisibleIndex();
-    var lastVisibleIndex = this.lastVisibleIndex();
-    if (index > firstVisibleIndex && index < lastVisibleIndex)
-      return;
-    if (makeLast)
-      this.forceScrollItemToBeLast(index);
-    else if (index <= firstVisibleIndex)
-      this.forceScrollItemToBeFirst(index);
-    else if (index >= lastVisibleIndex)
-      this.forceScrollItemToBeLast(index);
-  }
-
-  /**
-   * @param {number} index
-   */
-  forceScrollItemToBeFirst(index) {
-    this.element.scrollTop = index > 0 ? this._cumulativeHeights[index - 1] : 0;
-    this._update();
-  }
-
-  /**
-   * @param {number} index
-   */
-  forceScrollItemToBeLast(index) {
-    this.element.scrollTop = this._cumulativeHeights[index] - this._visibleHeight();
-    this._update();
-  }
-
-  /**
-   * @return {number}
-   */
-  _visibleHeight() {
-    return this.element.offsetHeight;
-  }
-};
-
-/**
- * @interface
- */
-UI.ViewportControl.Provider = function() {};
-
-UI.ViewportControl.Provider.prototype = {
-  /**
-   * @param {number} index
-   * @return {number}
-   */
-  fastItemHeight(index) {
-    return 0;
-  },
-
-  /**
-   * @return {number}
-   */
-  itemCount() {
-    return 0;
-  },
-
-  /**
-   * @param {number} index
-   * @return {?Element}
-   */
-  itemElement(index) {
-    return null;
-  }
-};
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/module.json b/third_party/WebKit/Source/devtools/front_end/ui/module.json
index 65562cc..80f64da 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/module.json
+++ b/third_party/WebKit/Source/devtools/front_end/ui/module.json
@@ -30,6 +30,7 @@
         "InplaceEditor.js",
         "TextEditor.js",
         "KeyboardShortcut.js",
+        "ListControl.js",
         "ListWidget.js",
         "Panel.js",
         "Popover.js",
@@ -50,7 +51,6 @@
         "SuggestBox.js",
         "TabbedPane.js",
         "UIUtils.js",
-        "ViewportControl.js",
         "ZoomManager.js"
     ],
     "resources": [
diff --git a/third_party/WebKit/Source/devtools/front_end/ui_lazy/CommandMenu.js b/third_party/WebKit/Source/devtools/front_end/ui_lazy/CommandMenu.js
index e70cb71..93d494d 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui_lazy/CommandMenu.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui_lazy/CommandMenu.js
@@ -225,10 +225,12 @@
 
   /**
    * @override
-   * @param {number} itemIndex
+   * @param {?number} itemIndex
    * @param {string} promptValue
    */
   selectItem(itemIndex, promptValue) {
+    if (itemIndex === null)
+      return;
     this._commands[itemIndex].execute();
   }
 
diff --git a/third_party/WebKit/Source/devtools/front_end/ui_lazy/FilteredListWidget.js b/third_party/WebKit/Source/devtools/front_end/ui_lazy/FilteredListWidget.js
index 7cb25af..64dfe06 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui_lazy/FilteredListWidget.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui_lazy/FilteredListWidget.js
@@ -4,8 +4,8 @@
  * found in the LICENSE file.
  */
 /**
- * @implements {UI.ViewportControl.Provider}
  * @unrestricted
+ * @implements {UI.ListDelegate}
  */
 UI.FilteredListWidget = class extends UI.VBox {
   /**
@@ -35,9 +35,9 @@
     this._progressElement = this.contentElement.createChild('div', 'filtered-list-widget-progress');
     this._progressBarElement = this._progressElement.createChild('div', 'filtered-list-widget-progress-bar');
 
-    this._filteredItems = [];
-    this._viewportControl = new UI.ViewportControl(this);
-    this._itemElementsContainer = this._viewportControl.element;
+    /** @type {!UI.ListControl<number>} */
+    this._list = new UI.ListControl(this);
+    this._itemElementsContainer = this._list.element;
     this._itemElementsContainer.classList.add('container');
     this._itemElementsContainer.addEventListener('click', this._onClick.bind(this), false);
     this.contentElement.appendChild(this._itemElementsContainer);
@@ -48,10 +48,6 @@
     this._delegate.setRefreshCallback(this._itemsLoaded.bind(this));
     this._itemsLoaded();
     this._updateShowMatchingItems();
-    this._viewportControl.refresh();
-
-    /** @typedef {!Array.<!Element>} */
-    this._elements = [];
   }
 
   /**
@@ -78,7 +74,6 @@
     this._dialog.setPosition(undefined, 22);
     this.show(this._dialog.element);
     this._dialog.show();
-    this._progressElementWidth = this._progressElement.offsetWidth;
   }
 
   /**
@@ -92,6 +87,7 @@
    * @override
    */
   willHide() {
+    this._list.setHeightMode(UI.ListHeightMode.Measured);
     this._delegate.dispose();
     if (this._filterTimer)
       clearTimeout(this._filterTimer);
@@ -104,14 +100,12 @@
     event.preventDefault();
     if (!this._delegate.itemCount())
       return;
-    var selectedIndex = this._shouldShowMatchingItems() && this._selectedIndexInFiltered < this._filteredItems.length ?
-        this._filteredItems[this._selectedIndexInFiltered] :
-        null;
+    var selectedIndexInDelegate = this._shouldShowMatchingItems() ? this._list.selectedItem() : null;
 
     // Detach dialog before allowing delegate to override focus.
     if (this._dialog)
       this._dialog.detach();
-    this._delegate.selectItemWithQuery(selectedIndex, this._value());
+    this._delegate.selectItemWithQuery(selectedIndexInDelegate, this._value());
   }
 
   _itemsLoaded() {
@@ -126,21 +120,53 @@
   }
 
   /**
-   * @param {number} index
+   * @override
+   * @param {number} item
    * @return {!Element}
    */
-  _createItemElement(index) {
+  createElementForItem(item) {
     var itemElement = createElement('div');
     itemElement.className = 'filtered-list-widget-item ' + (this._renderAsTwoRows ? 'two-rows' : 'one-row');
-    itemElement._titleElement = itemElement.createChild('div', 'filtered-list-widget-title');
-    itemElement._subtitleElement = itemElement.createChild('div', 'filtered-list-widget-subtitle');
-    itemElement._subtitleElement.textContent = '\u200B';
-    itemElement._index = index;
-    this._delegate.renderItem(index, this._value(), itemElement._titleElement, itemElement._subtitleElement);
+    var titleElement = itemElement.createChild('div', 'filtered-list-widget-title');
+    var subtitleElement = itemElement.createChild('div', 'filtered-list-widget-subtitle');
+    subtitleElement.textContent = '\u200B';
+    this._delegate.renderItem(item, this._value(), titleElement, subtitleElement);
     return itemElement;
   }
 
   /**
+   * @override
+   * @param {number} item
+   * @return {number}
+   */
+  heightForItem(item) {
+    return 0;
+  }
+
+  /**
+   * @override
+   * @param {number} item
+   * @return {boolean}
+   */
+  isItemSelectable(item) {
+    return true;
+  }
+
+  /**
+   * @override
+   * @param {?number} from
+   * @param {?number} to
+   * @param {?Element} fromElement
+   * @param {?Element} toElement
+   */
+  selectedItemChanged(from, to, fromElement, toElement) {
+    if (fromElement)
+      fromElement.classList.remove('selected');
+    if (toElement)
+      toElement.classList.add('selected');
+  }
+
+  /**
    * @param {string} query
    */
   setQuery(query) {
@@ -167,8 +193,8 @@
       clearTimeout(this._scoringTimer);
       delete this._scoringTimer;
 
-      if (this._refreshViewportWithCurrentResult)
-        this._refreshViewportWithCurrentResult();
+      if (this._refreshListWithCurrentResult)
+        this._refreshListWithCurrentResult();
     }
 
     this._progressBarElement.style.transform = 'scaleX(0)';
@@ -179,8 +205,6 @@
 
     var filterRegex = query ? UI.FilteredListWidget.filterRegex(query) : null;
 
-    var oldSelectedAbsoluteIndex =
-        this._selectedIndexInFiltered && query ? this._filteredItems[this._selectedIndexInFiltered] : undefined;
     var filteredItems = [];
 
     var bestScores = [];
@@ -237,8 +261,7 @@
         }
       }
 
-      this._refreshViewportWithCurrentResult =
-          this._refreshViewport.bind(this, bestItems, overflowItems, filteredItems, oldSelectedAbsoluteIndex);
+      this._refreshListWithCurrentResult = this._refreshList.bind(this, bestItems, overflowItems, filteredItems);
 
       // Process everything in chunks.
       if (i < this._delegate.itemCount()) {
@@ -248,7 +271,7 @@
       }
       this._progressBarElement.style.transform = 'scaleX(1)';
       this._progressBarElement.classList.add('filtered-widget-progress-fade');
-      this._refreshViewportWithCurrentResult();
+      this._refreshListWithCurrentResult();
     }
   }
 
@@ -256,22 +279,13 @@
    * @param {!Array<number>} bestItems
    * @param {!Array<number>} overflowItems
    * @param {!Array<number>} filteredItems
-   * @param {number|undefined} selectedAbsoluteIndex
    */
-  _refreshViewport(bestItems, overflowItems, filteredItems, selectedAbsoluteIndex) {
-    delete this._refreshViewportWithCurrentResult;
-    this._filteredItems = bestItems.concat(overflowItems).concat(filteredItems);
-
-    this._selectedIndexInFiltered = 0;
-    for (var i = 0; selectedAbsoluteIndex !== undefined && i < this._filteredItems.length; ++i) {
-      if (this._filteredItems[i] === selectedAbsoluteIndex) {
-        this._selectedIndexInFiltered = i;
-        break;
-      }
-    }
-    this._elements = [];
-    this._viewportControl.refresh();
-    this._updateSelection(this._selectedIndexInFiltered, false);
+  _refreshList(bestItems, overflowItems, filteredItems) {
+    delete this._refreshListWithCurrentResult;
+    filteredItems = [].concat(bestItems, overflowItems, filteredItems);
+    this._list.replaceAllItems(filteredItems);
+    if (filteredItems.length)
+      this._list.selectItemAtIndex(0, true);
     this._itemsFilteredForTest();
   }
 
@@ -293,38 +307,15 @@
   }
 
   /**
-   * @return {number}
+   * @param {!Event} event
    */
-  _rowsPerViewport() {
-    return Math.floor(this._viewportControl.element.clientHeight / this._rowHeight);
-  }
-
   _onKeyDown(event) {
-    var newSelectedIndex = this._selectedIndexInFiltered;
+    if (this._list.onKeyDown(event)) {
+      event.consume(true);
+      return;
+    }
 
     switch (event.keyCode) {
-      case UI.KeyboardShortcut.Keys.Down.code:
-        if (++newSelectedIndex >= this._filteredItems.length)
-          newSelectedIndex = 0;
-        this._updateSelection(newSelectedIndex, true);
-        event.consume(true);
-        break;
-      case UI.KeyboardShortcut.Keys.Up.code:
-        if (--newSelectedIndex < 0)
-          newSelectedIndex = this._filteredItems.length - 1;
-        this._updateSelection(newSelectedIndex, false);
-        event.consume(true);
-        break;
-      case UI.KeyboardShortcut.Keys.PageDown.code:
-        newSelectedIndex = Math.min(newSelectedIndex + this._rowsPerViewport(), this._filteredItems.length - 1);
-        this._updateSelection(newSelectedIndex, true);
-        event.consume(true);
-        break;
-      case UI.KeyboardShortcut.Keys.PageUp.code:
-        newSelectedIndex = Math.max(newSelectedIndex - this._rowsPerViewport(), 0);
-        this._updateSelection(newSelectedIndex, false);
-        event.consume(true);
-        break;
       case UI.KeyboardShortcut.Keys.Enter.code:
         this._onEnter(event);
         break;
@@ -342,63 +333,17 @@
   }
 
   /**
-   * @param {number} index
-   * @param {boolean} makeLast
+   * @param {!Event} event
    */
-  _updateSelection(index, makeLast) {
-    if (!this._filteredItems.length)
-      return;
-    if (this._selectedElement)
-      this._selectedElement.classList.remove('selected');
-    this._viewportControl.scrollItemIntoView(index, makeLast);
-    this._selectedIndexInFiltered = index;
-    this._selectedElement = this._elements[index];
-    if (this._selectedElement)
-      this._selectedElement.classList.add('selected');
-  }
-
   _onClick(event) {
-    var itemElement = event.target.enclosingNodeOrSelfWithClass('filtered-list-widget-item');
-    if (!itemElement)
+    if (!this._list.onClick(event))
       return;
 
+    event.consume(true);
     // Detach dialog before allowing delegate to override focus.
     if (this._dialog)
       this._dialog.detach();
-    this._delegate.selectItemWithQuery(itemElement._index, this._value());
-  }
-
-  /**
-   * @override
-   * @return {number}
-   */
-  itemCount() {
-    return this._filteredItems.length;
-  }
-
-  /**
-   * @override
-   * @param {number} index
-   * @return {number}
-   */
-  fastItemHeight(index) {
-    if (!this._rowHeight) {
-      var delegateIndex = this._filteredItems[index];
-      var element = this._createItemElement(delegateIndex);
-      this._rowHeight = UI.measurePreferredSize(element, this._itemElementsContainer).height;
-    }
-    return this._rowHeight;
-  }
-
-  /**
-   * @override
-   * @param {number} index
-   * @return {!Element}
-   */
-  itemElement(index) {
-    if (!this._elements[index])
-      this._elements[index] = this._createItemElement(this._filteredItems[index]);
-    return this._elements[index];
+    this._delegate.selectItemWithQuery(this._list.selectedItem(), this._value());
   }
 };
 
@@ -524,7 +469,7 @@
   }
 
   /**
-   * @param {number} itemIndex
+   * @param {?number} itemIndex
    * @param {string} promptValue
    */
   selectItemWithQuery(itemIndex, promptValue) {
@@ -535,7 +480,7 @@
   }
 
   /**
-   * @param {number} itemIndex
+   * @param {?number} itemIndex
    * @param {string} promptValue
    */
   selectItem(itemIndex, promptValue) {
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 0feacbcd..d9e67d0 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -50215,6 +50215,15 @@
   <summary>Print preview events.</summary>
 </histogram>
 
+<histogram name="PrintPreview.PrintDocumentType"
+    enum="PrintPreviewPrintDocumentTypeBuckets">
+  <owner>rbpotter@chromium.org</owner>
+  <summary>
+    Track type of documents printed (HTML vs PDF). Recorded immediately after a
+    user requests that a document be printed.
+  </summary>
+</histogram>
+
 <histogram name="PrintPreview.PrintSettings" enum="PrintSettings">
   <owner>vitalybuka@chromium.org</owner>
   <summary>
@@ -99983,6 +99992,11 @@
   <int value="13" label="INVITATION_REJECTED"/>
 </enum>
 
+<enum name="PrintPreviewPrintDocumentTypeBuckets" type="int">
+  <int value="0" label="HTML_DOCUMENT"/>
+  <int value="1" label="PDF_DOCUMENT"/>
+</enum>
+
 <enum name="PrintPreviewPrintSettingsUiBuckets" type="int">
   <int value="0" label="ADVANCED_SETTINGS_DIALOG_SHOWN"/>
   <int value="1" label="ADVANCED_SETTINGS_DIALOG_CANCELED"/>
@@ -113235,6 +113249,21 @@
       name="PageLoad.ParseTiming.NavigationToParseStart.LoadType.ForwardBackNavigation"/>
 </histogram_suffixes>
 
+<histogram_suffixes name="PageLoadMetricsClientsAmpCachePages" separator="."
+    ordering="prefix">
+  <suffix name="Clients.AMPCache"
+      label="PageLoadMetrics that are a result of a navigations to an AMP
+             cache page. Same page navigations are not tracked."/>
+  <affected-histogram
+      name="PageLoad.DocumentTiming.NavigationToDOMContentLoadedEventFired"/>
+  <affected-histogram name="PageLoad.DocumentTiming.NavigationToFirstLayout"/>
+  <affected-histogram
+      name="PageLoad.DocumentTiming.NavigationToLoadEventFired"/>
+  <affected-histogram
+      name="PageLoad.PaintTiming.NavigationToFirstContentfulPaint"/>
+  <affected-histogram name="PageLoad.ParseTiming.NavigationToParseStart"/>
+</histogram_suffixes>
+
 <histogram_suffixes name="PageLoadMetricsClientsCssScanner" separator="."
     ordering="prefix">
   <suffix name="Clients.CssScanner"