Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include <map> | |
| 6 #include <memory> | |
| 5 #include <string> | 7 #include <string> |
| 8 #include <utility> | |
| 6 | 9 |
| 7 #include "chrome/browser/predictors/loading_data_collector.h" | 10 #include "chrome/browser/predictors/loading_data_collector.h" |
| 11 #include "chrome/browser/predictors/loading_stats_collector.h" | |
| 12 #include "chrome/browser/predictors/resource_prefetch_predictor.h" | |
| 13 #include "chrome/browser/predictors/resource_prefetch_predictor_tables.h" | |
| 8 #include "chrome/browser/profiles/profile.h" | 14 #include "chrome/browser/profiles/profile.h" |
| 15 #include "components/history/core/browser/history_service.h" | |
| 9 #include "components/mime_util/mime_util.h" | 16 #include "components/mime_util/mime_util.h" |
| 17 #include "content/public/browser/browser_thread.h" | |
| 10 #include "content/public/browser/resource_request_info.h" | 18 #include "content/public/browser/resource_request_info.h" |
| 11 #include "content/public/common/resource_type.h" | 19 #include "content/public/common/resource_type.h" |
| 20 #include "net/http/http_response_headers.h" | |
| 12 #include "net/url_request/url_request.h" | 21 #include "net/url_request/url_request.h" |
| 13 | 22 |
| 23 using content::BrowserThread; | |
| 24 | |
| 14 namespace predictors { | 25 namespace predictors { |
| 15 | 26 |
| 16 namespace { | 27 namespace { |
| 17 | 28 |
| 18 bool g_allow_port_in_urls = false; | 29 bool g_allow_port_in_urls = false; |
| 19 | 30 |
| 31 // Sorted by decreasing likelihood according to HTTP archive. | |
| 32 const char* kFontMimeTypes[] = {"font/woff2", | |
| 33 "application/x-font-woff", | |
| 34 "application/font-woff", | |
| 35 "application/font-woff2", | |
| 36 "font/x-woff", | |
| 37 "application/x-font-ttf", | |
| 38 "font/woff", | |
| 39 "font/ttf", | |
| 40 "application/x-font-otf", | |
| 41 "x-font/woff", | |
| 42 "application/font-sfnt", | |
| 43 "application/font-ttf"}; | |
| 44 | |
| 45 bool IsNoStore(const net::URLRequest& response) { | |
| 46 if (response.was_cached()) | |
| 47 return false; | |
| 48 | |
| 49 const net::HttpResponseInfo& response_info = response.response_info(); | |
| 50 if (!response_info.headers.get()) | |
| 51 return false; | |
| 52 return response_info.headers->HasHeaderValue("cache-control", "no-store"); | |
| 53 } | |
| 54 | |
| 20 } // namespace | 55 } // namespace |
| 21 | 56 |
| 57 OriginRequestSummary::OriginRequestSummary() | |
| 58 : origin(), | |
| 59 always_access_network(false), | |
| 60 accessed_network(false), | |
| 61 first_occurrence(0) {} | |
| 62 | |
| 63 OriginRequestSummary::OriginRequestSummary(const OriginRequestSummary& other) = | |
| 64 default; | |
| 65 | |
| 66 OriginRequestSummary::~OriginRequestSummary() {} | |
| 67 | |
| 68 URLRequestSummary::URLRequestSummary() | |
| 69 : resource_type(content::RESOURCE_TYPE_LAST_TYPE), | |
| 70 priority(net::IDLE), | |
| 71 before_first_contentful_paint(false), | |
| 72 was_cached(false), | |
| 73 has_validators(false), | |
| 74 always_revalidate(false), | |
| 75 is_no_store(false), | |
| 76 network_accessed(false) {} | |
| 77 | |
| 78 URLRequestSummary::URLRequestSummary(const URLRequestSummary& other) = default; | |
| 79 | |
| 80 URLRequestSummary::~URLRequestSummary() {} | |
| 81 | |
| 82 // static | |
| 83 bool URLRequestSummary::SummarizeResponse(const net::URLRequest& request, | |
| 84 URLRequestSummary* summary) { | |
| 85 const content::ResourceRequestInfo* request_info = | |
| 86 content::ResourceRequestInfo::ForRequest(&request); | |
| 87 if (!request_info) | |
| 88 return false; | |
| 89 | |
| 90 // This method is called when the response is started, so this field reflects | |
| 91 // the time at which the response began, not when it finished, as would | |
| 92 // arguably be ideal. This means if firstContentfulPaint happens after the | |
| 93 // response has started, but before it's finished, we will erroneously mark | |
| 94 // the resource as having been loaded before firstContentfulPaint. This is | |
| 95 // a rare and insignificant enough occurrence that we opt to record the time | |
| 96 // here for the sake of simplicity. | |
| 97 summary->response_time = base::TimeTicks::Now(); | |
| 98 summary->resource_url = request.original_url(); | |
| 99 summary->request_url = request.url(); | |
| 100 content::ResourceType resource_type_from_request = | |
| 101 request_info->GetResourceType(); | |
| 102 summary->priority = request.priority(); | |
| 103 request.GetMimeType(&summary->mime_type); | |
| 104 summary->was_cached = request.was_cached(); | |
| 105 summary->resource_type = LoadingDataCollector::GetResourceType( | |
| 106 resource_type_from_request, summary->mime_type); | |
| 107 | |
| 108 scoped_refptr<net::HttpResponseHeaders> headers = | |
| 109 request.response_info().headers; | |
| 110 if (headers.get()) { | |
| 111 summary->has_validators = headers->HasValidators(); | |
| 112 // RFC 2616, section 14.9. | |
| 113 summary->always_revalidate = | |
| 114 headers->HasHeaderValue("cache-control", "no-cache") || | |
| 115 headers->HasHeaderValue("pragma", "no-cache") || | |
| 116 headers->HasHeaderValue("vary", "*"); | |
| 117 summary->is_no_store = IsNoStore(request); | |
| 118 } | |
| 119 summary->network_accessed = request.response_info().network_accessed; | |
| 120 return true; | |
| 121 } | |
| 122 | |
| 123 PageRequestSummary::PageRequestSummary(const GURL& i_main_frame_url) | |
| 124 : main_frame_url(i_main_frame_url), | |
| 125 initial_url(i_main_frame_url), | |
| 126 first_contentful_paint(base::TimeTicks::Max()) {} | |
| 127 | |
| 128 PageRequestSummary::PageRequestSummary(const PageRequestSummary& other) = | |
| 129 default; | |
| 130 | |
| 131 void PageRequestSummary::UpdateOrAddToOrigins( | |
| 132 const URLRequestSummary& request_summary) { | |
| 133 const GURL& request_url = request_summary.request_url; | |
| 134 DCHECK(request_url.is_valid()); | |
| 135 if (!request_url.is_valid()) | |
| 136 return; | |
| 137 | |
| 138 GURL origin = request_url.GetOrigin(); | |
| 139 auto it = origins.find(origin); | |
| 140 if (it == origins.end()) { | |
| 141 OriginRequestSummary summary; | |
| 142 summary.origin = origin; | |
| 143 summary.first_occurrence = origins.size(); | |
| 144 it = origins.insert({origin, summary}).first; | |
| 145 } | |
| 146 | |
| 147 it->second.always_access_network |= | |
| 148 request_summary.always_revalidate || request_summary.is_no_store; | |
| 149 it->second.accessed_network |= request_summary.network_accessed; | |
| 150 } | |
| 151 | |
| 152 PageRequestSummary::~PageRequestSummary() {} | |
| 153 | |
| 154 content::ResourceType LoadingDataCollector::GetResourceTypeFromMimeType( | |
| 155 const std::string& mime_type, | |
| 156 content::ResourceType fallback) { | |
| 157 if (mime_type.empty()) { | |
| 158 return fallback; | |
| 159 } else if (mime_util::IsSupportedImageMimeType(mime_type)) { | |
| 160 return content::RESOURCE_TYPE_IMAGE; | |
| 161 } else if (mime_util::IsSupportedJavascriptMimeType(mime_type)) { | |
| 162 return content::RESOURCE_TYPE_SCRIPT; | |
| 163 } else if (net::MatchesMimeType("text/css", mime_type)) { | |
| 164 return content::RESOURCE_TYPE_STYLESHEET; | |
| 165 } else { | |
| 166 bool found = | |
| 167 std::any_of(std::begin(kFontMimeTypes), std::end(kFontMimeTypes), | |
| 168 [&mime_type](const std::string& mime) { | |
| 169 return net::MatchesMimeType(mime, mime_type); | |
| 170 }); | |
| 171 if (found) | |
| 172 return content::RESOURCE_TYPE_FONT_RESOURCE; | |
| 173 } | |
| 174 return fallback; | |
| 175 } | |
| 176 | |
| 177 content::ResourceType LoadingDataCollector::GetResourceType( | |
| 178 content::ResourceType resource_type, | |
| 179 const std::string& mime_type) { | |
| 180 // Restricts content::RESOURCE_TYPE_{PREFETCH,SUB_RESOURCE,XHR} to a small set | |
| 181 // of mime types, because these resource types don't communicate how the | |
| 182 // resources will be used. | |
| 183 if (resource_type == content::RESOURCE_TYPE_PREFETCH || | |
| 184 resource_type == content::RESOURCE_TYPE_SUB_RESOURCE || | |
| 185 resource_type == content::RESOURCE_TYPE_XHR) { | |
| 186 return GetResourceTypeFromMimeType(mime_type, | |
| 187 content::RESOURCE_TYPE_LAST_TYPE); | |
| 188 } | |
| 189 return resource_type; | |
| 190 } | |
| 191 | |
| 22 // static | 192 // static |
| 23 bool LoadingDataCollector::ShouldRecordRequest( | 193 bool LoadingDataCollector::ShouldRecordRequest( |
| 24 net::URLRequest* request, | 194 net::URLRequest* request, |
| 25 content::ResourceType resource_type) { | 195 content::ResourceType resource_type) { |
| 26 const content::ResourceRequestInfo* request_info = | 196 const content::ResourceRequestInfo* request_info = |
| 27 content::ResourceRequestInfo::ForRequest(request); | 197 content::ResourceRequestInfo::ForRequest(request); |
| 28 if (!request_info) | 198 if (!request_info) |
| 29 return false; | 199 return false; |
| 30 | 200 |
| 31 if (!request_info->IsMainFrame()) | 201 if (!request_info->IsMainFrame()) |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 91 return false; | 261 return false; |
| 92 | 262 |
| 93 return true; | 263 return true; |
| 94 } | 264 } |
| 95 | 265 |
| 96 // static | 266 // static |
| 97 bool LoadingDataCollector::IsHandledResourceType( | 267 bool LoadingDataCollector::IsHandledResourceType( |
| 98 content::ResourceType resource_type, | 268 content::ResourceType resource_type, |
| 99 const std::string& mime_type) { | 269 const std::string& mime_type) { |
| 100 content::ResourceType actual_resource_type = | 270 content::ResourceType actual_resource_type = |
| 101 ResourcePrefetchPredictor::GetResourceType(resource_type, mime_type); | 271 GetResourceType(resource_type, mime_type); |
| 102 return actual_resource_type == content::RESOURCE_TYPE_STYLESHEET || | 272 return actual_resource_type == content::RESOURCE_TYPE_STYLESHEET || |
| 103 actual_resource_type == content::RESOURCE_TYPE_SCRIPT || | 273 actual_resource_type == content::RESOURCE_TYPE_SCRIPT || |
| 104 actual_resource_type == content::RESOURCE_TYPE_IMAGE || | 274 actual_resource_type == content::RESOURCE_TYPE_IMAGE || |
| 105 actual_resource_type == content::RESOURCE_TYPE_FONT_RESOURCE; | 275 actual_resource_type == content::RESOURCE_TYPE_FONT_RESOURCE; |
| 106 } | 276 } |
| 107 | 277 |
| 108 // static | 278 // static |
| 109 void LoadingDataCollector::SetAllowPortInUrlsForTesting(bool state) { | 279 void LoadingDataCollector::SetAllowPortInUrlsForTesting(bool state) { |
| 110 g_allow_port_in_urls = state; | 280 g_allow_port_in_urls = state; |
| 111 } | 281 } |
| 112 | 282 |
| 113 LoadingDataCollector::LoadingDataCollector(ResourcePrefetchPredictor* predictor) | 283 LoadingDataCollector::LoadingDataCollector( |
| 114 : predictor_(predictor) {} | 284 ResourcePrefetchPredictor* predictor, |
| 285 predictors::LoadingStatsCollector* stats_collector, | |
| 286 const LoadingPredictorConfig& config) | |
| 287 : predictor_(predictor), | |
| 288 stats_collector_(stats_collector), | |
| 289 config_(config) {} | |
| 115 | 290 |
| 116 LoadingDataCollector::~LoadingDataCollector() {} | 291 LoadingDataCollector::~LoadingDataCollector() {} |
| 117 | 292 |
| 118 void LoadingDataCollector::RecordURLRequest( | 293 void LoadingDataCollector::RecordURLRequest(const URLRequestSummary& request) { |
| 119 const ResourcePrefetchPredictor::URLRequestSummary& request) { | 294 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 120 predictor_->RecordURLRequest(request); | 295 CHECK_EQ(request.resource_type, content::RESOURCE_TYPE_MAIN_FRAME); |
| 296 | |
| 297 CleanupAbandonedNavigations(request.navigation_id); | |
| 298 | |
| 299 // New empty navigation entry. | |
| 300 const GURL& main_frame_url = request.navigation_id.main_frame_url; | |
| 301 inflight_navigations_.emplace( | |
| 302 request.navigation_id, | |
| 303 base::MakeUnique<PageRequestSummary>(main_frame_url)); | |
| 121 } | 304 } |
| 122 | 305 |
| 123 void LoadingDataCollector::RecordURLResponse( | 306 void LoadingDataCollector::RecordURLResponse( |
| 124 const ResourcePrefetchPredictor::URLRequestSummary& response) { | 307 const URLRequestSummary& response) { |
| 125 predictor_->RecordURLResponse(response); | 308 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 309 | |
| 310 if (response.resource_type == content::RESOURCE_TYPE_MAIN_FRAME) | |
| 311 return; | |
| 312 | |
| 313 NavigationMap::const_iterator nav_it = | |
| 314 inflight_navigations_.find(response.navigation_id); | |
| 315 if (nav_it == inflight_navigations_.end()) | |
| 316 return; | |
| 317 auto& page_request_summary = *nav_it->second; | |
| 318 | |
| 319 if (!response.is_no_store) | |
| 320 page_request_summary.subresource_requests.push_back(response); | |
| 321 | |
| 322 if (config_.is_origin_learning_enabled) | |
| 323 page_request_summary.UpdateOrAddToOrigins(response); | |
| 126 } | 324 } |
| 127 | 325 |
| 128 void LoadingDataCollector::RecordURLRedirect( | 326 void LoadingDataCollector::RecordURLRedirect( |
| 129 const ResourcePrefetchPredictor::URLRequestSummary& response) { | 327 const URLRequestSummary& response) { |
| 130 predictor_->RecordURLRedirect(response); | 328 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 329 | |
| 330 if (response.resource_type == content::RESOURCE_TYPE_MAIN_FRAME) | |
| 331 OnMainFrameRedirect(response); | |
| 332 else | |
| 333 OnSubresourceRedirect(response); | |
| 131 } | 334 } |
| 132 | 335 |
| 133 void LoadingDataCollector::RecordMainFrameLoadComplete( | 336 void LoadingDataCollector::RecordMainFrameLoadComplete( |
| 134 const NavigationID& navigation_id) { | 337 const NavigationID& navigation_id) { |
| 135 predictor_->RecordMainFrameLoadComplete(navigation_id); | 338 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 339 | |
| 340 // WebContents can return an empty URL if the navigation entry corresponding | |
| 341 // to the navigation has not been created yet. | |
| 342 LOG(WARNING) << "navigation_id.main_frame_url.is_empty(): " | |
| 343 << navigation_id.main_frame_url.is_empty(); | |
| 344 if (navigation_id.main_frame_url.is_empty()) | |
| 345 return; | |
| 346 | |
| 347 NavigationMap::iterator nav_it = inflight_navigations_.find(navigation_id); | |
| 348 LOG(WARNING) << "nav_it == inflight_navigations_.end(): " | |
| 349 << (nav_it == inflight_navigations_.end()); | |
| 350 if (nav_it == inflight_navigations_.end()) | |
| 351 return; | |
| 352 | |
| 353 // Remove the navigation from the inflight navigations. | |
| 354 std::unique_ptr<PageRequestSummary> summary = std::move(nav_it->second); | |
| 355 inflight_navigations_.erase(nav_it); | |
| 356 | |
| 357 // Set before_first_contentful paint for each resource. | |
| 358 for (auto& request_summary : summary->subresource_requests) { | |
| 359 request_summary.before_first_contentful_paint = | |
| 360 request_summary.response_time < summary->first_contentful_paint; | |
| 361 } | |
| 362 | |
| 363 if (stats_collector_) | |
| 364 stats_collector_->RecordPageRequestSummary(*summary); | |
| 365 | |
| 366 LOG(WARNING) << "predictor_: " << predictor_; | |
|
alexilin
2017/06/22 19:39:05
Please, remove all logging statements.
trevordixon
2017/06/27 21:28:55
Done.
| |
| 367 if (predictor_) { | |
| 368 LOG(WARNING) << "CALLING"; | |
| 369 predictor_->RecordPageRequestSummary(std::move(summary)); | |
| 370 LOG(WARNING) << "CALLED"; | |
| 371 } | |
| 136 } | 372 } |
| 137 | 373 |
| 138 void LoadingDataCollector::RecordFirstContentfulPaint( | 374 void LoadingDataCollector::RecordFirstContentfulPaint( |
| 139 const NavigationID& navigation_id, | 375 const NavigationID& navigation_id, |
| 140 const base::TimeTicks& first_contentful_paint) { | 376 const base::TimeTicks& first_contentful_paint) { |
| 141 predictor_->RecordFirstContentfulPaint(navigation_id, first_contentful_paint); | 377 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 378 | |
| 379 NavigationMap::iterator nav_it = inflight_navigations_.find(navigation_id); | |
| 380 if (nav_it != inflight_navigations_.end()) | |
| 381 nav_it->second->first_contentful_paint = first_contentful_paint; | |
| 382 } | |
| 383 | |
| 384 void LoadingDataCollector::OnMainFrameRedirect( | |
| 385 const URLRequestSummary& response) { | |
| 386 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 387 | |
| 388 const GURL& main_frame_url = response.navigation_id.main_frame_url; | |
| 389 std::unique_ptr<PageRequestSummary> summary; | |
| 390 NavigationMap::iterator nav_it = | |
| 391 inflight_navigations_.find(response.navigation_id); | |
| 392 if (nav_it != inflight_navigations_.end()) { | |
| 393 summary = std::move(nav_it->second); | |
| 394 inflight_navigations_.erase(nav_it); | |
| 395 } | |
| 396 | |
| 397 // The redirect url may be empty if the URL was invalid. | |
| 398 if (response.redirect_url.is_empty()) | |
| 399 return; | |
| 400 | |
| 401 // If we lost the information about the first hop for some reason. | |
| 402 if (!summary) { | |
| 403 summary = base::MakeUnique<PageRequestSummary>(main_frame_url); | |
| 404 } | |
| 405 | |
| 406 // A redirect will not lead to another OnMainFrameRequest call, so record the | |
| 407 // redirect url as a new navigation id and save the initial url. | |
| 408 NavigationID navigation_id(response.navigation_id); | |
| 409 navigation_id.main_frame_url = response.redirect_url; | |
| 410 summary->main_frame_url = response.redirect_url; | |
| 411 inflight_navigations_.emplace(navigation_id, std::move(summary)); | |
| 412 } | |
| 413 | |
| 414 void LoadingDataCollector::OnSubresourceRedirect( | |
| 415 const URLRequestSummary& response) { | |
| 416 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 417 | |
| 418 if (!config_.is_origin_learning_enabled) | |
| 419 return; | |
| 420 | |
| 421 NavigationMap::const_iterator nav_it = | |
| 422 inflight_navigations_.find(response.navigation_id); | |
| 423 if (nav_it == inflight_navigations_.end()) | |
| 424 return; | |
| 425 auto& page_request_summary = *nav_it->second; | |
| 426 page_request_summary.UpdateOrAddToOrigins(response); | |
| 427 } | |
| 428 | |
| 429 void LoadingDataCollector::CleanupAbandonedNavigations( | |
| 430 const NavigationID& navigation_id) { | |
| 431 if (stats_collector_) | |
| 432 stats_collector_->CleanupAbandonedStats(); | |
| 433 | |
| 434 static const base::TimeDelta max_navigation_age = | |
| 435 base::TimeDelta::FromSeconds(config_.max_navigation_lifetime_seconds); | |
| 436 | |
| 437 base::TimeTicks time_now = base::TimeTicks::Now(); | |
| 438 for (NavigationMap::iterator it = inflight_navigations_.begin(); | |
| 439 it != inflight_navigations_.end();) { | |
| 440 if ((it->first.tab_id == navigation_id.tab_id) || | |
| 441 (time_now - it->first.creation_time > max_navigation_age)) { | |
| 442 inflight_navigations_.erase(it++); | |
| 443 } else { | |
| 444 ++it; | |
| 445 } | |
| 446 } | |
| 142 } | 447 } |
| 143 | 448 |
| 144 } // namespace predictors | 449 } // namespace predictors |
| OLD | NEW |