blob: 45d454f82f277515698891d2a307f85e8c760796 [file] [log] [blame]
Hiroshige Hayashizaki386c5022025-08-12 14:43:371// Copyright 2025 The Chromium Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/browser/preloading/prefetch/prefetch_serving_handle.h"
6
7#include "base/metrics/histogram_macros.h"
8#include "base/notimplemented.h"
9#include "base/time/time.h"
10#include "content/browser/preloading/prefetch/prefetch_container.h"
11#include "content/browser/preloading/prefetch/prefetch_cookie_listener.h"
12#include "content/browser/preloading/prefetch/prefetch_probe_result.h"
13#include "content/browser/preloading/prefetch/prefetch_response_reader.h"
14#include "content/browser/preloading/prefetch/prefetch_single_redirect_hop.h"
15#include "content/browser/preloading/prefetch/prefetch_status.h"
16#include "content/public/browser/preloading.h"
17#include "url/gurl.h"
18
19namespace content {
20namespace {
21
22void RecordCookieCopyTimes(
23 const base::TimeTicks& cookie_copy_start_time,
24 const base::TimeTicks& cookie_read_end_and_write_start_time,
25 const base::TimeTicks& cookie_copy_end_time) {
26 UMA_HISTOGRAM_CUSTOM_TIMES(
27 "PrefetchProxy.AfterClick.Mainframe.CookieReadTime",
28 cookie_read_end_and_write_start_time - cookie_copy_start_time,
29 base::TimeDelta(), base::Seconds(5), 50);
30 UMA_HISTOGRAM_CUSTOM_TIMES(
31 "PrefetchProxy.AfterClick.Mainframe.CookieWriteTime",
32 cookie_copy_end_time - cookie_read_end_and_write_start_time,
33 base::TimeDelta(), base::Seconds(5), 50);
34 UMA_HISTOGRAM_CUSTOM_TIMES(
35 "PrefetchProxy.AfterClick.Mainframe.CookieCopyTime",
36 cookie_copy_end_time - cookie_copy_start_time, base::TimeDelta(),
37 base::Seconds(5), 50);
38}
39
40} // namespace
41
42PrefetchServingHandle::PrefetchServingHandle()
43 : PrefetchServingHandle(nullptr, 0) {}
44
45PrefetchServingHandle::PrefetchServingHandle(
46 base::WeakPtr<PrefetchContainer> prefetch_container,
47 size_t index_redirect_chain_to_serve)
48 : prefetch_container_(std::move(prefetch_container)),
49 index_redirect_chain_to_serve_(index_redirect_chain_to_serve) {}
50
51PrefetchServingHandle::PrefetchServingHandle(PrefetchServingHandle&&) = default;
52PrefetchServingHandle& PrefetchServingHandle::operator=(
53 PrefetchServingHandle&&) = default;
54PrefetchServingHandle::~PrefetchServingHandle() = default;
55
56PrefetchServingHandle PrefetchServingHandle::Clone() const {
57 return PrefetchServingHandle(prefetch_container_,
58 index_redirect_chain_to_serve_);
59}
60
61const std::vector<std::unique_ptr<PrefetchSingleRedirectHop>>&
62PrefetchServingHandle::redirect_chain() const {
63 CHECK(GetPrefetchContainer());
64 return GetPrefetchContainer()->redirect_chain(
65 base::PassKey<PrefetchServingHandle>());
66}
67
68PrefetchNetworkContext* PrefetchServingHandle::GetCurrentNetworkContextToServe()
69 const {
70 const PrefetchSingleRedirectHop& this_prefetch =
71 GetCurrentSingleRedirectHopToServe();
72 return GetPrefetchContainer()->GetNetworkContext(
73 this_prefetch.is_isolated_network_context_required_);
74}
75
76bool PrefetchServingHandle::HaveDefaultContextCookiesChanged() const {
77 const PrefetchSingleRedirectHop& this_prefetch =
78 GetCurrentSingleRedirectHopToServe();
79 if (this_prefetch.cookie_listener_) {
80 return this_prefetch.cookie_listener_->HaveCookiesChanged();
81 }
82 return false;
83}
84
85bool PrefetchServingHandle::HasIsolatedCookieCopyStarted() const {
86 switch (GetCurrentSingleRedirectHopToServe().cookie_copy_status_) {
87 case PrefetchSingleRedirectHop::CookieCopyStatus::kNotStarted:
88 return false;
89 case PrefetchSingleRedirectHop::CookieCopyStatus::kInProgress:
90 case PrefetchSingleRedirectHop::CookieCopyStatus::kCompleted:
91 return true;
92 }
93}
94
95bool PrefetchServingHandle::IsIsolatedCookieCopyInProgress() const {
96 switch (GetCurrentSingleRedirectHopToServe().cookie_copy_status_) {
97 case PrefetchSingleRedirectHop::CookieCopyStatus::kNotStarted:
98 case PrefetchSingleRedirectHop::CookieCopyStatus::kCompleted:
99 return false;
100 case PrefetchSingleRedirectHop::CookieCopyStatus::kInProgress:
101 return true;
102 }
103}
104
105void PrefetchServingHandle::OnIsolatedCookieCopyStart() const {
106 DCHECK(!IsIsolatedCookieCopyInProgress());
107
108 // We should temporarily ignore the cookie monitoring by
109 // `PrefetchCookieListener` during the isolated cookie is written to the
110 // default network context.
111 // `PrefetchCookieListener` should monitor whether the cookie is changed from
112 // what we stored in isolated network context when prefetching so that we can
113 // avoid serving the stale prefetched content. Currently
114 // `PrefetchCookieListener` will also catch isolated cookie copy as a cookie
115 // change. To handle this event as a false positive (as the cookie isn't
116 // changed from what we stored on prefetching), we can pause the lisner during
117 // copying, keeping the prefetch servable.
118 GetPrefetchContainer()->PauseAllCookieListeners();
119
120 GetCurrentSingleRedirectHopToServe().cookie_copy_status_ =
121 PrefetchSingleRedirectHop::CookieCopyStatus::kInProgress;
122
123 GetCurrentSingleRedirectHopToServe().cookie_copy_start_time_ =
124 base::TimeTicks::Now();
125}
126
127void PrefetchServingHandle::OnIsolatedCookiesReadCompleteAndWriteStart() const {
128 DCHECK(IsIsolatedCookieCopyInProgress());
129
130 GetCurrentSingleRedirectHopToServe().cookie_read_end_and_write_start_time_ =
131 base::TimeTicks::Now();
132}
133
134void PrefetchServingHandle::OnIsolatedCookieCopyComplete() const {
135 DCHECK(IsIsolatedCookieCopyInProgress());
136
137 // Resumes `PrefetchCookieListener` so that we can keep monitoring the
138 // cookie change for the prefetch, which may be served again.
139 GetPrefetchContainer()->ResumeAllCookieListeners();
140
141 const auto& this_prefetch = GetCurrentSingleRedirectHopToServe();
142
143 this_prefetch.cookie_copy_status_ =
144 PrefetchSingleRedirectHop::CookieCopyStatus::kCompleted;
145
146 if (this_prefetch.cookie_copy_start_time_.has_value() &&
147 this_prefetch.cookie_read_end_and_write_start_time_.has_value()) {
148 RecordCookieCopyTimes(
149 this_prefetch.cookie_copy_start_time_.value(),
150 this_prefetch.cookie_read_end_and_write_start_time_.value(),
151 base::TimeTicks::Now());
152 }
153
154 if (this_prefetch.on_cookie_copy_complete_callback_) {
155 std::move(this_prefetch.on_cookie_copy_complete_callback_).Run();
156 }
157}
158
159void PrefetchServingHandle::OnInterceptorCheckCookieCopy() const {
160 if (!GetCurrentSingleRedirectHopToServe().cookie_copy_start_time_) {
161 return;
162 }
163
164 UMA_HISTOGRAM_CUSTOM_TIMES(
165 "PrefetchProxy.AfterClick.Mainframe.CookieCopyStartToInterceptorCheck",
166 base::TimeTicks::Now() -
167 GetCurrentSingleRedirectHopToServe().cookie_copy_start_time_.value(),
168 base::TimeDelta(), base::Seconds(5), 50);
169}
170
171void PrefetchServingHandle::SetOnCookieCopyCompleteCallback(
172 base::OnceClosure callback) const {
173 DCHECK(IsIsolatedCookieCopyInProgress());
174
175 GetCurrentSingleRedirectHopToServe().on_cookie_copy_complete_callback_ =
176 std::move(callback);
177}
178
179std::pair<PrefetchRequestHandler, base::WeakPtr<ServiceWorkerClient>>
180PrefetchServingHandle::CreateRequestHandler() {
181 // Create a `PrefetchRequestHandler` from the current
182 // `PrefetchSingleRedirectHop` and its corresponding
183 // `PrefetchStreamingURLLoader`.
184 auto handler = GetCurrentSingleRedirectHopToServe()
185 .response_reader_->CreateRequestHandler();
186
187 // Advance the current `PrefetchSingleRedirectHop` position.
188 AdvanceCurrentURLToServe();
189
190 return handler;
191}
192
193bool PrefetchServingHandle::VariesOnCookieIndices() const {
194 return GetCurrentSingleRedirectHopToServe()
195 .response_reader_->VariesOnCookieIndices();
196}
197
198bool PrefetchServingHandle::MatchesCookieIndices(
199 base::span<const std::pair<std::string, std::string>> cookies) const {
200 return GetCurrentSingleRedirectHopToServe()
201 .response_reader_->MatchesCookieIndices(cookies);
202}
203
204void PrefetchServingHandle::OnPrefetchProbeResult(
205 PrefetchProbeResult probe_result) const {
206 GetPrefetchContainer()->SetProbeResult(base::PassKey<PrefetchServingHandle>(),
207 probe_result);
208
209 // TODO(https:crbug.com/433057364): Clean up the code below, so that we no
210 // longer need `TriggeringOutcomeFromStatusForServingHandle`.
211
212 // It's possible for the prefetch to fail (e.g., due to a network error) while
213 // the origin probe is running. We avoid overwriting the status in that case.
214 if (PrefetchContainer::TriggeringOutcomeFromStatusForServingHandle(
215 base::PassKey<PrefetchServingHandle>(), GetPrefetchStatus()) ==
216 PreloadingTriggeringOutcome::kFailure) {
217 return;
218 }
219
220 switch (probe_result) {
221 case PrefetchProbeResult::kNoProbing:
222 case PrefetchProbeResult::kDNSProbeSuccess:
223 case PrefetchProbeResult::kTLSProbeSuccess:
224 // Wait to update the prefetch status until the probe for the final
225 // redirect hop is a success.
226 if (index_redirect_chain_to_serve_ == redirect_chain().size() - 1) {
227 GetPrefetchContainer()->SetPrefetchStatus(
228 PrefetchStatus::kPrefetchResponseUsed);
229 }
230 break;
231 case PrefetchProbeResult::kDNSProbeFailure:
232 case PrefetchProbeResult::kTLSProbeFailure:
233 GetPrefetchContainer()->SetPrefetchStatus(
234 PrefetchStatus::kPrefetchNotUsedProbeFailed);
235 break;
236 default:
237 NOTIMPLEMENTED();
238 }
239}
240
241bool PrefetchServingHandle::DoesCurrentURLToServeMatch(const GURL& url) const {
242 CHECK(index_redirect_chain_to_serve_ >= 1);
243 return GetCurrentSingleRedirectHopToServe().url_ == url;
244}
245
246bool PrefetchServingHandle::IsEnd() const {
247 CHECK(index_redirect_chain_to_serve_ <= redirect_chain().size());
248 return index_redirect_chain_to_serve_ >= redirect_chain().size();
249}
250
251const PrefetchSingleRedirectHop&
252PrefetchServingHandle::GetCurrentSingleRedirectHopToServe() const {
253 CHECK(index_redirect_chain_to_serve_ >= 0 &&
254 index_redirect_chain_to_serve_ < redirect_chain().size());
255 return *redirect_chain()[index_redirect_chain_to_serve_];
256}
257
258const GURL& PrefetchServingHandle::GetCurrentURLToServe() const {
259 return GetCurrentSingleRedirectHopToServe().url_;
260}
261
262bool PrefetchServingHandle::IsIsolatedNetworkContextRequiredToServe() const {
263 const PrefetchSingleRedirectHop& this_prefetch =
264 GetCurrentSingleRedirectHopToServe();
265 return this_prefetch.is_isolated_network_context_required_;
266}
267
268base::WeakPtr<PrefetchResponseReader>
269PrefetchServingHandle::GetCurrentResponseReaderToServeForTesting() {
270 return GetCurrentSingleRedirectHopToServe().response_reader_->GetWeakPtr();
271}
272
273PrefetchServableState PrefetchServingHandle::GetServableState(
274 base::TimeDelta cacheable_duration) const {
275 return GetPrefetchContainer()->GetServableState(cacheable_duration);
276}
277
278bool PrefetchServingHandle::HasPrefetchStatus() const {
279 return GetPrefetchContainer()->HasPrefetchStatus();
280}
281
282PrefetchStatus PrefetchServingHandle::GetPrefetchStatus() const {
283 return GetPrefetchContainer()->GetPrefetchStatus();
284}
285
286} // namespace content