blob: 23af7a89b7b502b5ae8e79f6fee9e67cdb995e32 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_PUBLIC_COMMON_LOADER_THROTTLING_URL_LOADER_H_
#define THIRD_PARTY_BLINK_PUBLIC_COMMON_LOADER_THROTTLING_URL_LOADER_H_
#include <memory>
#include <optional>
#include <string_view>
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/unique_receiver_set.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/mojom/accept_ch_frame_observer.mojom.h"
#include "services/network/public/mojom/url_loader.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom-forward.h"
#include "services/network/public/mojom/url_response_head.mojom-forward.h"
#include "services/network/public/mojom/web_client_hints_types.mojom.h"
#include "third_party/blink/public/common/common_export.h"
#include "third_party/blink/public/common/loader/url_loader_throttle.h"
namespace base {
class SequencedTaskRunner;
}
namespace blink {
// ThrottlingURLLoader is a wrapper around the
// network::mojom::URLLoader[Factory] interfaces. It applies a list of
// URLLoaderThrottle instances which could defer, resume restart or cancel the
// URL loading. If the Mojo connection fails during the request it is canceled
// with net::ERR_ABORTED.
class BLINK_COMMON_EXPORT ThrottlingURLLoader
: public network::mojom::URLLoaderClient {
public:
// A delegate to override the handling actions to some of the URL loading
// stages from the calls of `network::mojom::URLLoaderClient`, received by
// `client_receiver_`. For the stages not provided by this delegate, e.g.
// `OnTransferSizeUpdated()`, `OnReceiveEarlyHints()`, `OnUploadProgress()`,
// they follow the default implementation within ThrottlingURLLoader.
// See also
// https://docs.google.com/document/d/1RKPgoLBrrLZBPn01XtwHJiLlH9rA7nIRXQJIR7BUqJA/edit#heading=h.y1og20bzkuf7
class ClientReceiverDelegate {
public:
// Called at the end of `ThrottlingURLLoader::OnReceiveRedirect()`.
// It allows the delegate to decide how to proceed with the redirect instead
// of simply calling `forwarding_client_`.
virtual void EndReceiveRedirect(
const net::RedirectInfo& redirect_info,
network::mojom::URLResponseHeadPtr response_head) = 0;
// Called at the beginning of `ThrottlingURLLoader::OnReceiveResponse()`,
// and overrides all of its behavior.
// This method receive the same params as if they are coming directly from
// network::mojom::URLLoaderClient.
virtual void OnReceiveResponse(
network::mojom::URLResponseHeadPtr response_head,
mojo::ScopedDataPipeConsumerHandle body,
std::optional<mojo_base::BigBuffer> cached_metadata) = 0;
// Called at the beginning of `ThrottlingURLLoader::OnComplete()`, and
// overrides all of its behavior.
// This method receive the same params as if they are coming directly from
// network::mojom::URLLoaderClient.
virtual void OnComplete(
const network::URLLoaderCompletionStatus& status) = 0;
// Called when a loading stage is cancelled by throttles or due to mojo
// disconnection. `status` is internally constructed by ThrottlingURLLoader
// when the latter decides to terminate.
virtual void CancelWithStatus(
const network::URLLoaderCompletionStatus& status) = 0;
};
// Reason used when resetting the URLLoader to follow a redirect.
static const char kFollowRedirectReason[];
// |url_request| can be mutated by this function, and doesn't need to stay
// alive after calling this function.
//
// |client| must stay alive during the lifetime of the returned object. Please
// note that the request may not start immediately since it could be deferred
// by throttles.
//
// |client_receiver_delegate| if provided, must stay alive during the lifetime
// of the returned object. When set, the following behaviors of the returned
// object will be overridden by the delegate:
// - `OnReceiveRedirect()` will execute custom logic at its end, instead of
// calling `forwarding_client_`.
// - `OnReceiveResponse()` and `OnComplete()` will be entirely replaced by the
// delegate's ones.
// - `CancelWithExtendedError()` will execute custom logic at its end, instead
// of calling `forwarding_client_`.
// In addition, |client_receiver_delegate| is not orthogonal to |client|, in
// that the latter is still necessary for the returned object to run the
// throttle-related logic.
// Note that once |client_receiver_delegate| is set, the relevant throttle
// callbacks like BeforeWillProcessResponse(), WillProcessResponse(), and
// WillOnCompleteWithError(), will not be triggered by the returned object.
//
// Note that `CreateLoaderAndStart()` (or equivalently `Start()`) can notify
// `client` immediately & synchronously (e.g. via
// `URLLoaderThrottle::Delegate::CancelWithError()` inside
// `WillStartRequest()`) even before returning the
// `std::unique_ptr<ThrottlingURLLoader>`. To avoid this for the short-term,
// call `CreateLoader()` then `Start()`.
//
// TODO(https://crbug.com/433324863): Figure out a longer-term solution.
static std::unique_ptr<ThrottlingURLLoader> CreateLoaderAndStart(
scoped_refptr<network::SharedURLLoaderFactory> factory,
std::vector<std::unique_ptr<URLLoaderThrottle>> throttles,
int32_t request_id,
uint32_t options,
network::ResourceRequest* url_request,
network::mojom::URLLoaderClient* client,
const net::NetworkTrafficAnnotationTag& traffic_annotation,
scoped_refptr<base::SequencedTaskRunner> task_runner,
std::optional<std::vector<std::string>> cors_exempt_header_list =
std::nullopt,
ClientReceiverDelegate* client_receiver_delegate = nullptr,
const std::vector<int>* initiator_origin_trial_features = nullptr);
// See the comments at `CreateLoaderAndStart()` above for parameters.
static std::unique_ptr<ThrottlingURLLoader> CreateLoader(
std::vector<std::unique_ptr<URLLoaderThrottle>> throttles,
network::mojom::URLLoaderClient* client,
const net::NetworkTrafficAnnotationTag& traffic_annotation,
ClientReceiverDelegate* client_receiver_delegate);
void Start(scoped_refptr<network::SharedURLLoaderFactory> factory,
int32_t request_id,
uint32_t options,
network::ResourceRequest* url_request,
scoped_refptr<base::SequencedTaskRunner> task_runner,
std::optional<std::vector<std::string>> cors_exempt_header_list,
const std::vector<int>* initiator_origin_trial_features);
ThrottlingURLLoader(const ThrottlingURLLoader&) = delete;
ThrottlingURLLoader& operator=(const ThrottlingURLLoader&) = delete;
~ThrottlingURLLoader() override;
// Follows a redirect, calling CreateLoaderAndStart() on the factory. This
// is useful if the factory uses different loaders for different URLs.
void FollowRedirectForcingRestart();
// This should be called if the loader will be recreated to follow a redirect
// instead of calling FollowRedirect(). This can be used if a loader is
// implementing similar logic to FollowRedirectForcingRestart(). If this is
// called, a future request for the redirect should be guaranteed to be sent
// with the same request_id.
// `removed_headers`, `modified_headers` and `modified_cors_exempt_headers`
// will be merged to corresponding members in the ThrottlingURLLoader, and
// then apply updates against `resource_request`.
void ResetForFollowRedirect(
network::ResourceRequest& resource_request,
const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers,
const net::HttpRequestHeaders& modified_cors_exempt_headers);
void FollowRedirect(
const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers,
const net::HttpRequestHeaders& modified_cors_exempt_headers);
void SetPriority(net::RequestPriority priority, int32_t intra_priority_value);
// Disconnect the forwarding URLLoaderClient and the URLLoader. Returns the
// datapipe endpoints.
network::mojom::URLLoaderClientEndpointsPtr Unbind();
void CancelWithError(int error_code, std::string_view custom_reason);
void CancelWithExtendedError(int error_code,
int extended_reason_code,
std::string_view custom_reason);
bool response_intercepted() const { return response_intercepted_; }
// Indicates a restart did occur due to a Critical-CH HTTP Header.
void DidRestartForCriticalClientHint() {
critical_ch_restart_time_ = base::TimeTicks::Now();
}
private:
class ForwardingThrottleDelegate;
ThrottlingURLLoader(
std::vector<std::unique_ptr<URLLoaderThrottle>> throttles,
network::mojom::URLLoaderClient* client,
const net::NetworkTrafficAnnotationTag& traffic_annotation,
ClientReceiverDelegate* client_receiver_delegate);
void StartNow();
void RestartWithURLResetNow();
// Processes the result of a URLLoaderThrottle call. If it's deferred, adds
// the throttle to the blocking set and updates |*should_defer| accordingly.
// Returns |true| if the request should continue to be processed (regardless
// of whether it's been deferred) or |false| if it's been cancelled.
bool HandleThrottleResult(URLLoaderThrottle* throttle,
bool throttle_deferred = false,
bool* should_defer = nullptr);
// Stops a given throttle from deferring the request. If this was not the last
// deferring throttle, the request remains deferred. Otherwise it resumes
// progress.
void StopDeferringForThrottle(URLLoaderThrottle* throttle);
// network::mojom::URLLoaderClient implementation:
void OnReceiveEarlyHints(network::mojom::EarlyHintsPtr early_hints) override;
void OnReceiveResponse(
network::mojom::URLResponseHeadPtr response_head,
mojo::ScopedDataPipeConsumerHandle body,
std::optional<mojo_base::BigBuffer> cached_metadata) override;
void OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
network::mojom::URLResponseHeadPtr response_head) override;
void OnUploadProgress(int64_t current_position,
int64_t total_size,
OnUploadProgressCallback ack_callback) override;
void OnTransferSizeUpdated(int32_t transfer_size_diff) override;
void OnComplete(const network::URLLoaderCompletionStatus& status) override;
void OnClientConnectionError();
void Resume();
void SetPriority(net::RequestPriority priority);
void UpdateRequestHeaders(network::ResourceRequest& resource_request);
void UpdateDeferredResponseHead(
network::mojom::URLResponseHeadPtr new_response_head,
mojo::ScopedDataPipeConsumerHandle body);
void InterceptResponse(
mojo::PendingRemote<network::mojom::URLLoader> new_loader,
mojo::PendingReceiver<network::mojom::URLLoaderClient>
new_client_receiver,
mojo::PendingRemote<network::mojom::URLLoader>* original_loader,
mojo::PendingReceiver<network::mojom::URLLoaderClient>*
original_client_receiver,
mojo::ScopedDataPipeConsumerHandle* body);
// Disconnects the client connection and releases the URLLoader.
void DisconnectClient(std::string_view custom_description);
enum DeferredStage {
DEFERRED_NONE,
DEFERRED_START,
DEFERRED_REDIRECT,
DEFERRED_RESPONSE,
};
const char* GetStageNameForHistogram(DeferredStage stage);
DeferredStage deferred_stage_ = DEFERRED_NONE;
bool loader_completed_ = false;
bool did_receive_response_ = false;
struct ThrottleEntry {
ThrottleEntry(ThrottlingURLLoader* loader,
std::unique_ptr<URLLoaderThrottle> the_throttle);
ThrottleEntry(ThrottleEntry&& other);
ThrottleEntry& operator=(ThrottleEntry&& other);
~ThrottleEntry();
std::unique_ptr<URLLoaderThrottle> throttle;
std::unique_ptr<ForwardingThrottleDelegate> delegate;
};
std::vector<ThrottleEntry> throttles_;
std::map<URLLoaderThrottle*, /*start=*/base::Time> deferring_throttles_;
// NOTE: This may point to a native implementation (instead of a Mojo proxy
// object). And it is possible that the implementation of |forwarding_client_|
// destroys this object synchronously when this object is calling into it.
const raw_ptr<network::mojom::URLLoaderClient, DanglingUntriaged>
forwarding_client_;
mojo::Remote<network::mojom::URLLoader> url_loader_;
mojo::Receiver<network::mojom::URLLoaderClient> client_receiver_{this};
// A delegate to override some of the message handling for `client_receiver_`.
// If provided, its lifetime must be >= `this`.
raw_ptr<ClientReceiverDelegate> client_receiver_delegate_;
struct StartInfo {
StartInfo(
scoped_refptr<network::SharedURLLoaderFactory> in_url_loader_factory,
int32_t in_request_id,
uint32_t in_options,
network::ResourceRequest* in_url_request,
scoped_refptr<base::SequencedTaskRunner> in_task_runner,
std::optional<std::vector<std::string>> in_cors_exempt_header_list);
~StartInfo();
const scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory;
const int32_t request_id;
const uint32_t options;
network::ResourceRequest url_request;
// |task_runner| is used to set up |client_receiver_|.
const scoped_refptr<base::SequencedTaskRunner> task_runner;
const std::optional<std::vector<std::string>> cors_exempt_header_list;
};
// Holds any info needed to start or restart the request. Used when start is
// deferred or when FollowRedirectForcingRestart() is called.
std::unique_ptr<StartInfo> start_info_;
struct ResponseInfo {
explicit ResponseInfo(network::mojom::URLResponseHeadPtr in_response_head);
~ResponseInfo();
network::mojom::URLResponseHeadPtr response_head;
};
// Set if response is deferred.
std::unique_ptr<ResponseInfo> response_info_;
mojo::ScopedDataPipeConsumerHandle body_;
std::optional<mojo_base::BigBuffer> cached_metadata_;
struct RedirectInfo {
RedirectInfo(const net::RedirectInfo& in_redirect_info,
network::mojom::URLResponseHeadPtr in_response_head);
~RedirectInfo();
net::RedirectInfo redirect_info;
network::mojom::URLResponseHeadPtr response_head;
};
// Set if redirect is deferred.
std::unique_ptr<RedirectInfo> redirect_info_;
struct PriorityInfo {
PriorityInfo(net::RequestPriority in_priority,
int32_t in_intra_priority_value);
net::RequestPriority priority;
int32_t intra_priority_value;
};
// Set if request is deferred and SetPriority() is called.
std::unique_ptr<PriorityInfo> priority_info_;
// If
// - A throttle changed the URL in WillStartRequest(), or
// - `RestartWithURLReset` is set to true in `BeforeWillProcessResponse()` or
// `BeforeWillRedirectRequest()`,
// a synthesized redirect to the modified URL is dispatched.
// 1. `throttle_will_start_redirect_url_` is set when the synthesized redirect
// is scheduled.
// 2. The actual redirect is started in the first half of `StartNow()`.
// 3. `throttle_will_start_redirect_url_` is reset when the redirect is done.
GURL throttle_will_start_redirect_url_;
// Set if a throttle changed the URL in WillRedirectRequest.
// Only supported with the network service.
GURL throttle_will_redirect_redirect_url_;
// The first URL seen by the throttle.
GURL original_url_;
const net::NetworkTrafficAnnotationTag traffic_annotation_;
uint32_t inside_delegate_calls_ = 0;
// The latest request URL from where we expect a response
GURL response_url_;
bool response_intercepted_ = false;
std::vector<std::string> removed_headers_;
net::HttpRequestHeaders modified_headers_;
net::HttpRequestHeaders modified_cors_exempt_headers_;
base::TimeTicks critical_ch_restart_time_;
base::WeakPtrFactory<ThrottlingURLLoader> weak_factory_{this};
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_PUBLIC_COMMON_LOADER_THROTTLING_URL_LOADER_H_