1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/reduce_accept_language/reduce_accept_language_throttle.h"
#include <algorithm>
#include "base/metrics/histogram_functions.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/public/browser/origin_trials_controller_delegate.h"
#include "content/public/browser/reduce_accept_language_controller_delegate.h"
#include "content/public/browser/reduce_accept_language_utils.h"
#include "services/network/public/cpp/features.h"
#include "third_party/blink/public/common/loader/url_loader_throttle.h"
namespace content {
namespace {
// Metrics on the count of requests restarted or the reason why not restarted
// when reducing accept-language HTTP header. These values are persisted to
// logs. Entries should not be renumbered and numeric values should never be
// reused.
enum class AcceptLanguageNegotiationRestart {
kNavigationStarted = 0,
kAvailLanguageAndContentLanguageHeaderPresent = 1,
kServiceWorkerPreloadRequest = 2,
kNavigationRestarted = 3,
kMaxValue = kNavigationRestarted,
};
// Logging the metric related to reduce accept language header and
// corresponding restart metric when language negotiation happens.
void LogAcceptLanguageStatus(AcceptLanguageNegotiationRestart status) {
base::UmaHistogramEnumeration(
"ReduceAcceptLanguage.AcceptLanguageNegotiationRestart", status);
}
} // namespace
ReduceAcceptLanguageThrottle::ReduceAcceptLanguageThrottle(
ReduceAcceptLanguageUtils reduce_accept_language_utils,
OriginTrialsControllerDelegate* origin_trials_delegate,
FrameTreeNodeId frame_tree_node_id)
: reduce_accept_language_utils_(std::move(reduce_accept_language_utils)),
origin_trials_delegate_(origin_trials_delegate),
frame_tree_node_id_(frame_tree_node_id) {
LogAcceptLanguageStatus(AcceptLanguageNegotiationRestart::kNavigationStarted);
}
ReduceAcceptLanguageThrottle::~ReduceAcceptLanguageThrottle() = default;
void ReduceAcceptLanguageThrottle::WillStartRequest(
network::ResourceRequest* request,
bool* defer) {
last_request_url_ = request->url;
initial_request_headers_ = request->headers;
}
void ReduceAcceptLanguageThrottle::BeforeWillRedirectRequest(
net::RedirectInfo* redirect_info,
const network::mojom::URLResponseHead& response_head,
RestartWithURLReset* restart_with_url_reset,
std::vector<std::string>* to_be_removed_request_headers,
net::HttpRequestHeaders* modified_request_headers,
net::HttpRequestHeaders* modified_cors_exempt_request_headers) {
// For redirect case, checking if a redirect response should result in a
// restart of the last requested URL with a better negotiation language,
// rather than following the redirect.
//
// Suppose origin A returns a response redirecting to origin B,
// `last_request_url_` will be A, and `response_head` contains
// Content-Language and Avail-Language headers suggesting whether we should
// follow the redirect response. If the response shows it supports one of
// user's other preferred language and needs to restart. We will restart the
// request to A with the new preferred language and expect A responses a
// different redirect. For detail example, see
// https://github.com/Tanych/accept-language/issues/3.
MaybeRestartWithLanguageNegotiation(response_head, restart_with_url_reset);
// Update the url with the redirect new url to make sure last_request_url_
// with be the response_url.
last_request_url_ = redirect_info->new_url;
}
void ReduceAcceptLanguageThrottle::BeforeWillProcessResponse(
const GURL& response_url,
const network::mojom::URLResponseHead& response_head,
RestartWithURLReset* restart_with_url_reset) {
DCHECK_EQ(response_url, last_request_url_);
MaybeRestartWithLanguageNegotiation(response_head, restart_with_url_reset);
}
void ReduceAcceptLanguageThrottle::MaybeRestartWithLanguageNegotiation(
const network::mojom::URLResponseHead& response_head,
RestartWithURLReset* restart_with_url_reset) {
// For responses that don't contains content-language and avail-language
// header, we skip language negotiation for them since we don't know whether
// we can get a better representation.
if (!response_head.parsed_headers ||
!response_head.parsed_headers->content_language ||
!response_head.parsed_headers->avail_language) {
return;
}
LogAcceptLanguageStatus(AcceptLanguageNegotiationRestart::
kAvailLanguageAndContentLanguageHeaderPresent);
// Skip restart when it's a service worker navigation preload request,
// otherwise request for the same origin can't guarantee restart at most once.
// All URLLoaderThrottles instances get recreated and `restarted_origins_` get
// reset when requests are the service worker preload requests. This could
// lead to browsers resending too many requests if
// `ParseAndPersistAcceptLanguageForNavigation` returns need to restart.
if (response_head.did_service_worker_navigation_preload) {
LogAcceptLanguageStatus(
AcceptLanguageNegotiationRestart::kServiceWorkerPreloadRequest);
return;
}
url::Origin last_request_origin = url::Origin::Create(last_request_url_);
if (!ReduceAcceptLanguageUtils::OriginCanReduceAcceptLanguage(
last_request_origin)) {
return;
}
FrameTreeNode* frame_tree_node =
FrameTreeNode::GloballyFindByID(frame_tree_node_id_);
// Skip if origin opted-in ReduceAcceptLanguage deprecation origin trial.
if (ReduceAcceptLanguageUtils::CheckDisableReduceAcceptLanguageOriginTrial(
last_request_url_, frame_tree_node, origin_trials_delegate_)) {
return;
}
// Only restart once per-Origin (per navigation).
if (!restarted_origins_.insert(last_request_origin).second)
return;
bool need_restart =
reduce_accept_language_utils_.ReadAndPersistAcceptLanguageForNavigation(
last_request_origin, initial_request_headers_,
response_head.parsed_headers);
// Only restart if the initial accept language doesn't match content language.
if (need_restart) {
LogAcceptLanguageStatus(
AcceptLanguageNegotiationRestart::kNavigationRestarted);
// RestartWithURLResetAndFlags will restart from the original requested URL.
// Ideally, we expect only restart last requested URL to avoid unnecessary
// restarts starting from the original request URL. However, for cross
// origin redirects, it won't pass the SiteForCookies equivalent check on
// URLLoader when using RestartWithFlags.
*restart_with_url_reset = RestartWithURLReset(true);
return;
}
}
} // namespace content
|