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"