blob: 3e08a3adcb31ab22296a11757732fa5f4576b91b [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "chrome/browser/predictors/loading_data_collector.h"
#include "chrome/browser/predictors/loading_stats_collector.h"
#include "chrome/browser/predictors/resource_prefetch_predictor.h"
#include "chrome/browser/predictors/resource_prefetch_predictor_tables.h"
#include "chrome/browser/profiles/profile.h"
#include "components/history/core/browser/history_service.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/resource_request_info.h"
#include "content/public/common/resource_type.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_request.h"
#include "third_party/blink/public/common/mime_util/mime_util.h"
using content::BrowserThread;
namespace predictors {
namespace {
bool g_allow_port_in_urls = true;
// Sorted by decreasing likelihood according to HTTP archive.
const char* const kFontMimeTypes[] = {"font/woff2",
"application/x-font-woff",
"application/font-woff",
"application/font-woff2",
"font/x-woff",
"application/x-font-ttf",
"font/woff",
"font/ttf",
"application/x-font-otf",
"x-font/woff",
"application/font-sfnt",
"application/font-ttf"};
// Determines the ResourceType from the mime type, defaulting to the
// |fallback| if the ResourceType could not be determined.
content::ResourceType GetResourceTypeFromMimeType(
const std::string& mime_type,
content::ResourceType fallback) {
if (mime_type.empty()) {
return fallback;
} else if (blink::IsSupportedImageMimeType(mime_type)) {
return content::RESOURCE_TYPE_IMAGE;
} else if (blink::IsSupportedJavascriptMimeType(mime_type)) {
return content::RESOURCE_TYPE_SCRIPT;
} else if (net::MatchesMimeType("text/css", mime_type)) {
return content::RESOURCE_TYPE_STYLESHEET;
} else {
bool found =
std::any_of(std::begin(kFontMimeTypes), std::end(kFontMimeTypes),
[&mime_type](const std::string& mime) {
return net::MatchesMimeType(mime, mime_type);
});
if (found)
return content::RESOURCE_TYPE_FONT_RESOURCE;
}
return fallback;
}
// Determines the resource type from the declared one, falling back to MIME
// type detection when it is not explicit.
content::ResourceType GetResourceType(content::ResourceType resource_type,
const std::string& mime_type) {
// Restricts content::RESOURCE_TYPE_{PREFETCH,SUB_RESOURCE,XHR} to a small set
// of mime types, because these resource types don't communicate how the
// resources will be used.
if (resource_type == content::RESOURCE_TYPE_PREFETCH ||
resource_type == content::RESOURCE_TYPE_SUB_RESOURCE ||
resource_type == content::RESOURCE_TYPE_XHR) {
return GetResourceTypeFromMimeType(mime_type,
content::RESOURCE_TYPE_LAST_TYPE);
}
return resource_type;
}
} // namespace
OriginRequestSummary::OriginRequestSummary() = default;
OriginRequestSummary::OriginRequestSummary(const OriginRequestSummary& other) =
default;
OriginRequestSummary::~OriginRequestSummary() = default;
PageRequestSummary::PageRequestSummary(const GURL& i_main_frame_url)
: main_frame_url(i_main_frame_url),
initial_url(i_main_frame_url),
first_contentful_paint(base::TimeTicks::Max()) {}
PageRequestSummary::PageRequestSummary(const PageRequestSummary& other) =
default;
void PageRequestSummary::UpdateOrAddToOrigins(
const content::mojom::ResourceLoadInfo& resource_load_info) {
for (const auto& redirect_info : resource_load_info.redirect_info_chain)
UpdateOrAddToOrigins(redirect_info->url, redirect_info->network_info);
UpdateOrAddToOrigins(resource_load_info.url, resource_load_info.network_info);
}
void PageRequestSummary::UpdateOrAddToOrigins(
const GURL& url,
const content::mojom::CommonNetworkInfoPtr& network_info) {
GURL origin = url.GetOrigin();
if (!origin.is_valid())
return;
auto it = origins.find(origin);
if (it == origins.end()) {
OriginRequestSummary summary;
summary.origin = origin;
summary.first_occurrence = origins.size();
it = origins.insert({origin, summary}).first;
}
it->second.always_access_network |= network_info->always_access_network;
it->second.accessed_network |= network_info->network_accessed;
}
PageRequestSummary::~PageRequestSummary() = default;
// static
void LoadingDataCollector::SetAllowPortInUrlsForTesting(bool state) {
g_allow_port_in_urls = state;
}
LoadingDataCollector::LoadingDataCollector(
ResourcePrefetchPredictor* predictor,
predictors::LoadingStatsCollector* stats_collector,
const LoadingPredictorConfig& config)
: predictor_(predictor),
stats_collector_(stats_collector),
config_(config) {}
LoadingDataCollector::~LoadingDataCollector() = default;
void LoadingDataCollector::RecordStartNavigation(
const NavigationID& navigation_id) {
CleanupAbandonedNavigations(navigation_id);
// New empty navigation entry.
inflight_navigations_.emplace(
navigation_id,
std::make_unique<PageRequestSummary>(navigation_id.main_frame_url));
}
void LoadingDataCollector::RecordFinishNavigation(
const NavigationID& old_navigation_id,
const NavigationID& new_navigation_id,
bool is_error_page) {
if (is_error_page) {
inflight_navigations_.erase(old_navigation_id);
return;
}
// All subsequent events corresponding to this navigation will have
// |new_navigation_id|. Find the |old_navigation_id| entry in
// |inflight_navigations_| and change its key to the |new_navigation_id|.
std::unique_ptr<PageRequestSummary> summary;
auto nav_it = inflight_navigations_.find(old_navigation_id);
if (nav_it != inflight_navigations_.end()) {
summary = std::move(nav_it->second);
DCHECK_EQ(summary->main_frame_url, old_navigation_id.main_frame_url);
summary->main_frame_url = new_navigation_id.main_frame_url;
inflight_navigations_.erase(nav_it);
} else {
summary =
std::make_unique<PageRequestSummary>(new_navigation_id.main_frame_url);
summary->initial_url = old_navigation_id.main_frame_url;
}
inflight_navigations_.emplace(new_navigation_id, std::move(summary));
}
void LoadingDataCollector::RecordResourceLoadComplete(
const NavigationID& navigation_id,
const content::mojom::ResourceLoadInfo& resource_load_info) {
auto nav_it = inflight_navigations_.find(navigation_id);
if (nav_it == inflight_navigations_.end())
return;
if (!ShouldRecordResourceLoad(resource_load_info))
return;
auto& page_request_summary = *nav_it->second;
page_request_summary.UpdateOrAddToOrigins(resource_load_info);
}
void LoadingDataCollector::RecordMainFrameLoadComplete(
const NavigationID& navigation_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Initialize |predictor_| no matter whether the |navigation_id| is present in
// |inflight_navigations_|. This is the case for NTP and about:blank pages,
// for example.
if (predictor_)
predictor_->StartInitialization();
auto nav_it = inflight_navigations_.find(navigation_id);
if (nav_it == inflight_navigations_.end())
return;
// Remove the navigation from the inflight navigations.
std::unique_ptr<PageRequestSummary> summary = std::move(nav_it->second);
inflight_navigations_.erase(nav_it);
if (stats_collector_)
stats_collector_->RecordPageRequestSummary(*summary);
if (predictor_)
predictor_->RecordPageRequestSummary(std::move(summary));
}
void LoadingDataCollector::RecordFirstContentfulPaint(
const NavigationID& navigation_id,
const base::TimeTicks& first_contentful_paint) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto nav_it = inflight_navigations_.find(navigation_id);
if (nav_it != inflight_navigations_.end())
nav_it->second->first_contentful_paint = first_contentful_paint;
}
// static
bool LoadingDataCollector::ShouldRecordResourceLoad(
const content::mojom::ResourceLoadInfo& resource_load_info) {
const GURL& url = resource_load_info.url;
if (!url.is_valid() || !url.SchemeIsHTTPOrHTTPS())
return false;
if (!g_allow_port_in_urls && url.has_port())
return false;
return IsHandledResourceType(resource_load_info.resource_type,
resource_load_info.mime_type) &&
resource_load_info.method == "GET";
}
// static
bool LoadingDataCollector::IsHandledResourceType(
content::ResourceType resource_type,
const std::string& mime_type) {
content::ResourceType actual_resource_type =
GetResourceType(resource_type, mime_type);
return actual_resource_type == content::RESOURCE_TYPE_MAIN_FRAME ||
actual_resource_type == content::RESOURCE_TYPE_STYLESHEET ||
actual_resource_type == content::RESOURCE_TYPE_SCRIPT ||
actual_resource_type == content::RESOURCE_TYPE_IMAGE ||
actual_resource_type == content::RESOURCE_TYPE_FONT_RESOURCE;
}
void LoadingDataCollector::CleanupAbandonedNavigations(
const NavigationID& navigation_id) {
if (stats_collector_)
stats_collector_->CleanupAbandonedStats();
static const base::TimeDelta max_navigation_age =
base::TimeDelta::FromSeconds(config_.max_navigation_lifetime_seconds);
base::TimeTicks time_now = base::TimeTicks::Now();
for (auto it = inflight_navigations_.begin();
it != inflight_navigations_.end();) {
if ((it->first.tab_id == navigation_id.tab_id) ||
(time_now - it->first.creation_time > max_navigation_age)) {
inflight_navigations_.erase(it++);
} else {
++it;
}
}
}
} // namespace predictors