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
160
161
162
content / browser / reduce_accept_language / reduce_accept_language_throttle.cc [blame]
// 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/reduce_accept_language/reduce_accept_language_utils.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 "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(
ReduceAcceptLanguageControllerDelegate& accept_language_delegate,
OriginTrialsControllerDelegate* origin_trials_delegate,
FrameTreeNodeId frame_tree_node_id)
: accept_language_delegate_(accept_language_delegate),
origin_trials_delegate_(origin_trials_delegate),
frame_tree_node_id_(frame_tree_node_id) {
DCHECK(
base::FeatureList::IsEnabled(network::features::kReduceAcceptLanguage));
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;
}
ReduceAcceptLanguageUtils reduce_language_utils(*accept_language_delegate_);
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_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