Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 1 | // Copyright 2023 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 | #ifndef CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_RESPONSE_READER_H_ |
| 6 | #define CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_RESPONSE_READER_H_ |
| 7 | |
| 8 | #include "base/time/time.h" |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 9 | #include "content/browser/preloading/prefetch/prefetch_streaming_url_loader_common_types.h" |
| 10 | #include "content/common/content_export.h" |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 11 | #include "mojo/public/cpp/bindings/receiver_set.h" |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 12 | #include "mojo/public/cpp/bindings/remote_set.h" |
Jeremy Roman | 43c200c | 2024-06-04 15:51:24 | [diff] [blame] | 13 | #include "net/http/http_cookie_indices.h" |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 14 | #include "services/network/public/mojom/url_loader.mojom.h" |
| 15 | #include "services/network/public/mojom/url_response_head.mojom-forward.h" |
| 16 | |
Hiroshige Hayashizaki | 57872f3 | 2025-07-09 11:52:02 | [diff] [blame] | 17 | namespace ukm::builders { |
| 18 | class PrefetchProxy_PrefetchedResource; |
| 19 | } // namespace ukm::builders |
| 20 | |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 21 | namespace content { |
| 22 | |
Hiroshige Hayashizaki | 57872f3 | 2025-07-09 11:52:02 | [diff] [blame] | 23 | class PrefetchContainer; |
Hiroshige Hayashizaki | 583b496 | 2025-08-19 18:58:26 | [diff] [blame] | 24 | class PrefetchDataPipeTee; |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 25 | class PrefetchStreamingURLLoader; |
Hiroshige Hayashizaki | 055871f2 | 2025-03-14 03:27:38 | [diff] [blame] | 26 | class ServiceWorkerClient; |
Hiroshige Hayashizaki | 0830e18 | 2025-03-14 02:03:06 | [diff] [blame] | 27 | class ServiceWorkerMainResourceHandle; |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 28 | |
| 29 | // `PrefetchResponseReader` stores the prefetched data needed for serving, and |
| 30 | // serves URLLoaderClients (`serving_url_loader_clients_`). One |
| 31 | // `PrefetchResponseReader` corresponds to one |
| 32 | // `PrefetchContainer::SinglePrefetch`, i.e. one redirect hop. |
| 33 | // |
| 34 | // A sequences of events are received from `PrefetchStreamingURLLoader` and |
| 35 | // served to each of `serving_url_loader_clients_`. |
| 36 | // |
| 37 | // `PrefetchResponseReader` is kept alive by: |
| 38 | // - `PrefetchContainer::SinglePrefetch::response_reader_` |
| 39 | // as long as `PrefetchContainer` is alive, |
| 40 | // - `PrefetchResponseReader::self_pointer_` |
| 41 | // while it is serving to its `mojom::URLLoaderClient`, or |
| 42 | // - The `PrefetchRequestHandler` returned by `CreateRequestHandler()` |
| 43 | // until it is called. |
| 44 | // |
Alison Gale | 770f3fc | 2024-04-27 00:39:58 | [diff] [blame] | 45 | // TODO(crbug.com/40064891): Currently at most one client per |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 46 | // `PrefetchResponseReader` is allowed due to other servablility conditions. |
| 47 | // Upcoming CLs will enable multiple clients/navigation requests per |
| 48 | // `PrefetchResponseReader` behind a flag. |
| 49 | class CONTENT_EXPORT PrefetchResponseReader final |
| 50 | : public network::mojom::URLLoader, |
| 51 | public base::RefCounted<PrefetchResponseReader> { |
| 52 | public: |
Hiroshige Hayashizaki | 40270b7 | 2025-07-23 01:52:01 | [diff] [blame] | 53 | PrefetchResponseReader(base::OnceClosure on_determined_head_callback, |
| 54 | OnPrefetchResponseCompletedCallback |
| 55 | on_prefetch_response_completed_callback); |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 56 | |
| 57 | void SetStreamingURLLoader( |
| 58 | base::WeakPtr<PrefetchStreamingURLLoader> streaming_url_loader); |
| 59 | base::WeakPtr<PrefetchStreamingURLLoader> GetStreamingLoader() const; |
| 60 | |
| 61 | // Asynchronously release `self_pointer_` if eligible. Note that `this` might |
| 62 | // be still be kept alive by others even after that. |
| 63 | void MaybeReleaseSoonSelfPointer(); |
| 64 | |
| 65 | // Adds events from the methods with the same names in |
| 66 | // `PrefetchStreamingURLLoader` to `event_queue_` and existing |
| 67 | // `serving_url_loader_clients_`. |
| 68 | void OnReceiveEarlyHints(network::mojom::EarlyHintsPtr early_hints); |
Hiroshige Hayashizaki | 0830e18 | 2025-03-14 02:03:06 | [diff] [blame] | 69 | void OnReceiveResponse( |
| 70 | std::optional<PrefetchErrorOnResponseReceived> status, |
| 71 | network::mojom::URLResponseHeadPtr head, |
| 72 | mojo::ScopedDataPipeConsumerHandle body, |
| 73 | std::unique_ptr<ServiceWorkerMainResourceHandle> service_worker_handle); |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 74 | void HandleRedirect(PrefetchRedirectStatus redirect_status, |
| 75 | const net::RedirectInfo& redirect_info, |
| 76 | network::mojom::URLResponseHeadPtr redirect_head); |
| 77 | void OnTransferSizeUpdated(int32_t transfer_size_diff); |
| 78 | void OnComplete(network::URLLoaderCompletionStatus completion_status); |
| 79 | |
| 80 | // Creates a request handler to serve the response of the prefetch. |
Hiroshige Hayashizaki | 8f74d22b | 2023-09-28 04:48:08 | [diff] [blame] | 81 | // |
| 82 | // `CreateRequestHandler()` is responsible for the final check for servability |
| 83 | // and can return a null PrefetchRequestHandler if the final check fails (even |
| 84 | // if `GetServableState()` previously returned `kServable`). |
| 85 | // |
| 86 | // The caller is responsible for: |
| 87 | // - Cookie-related checks and processing. |
| 88 | // For example, checking `HaveDefaultContextCookiesChanged()` is false and |
| 89 | // copying isolated cookies if needed. |
| 90 | // `PrefetchResponseReader::CreateRequestHandler()`, |
| 91 | // `PrefetchResponseReader::Servable()` nor |
| 92 | // `PrefetchContainer::GetServableState()` don't perform cookie-related |
| 93 | // checks. |
| 94 | // - Checking `Servable()`/`GetServableState()`. |
| 95 | // `cacheable_duration` is checked only there. |
Hiroshige Hayashizaki | 055871f2 | 2025-03-14 03:27:38 | [diff] [blame] | 96 | std::pair<PrefetchRequestHandler, base::WeakPtr<ServiceWorkerClient>> |
| 97 | CreateRequestHandler(); |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 98 | |
| 99 | bool Servable(base::TimeDelta cacheable_duration) const; |
| 100 | bool IsWaitingForResponse() const; |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 101 | const network::mojom::URLResponseHead* GetHead() const { return head_.get(); } |
| 102 | |
Jeremy Roman | 43c200c | 2024-06-04 15:51:24 | [diff] [blame] | 103 | // True if this response had Vary: Cookie (or Vary: *), and a Cookie-Indices |
| 104 | // header also applies. |
| 105 | bool VariesOnCookieIndices() const; |
| 106 | |
| 107 | // True if the request cookies `cookies` match those originally used when the |
| 108 | // prefetch request was made, to the extent required by Cookie-Indices. |
| 109 | // Do not call this if |VariesOnCookieIndices()| returns false. |
| 110 | bool MatchesCookieIndices( |
| 111 | base::span<const std::pair<std::string, std::string>> cookies) const; |
| 112 | |
Hiroshige Hayashizaki | 57872f3 | 2025-07-09 11:52:02 | [diff] [blame] | 113 | void RecordOnPrefetchContainerDestroyed( |
| 114 | base::PassKey<PrefetchContainer>, |
| 115 | ukm::builders::PrefetchProxy_PrefetchedResource& builder) const; |
| 116 | |
Hiroshige Hayashizaki | 6b7035c0 | 2025-07-18 21:06:32 | [diff] [blame] | 117 | // Valid state transitions (which imply valid event sequences) are: |
| 118 | // - Redirect: `kStarted` -> `kRedirectHandled` |
| 119 | // - Non-redirect: `kStarted` -> `kResponseReceived` -> `kCompleted` |
| 120 | // - Failure: `kStarted` -> `kFailed` |
| 121 | // `kStarted` -> `kFailedRedirect` |
| 122 | // `kStarted` -> `kFailedResponseReceived` -> `kFailed` |
| 123 | // `kStarted` -> `kResponseReceived` -> `kFailed` |
| 124 | // Optional `OnReceiveEarlyHints()` and `OnTransferSizeUpdated()` events can |
| 125 | // be received in any non-final states. |
| 126 | enum class LoadState { |
| 127 | // Initial state, not yet receiving a redirect nor non-redirect response. |
| 128 | kStarted, |
| 129 | |
| 130 | // [Final] A redirect response is received (`HandleRedirect()` is called). |
| 131 | // This is a final state because we always switch to a new |
| 132 | // `PrefetchResponseReader` on redirects. |
| 133 | kRedirectHandled, |
| 134 | |
| 135 | // [servable] A non-redirect successful response is received |
| 136 | // (`OnReceiveResponse()` is called with `servable` = true). |
| 137 | kResponseReceived, |
| 138 | |
| 139 | // A non-redirect failed response is received (`OnReceiveResponse()` is |
| 140 | // called with `servable` = false). |
| 141 | kFailedResponseReceived, |
| 142 | |
| 143 | // [Final, servable] Successful completion (`OnComplete(net::OK)` is called |
| 144 | // after `kResponseReceived`. |
| 145 | kCompleted, |
| 146 | |
| 147 | // [Final] Failed completion (`OnComplete()` is called, either with |
| 148 | // non-`net::OK`, or after `kFailedResponseReceived`). |
| 149 | kFailed, |
| 150 | |
| 151 | // [Final] Failed redirects. |
| 152 | kFailedRedirect |
| 153 | }; |
| 154 | |
| 155 | LoadState load_state() const { return load_state_; } |
| 156 | |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 157 | base::WeakPtr<PrefetchResponseReader> GetWeakPtr() { |
| 158 | return weak_ptr_factory_.GetWeakPtr(); |
| 159 | } |
| 160 | |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 161 | private: |
| 162 | // Identifies a client in `serving_url_loader_clients_`. |
| 163 | using ServingUrlLoaderClientId = mojo::RemoteSetElementId; |
| 164 | |
| 165 | friend class base::RefCounted<PrefetchResponseReader>; |
kenoss | ccc3589 | 2025-06-09 13:09:47 | [diff] [blame] | 166 | // This is necessary because `PrefetchContainerObserver` emulates a callback |
| 167 | // that we will provide in the future. |
| 168 | // |
| 169 | // TODO(crbug.com/400761083): Remove it. |
| 170 | friend class PrefetchContainerObserver; |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 171 | |
| 172 | ~PrefetchResponseReader() override; |
| 173 | |
| 174 | void BindAndStart( |
| 175 | mojo::ScopedDataPipeConsumerHandle body, |
| 176 | const network::ResourceRequest& resource_request, |
| 177 | mojo::PendingReceiver<network::mojom::URLLoader> receiver, |
| 178 | mojo::PendingRemote<network::mojom::URLLoaderClient> client); |
| 179 | |
| 180 | // Adds an event to the queue. |
| 181 | // The callbacks are called in-order for each of |
| 182 | // `serving_url_loader_clients_`, regardless of whether events are added |
| 183 | // before or after clients are added. |
Hiroshige Hayashizaki | 33d92a0a1 | 2025-03-13 19:07:58 | [diff] [blame] | 184 | using EventCallback = base::RepeatingCallback<void(ServingUrlLoaderClientId)>; |
| 185 | void AddEventToQueue(EventCallback callback); |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 186 | // Sends all stored events in `event_queue_` to the client. |
| 187 | // Called when a new client (identified by `client_id_`) is added. |
| 188 | void RunEventQueue(ServingUrlLoaderClientId client_id); |
| 189 | |
| 190 | // Helper functions to send the appropriate events to a client. |
| 191 | void ForwardCompletionStatus(ServingUrlLoaderClientId client_id); |
| 192 | void ForwardEarlyHints(const network::mojom::EarlyHintsPtr& early_hints, |
| 193 | ServingUrlLoaderClientId client_id); |
| 194 | void ForwardTransferSizeUpdate(int32_t transfer_size_diff, |
| 195 | ServingUrlLoaderClientId client_id); |
| 196 | void ForwardRedirect(const net::RedirectInfo& redirect_info, |
| 197 | const network::mojom::URLResponseHeadPtr&, |
| 198 | ServingUrlLoaderClientId client_id); |
| 199 | void ForwardResponse(ServingUrlLoaderClientId client_id); |
| 200 | |
| 201 | // network::mojom::URLLoader |
| 202 | void FollowRedirect( |
| 203 | const std::vector<std::string>& removed_headers, |
| 204 | const net::HttpRequestHeaders& modified_headers, |
| 205 | const net::HttpRequestHeaders& modified_cors_exempt_headers, |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 206 | const std::optional<GURL>& new_url) override; |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 207 | void SetPriority(net::RequestPriority priority, |
| 208 | int32_t intra_priority_value) override; |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 209 | |
| 210 | void OnServingURLLoaderMojoDisconnect(); |
| 211 | |
| 212 | PrefetchStreamingURLLoaderStatus GetStatusForRecording() const; |
| 213 | |
Jeremy Roman | 43c200c | 2024-06-04 15:51:24 | [diff] [blame] | 214 | // Stores info from the response head that will be needed later, before it is |
| 215 | // stored into `head_` (for non-redirect responses) or `event_queue_` (or |
| 216 | // redirect responses). |
| 217 | void StoreInfoFromResponseHead(const network::mojom::URLResponseHead& head); |
| 218 | |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 219 | // All URLLoader events are queued up here. |
Hiroshige Hayashizaki | 33d92a0a1 | 2025-03-13 19:07:58 | [diff] [blame] | 220 | std::vector<EventCallback> event_queue_; |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 221 | |
| 222 | // The status of the event queue. |
| 223 | enum class EventQueueStatus { |
| 224 | kNotRunning, |
| 225 | kRunning, |
| 226 | }; |
| 227 | EventQueueStatus event_queue_status_{EventQueueStatus::kNotRunning}; |
| 228 | |
Hiroshige Hayashizaki | 3ba25886 | 2025-03-14 16:06:39 | [diff] [blame] | 229 | // Always access/update through `load_state()` and |
| 230 | // `SetLoadStateAndAddEventToQueue()` below, to avoid unintentional state |
| 231 | // changes and missing related callbacks on state changes. |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 232 | LoadState load_state_{LoadState::kStarted}; |
Hiroshige Hayashizaki | 3ba25886 | 2025-03-14 16:06:39 | [diff] [blame] | 233 | |
Hiroshige Hayashizaki | 3ba25886 | 2025-03-14 16:06:39 | [diff] [blame] | 234 | void SetLoadStateAndAddEventToQueue(LoadState new_load_state, |
| 235 | EventCallback callback); |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 236 | |
Hiroshige Hayashizaki | 40270b7 | 2025-07-23 01:52:01 | [diff] [blame] | 237 | // Called when transitioned for the first time to a state other than |
| 238 | // `kStarted` nor `kRedirectHandled`. |
| 239 | // |
| 240 | // This should be always called once for the entire `PrefetchResponseReader`s |
| 241 | // for a given `PrefetchContainer`. |
| 242 | // TODO(https://crbug.com/400761083): This isn't called for: |
| 243 | // - unexpected mojo disconnection cases (See |
| 244 | // `PrefetchStreamingURLLoaderTest.UnexpectedUrlLoaderDisconnect`). |
| 245 | base::OnceClosure on_determined_head_callback_; |
| 246 | |
| 247 | // Called when transitioned to `kCompleted` or `kFailed`. |
| 248 | // This is called after `on_determined_head_callback_` at most once for the |
| 249 | // entire `PrefetchResponseReader`s for a given `PrefetchContainer`. |
| 250 | // TODO(https://crbug.com/400761083): This isn't called for: |
| 251 | // - `kFailedRedirect` (See |
| 252 | // `PrefetchStreamingURLLoaderTest.IneligibleRedirect`) or |
| 253 | // - unexpected mojo disconnection cases (See |
| 254 | // `PrefetchStreamingURLLoaderTest.UnexpectedUrlLoaderDisconnect`). |
| 255 | OnPrefetchResponseCompletedCallback on_prefetch_response_completed_callback_; |
| 256 | |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 257 | // Used for UMA recording. |
Alison Gale | 770f3fc | 2024-04-27 00:39:58 | [diff] [blame] | 258 | // TODO(crbug.com/40064891): we might want to adapt these flags and UMA |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 259 | // semantics for multiple client settings, but so far we don't have any |
| 260 | // specific plans. |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 261 | std::optional<PrefetchErrorOnResponseReceived> failure_reason_; |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 262 | bool served_before_completion_{false}; |
| 263 | bool served_after_completion_{false}; |
| 264 | bool should_record_metrics_{true}; |
| 265 | |
Jeremy Roman | 43c200c | 2024-06-04 15:51:24 | [diff] [blame] | 266 | // If present, this includes the sorted and unique names of the cookies which |
| 267 | // were specified in the Cookie-Indices header, and a hash of their values as |
| 268 | // obtained from `net::HashCookieIndices`. This is not set unless the Vary |
| 269 | // header also specified Cookie (or *). |
| 270 | // |
| 271 | // As one quirk, we presently still don't vary on cookies if Vary is specified |
| 272 | // and Cookie-Indices isn't, both because that was the prior behavior and |
| 273 | // because doing so requires having the precise string value of the header |
| 274 | // (including whitespace). |
| 275 | struct CookieIndicesInfo { |
| 276 | CookieIndicesInfo(); |
| 277 | ~CookieIndicesInfo(); |
| 278 | |
| 279 | std::vector<std::string> cookie_names; |
| 280 | net::CookieIndicesHash expected_hash; |
| 281 | }; |
| 282 | std::optional<CookieIndicesInfo> cookie_indices_; |
| 283 | |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 284 | // The prefetched data and metadata. Not set for a redirect response. |
| 285 | network::mojom::URLResponseHeadPtr head_; |
Hiroshige Hayashizaki | c0b6aea | 2023-10-17 21:56:43 | [diff] [blame] | 286 | scoped_refptr<PrefetchDataPipeTee> body_tee_; |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 287 | std::optional<network::URLLoaderCompletionStatus> completion_status_; |
Kouhei Ueno | 8c5784d | 2024-07-03 03:34:17 | [diff] [blame] | 288 | // Recorded on `OnComplete` and used to check if the prefetch data is still |
| 289 | // fresh for use. |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 290 | std::optional<base::TimeTicks> response_complete_time_; |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 291 | |
| 292 | // Only used temporarily to plumb the body `BindAndStart()` to |
| 293 | // `ForwardResponse()`. |
| 294 | mojo::ScopedDataPipeConsumerHandle forward_body_; |
| 295 | |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 296 | // The URL loader clients that will serve the prefetched data. |
| 297 | mojo::ReceiverSet<network::mojom::URLLoader> serving_url_loader_receivers_; |
| 298 | mojo::RemoteSet<network::mojom::URLLoaderClient> serving_url_loader_clients_; |
| 299 | |
| 300 | // Set when this manages its own lifetime. |
| 301 | scoped_refptr<PrefetchResponseReader> self_pointer_; |
| 302 | |
| 303 | base::WeakPtr<PrefetchStreamingURLLoader> streaming_url_loader_; |
| 304 | |
Hiroshige Hayashizaki | 0830e18 | 2025-03-14 02:03:06 | [diff] [blame] | 305 | // TODO(https://crbug.com/40947546): Currently redirects are not supported for |
| 306 | // ServiceWorker-controlled prefetches and thus we don't care about alignment |
| 307 | // between `PrefetchResponseReader`, `PrefetchStreamingURLLoader` and |
| 308 | // `PrefetchContainer` in terms of `ServiceWorkerMainResourceHandle`. |
| 309 | std::unique_ptr<ServiceWorkerMainResourceHandle> service_worker_handle_; |
| 310 | |
Hiroshige Hayashizaki | bf37abc3 | 2023-09-13 10:02:36 | [diff] [blame] | 311 | base::WeakPtrFactory<PrefetchResponseReader> weak_ptr_factory_{this}; |
| 312 | }; |
| 313 | |
| 314 | } // namespace content |
| 315 | |
| 316 | #endif // CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_RESPONSE_READER_H_ |