blob: ad2c247327b2280b077fa88312cf626b99dcaa97 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/client_hints/critical_client_hints_throttle.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "content/browser/client_hints/client_hints.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/client_hints_controller_delegate.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "net/http/http_util.h"
#include "services/network/public/cpp/client_hints.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/mojom/parsed_headers.mojom-forward.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "third_party/blink/public/common/loader/url_loader_throttle.h"
#include "third_party/blink/public/platform/web_client_hints_type.h"
namespace {
void LogCriticalCHStatus(CriticalCHRestart status) {
base::UmaHistogramEnumeration("ClientHints.CriticalCHRestart", status);
}
void LogAcceptCHFrameStatus(AcceptCHFrameRestart status) {
base::UmaHistogramEnumeration("ClientHints.AcceptCHFrame", status);
}
} // namespace
namespace content {
CriticalClientHintsThrottle::CriticalClientHintsThrottle(
BrowserContext* context,
ClientHintsControllerDelegate* client_hint_delegate,
int frame_tree_node_id)
: context_(context),
client_hint_delegate_(client_hint_delegate),
frame_tree_node_id_(frame_tree_node_id) {
LogCriticalCHStatus(CriticalCHRestart::kNavigationStarted);
}
void CriticalClientHintsThrottle::WillProcessResponse(
const GURL& response_url,
network::mojom::URLResponseHead* response_head,
bool* defer) {
if (critical_redirect_ ||
!base::FeatureList::IsEnabled(features::kCriticalClientHint)) {
return;
}
if (!response_head->parsed_headers ||
!response_head->parsed_headers->accept_ch ||
!response_head->parsed_headers->critical_ch)
return;
// Ensure that only hints in the accept-ch header are examined
blink::WebEnabledClientHints hints;
for (const auto& hint : response_head->parsed_headers->accept_ch.value())
hints.SetIsEnabled(hint, true);
std::vector<network::mojom::WebClientHintsType> critical_hints;
for (const auto& hint : response_head->parsed_headers->critical_ch.value())
if (hints.IsEnabled(hint))
critical_hints.push_back(hint);
if (critical_hints.empty())
return;
LogCriticalCHStatus(CriticalCHRestart::kHeaderPresent);
net::HttpRequestHeaders modified_headers;
if (ShouldRestartWithHints(response_url, critical_hints, modified_headers)) {
critical_redirect_ = true;
LogCriticalCHStatus(CriticalCHRestart::kNavigationRestarted);
delegate_->RestartWithModifiedHeadersNow(modified_headers);
}
}
void CriticalClientHintsThrottle::HandleAcceptCHFrameReceived(
const GURL& url,
const std::vector<network::mojom::WebClientHintsType>& accept_ch_frame) {
if (accept_ch_frame_redirect_ ||
!base::FeatureList::IsEnabled(network::features::kAcceptCHFrame)) {
return;
}
LogAcceptCHFrameStatus(AcceptCHFrameRestart::kFramePresent);
net::HttpRequestHeaders modified_headers;
if (ShouldRestartWithHints(url, accept_ch_frame, modified_headers)) {
accept_ch_frame_redirect_ = true;
LogAcceptCHFrameStatus(AcceptCHFrameRestart::kNavigationRestarted);
delegate_->RestartWithModifiedHeadersNow(modified_headers);
}
}
bool CriticalClientHintsThrottle::ShouldRestartWithHints(
const GURL& response_url,
const std::vector<network::mojom::WebClientHintsType>& hints,
net::HttpRequestHeaders& modified_headers) {
FrameTreeNode* frame_tree_node =
FrameTreeNode::GloballyFindByID(frame_tree_node_id_);
if (!AreCriticalHintsMissing(response_url, frame_tree_node,
client_hint_delegate_, hints)) {
return false;
}
client_hint_delegate_->SetAdditionalClientHints(hints);
// TODO(crbug.com/1195034): If the frame tree node doesn't have an associated
// navigation_request (e.g. a service worker request) it might not override
// the user agent correctly.
if (frame_tree_node) {
AddNavigationRequestClientHintsHeaders(
response_url, &modified_headers, context_, client_hint_delegate_,
frame_tree_node->navigation_request()->GetIsOverridingUserAgent(),
frame_tree_node);
} else {
AddPrefetchNavigationRequestClientHintsHeaders(
response_url, &modified_headers, context_, client_hint_delegate_,
/*is_ua_override_on=*/false, /*is_javascript_enabled=*/true);
}
client_hint_delegate_->ClearAdditionalClientHints();
return true;
}
} // namespace content