blob: ab524442aea9fb69bede7c1098a4c1d6341ae628 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_LOADER_KEEP_ALIVE_URL_LOADER_H_
#define CONTENT_BROWSER_LOADER_KEEP_ALIVE_URL_LOADER_H_
#include <stdint.h>
#include <memory>
#include <queue>
#include <string>
#include <string_view>
#include <vector>
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/weak_ptr.h"
#include "base/timer/timer.h"
#include "base/types/pass_key.h"
#include "content/common/content_export.h"
#include "content/public/browser/weak_document_ptr.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/url_loader.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "third_party/blink/public/common/loader/throttling_url_loader.h"
#include "third_party/blink/public/mojom/loader/fetch_later.mojom.h"
#include "url/gurl.h"
namespace network {
class SharedURLLoaderFactory;
}
namespace blink {
class URLLoaderThrottle;
}
namespace content {
class BrowserContext;
class KeepAliveAttributionRequestHelper;
class KeepAliveURLBrowserTestBase;
class KeepAliveURLLoaderService;
class PolicyContainerHost;
class RenderFrameHostImpl;
class WeakDocumentPtr;
// A URLLoader for loading a fetch keepalive request via the browser process,
// including requests generated from the following JS API calls:
// - fetch(..., {keepalive: true})
// - navigator.sendBeacon(...)
// - fetchLater(...)
//
// To load a keepalive request initiated by a renderer, this loader performs the
// following logic:
//
// 1. In ctor, stores request data sent from a renderer.
// 2. In `Start()`, asks the network service to start loading the request, and
// then runs throttles to perform checks.
// 3. Handles request loading results from the network service, i.e. from the
// remote of `url_loader_` (blink::ThrottlingURLLoader):
// A. If it is `OnReceiveRedirect()`, this loader performs checks and runs
// throttles, and then asks the network service to proceed with redirects
// without interacting with renderer. The redirect params are stored for
// later use.
// B. If it is `OnReceiveResponse()` or `OnComplete()`, this loader does not
// process response. Instead, it calls `ForwardURLLoad()` to begin to
// forward previously saved mojom::URLLoaderClient calls to the renderer,
// if the renderer is still alive; Otherwise, terminating this loader.
// C. If a throttle asynchronously asks to cancel the request, similar to B,
// the previously stored calls will be forwarded to the renderer.
// D. The renderer's response to `ForwardURLLoad()` may be any of
// mojom::URLLoader calls, in which they should continue forwarding by
// calling `ForwardURLLoader()` again.
//
// See the "Longer Redirect Chain" section of the Design Doc for an example
// call sequence diagram.
//
// This class must only be constructed by `KeepAliveURLLoaderService`.
//
// The lifetime of an instance is roughly equal to the lifetime of a keepalive
// request, which may surpass the initiator renderer's lifetime.
//
// * Design Doc:
// https://docs.google.com/document/d/1ZzxMMBvpqn8VZBZKnb7Go8TWjnrGcXuLS_USwVVRUvY
// * Mojo Connections:
// https://docs.google.com/document/d/1RKPgoLBrrLZBPn01XtwHJiLlH9rA7nIRXQJIR7BUqJA/edit#heading=h.y1og20bzkuf7
class CONTENT_EXPORT KeepAliveURLLoader
: public network::mojom::URLLoader,
public blink::ThrottlingURLLoader::ClientReceiverDelegate,
public blink::mojom::FetchLaterLoader {
public:
// A callback type to delete this loader immediately on triggered.
using OnDeleteCallback = base::OnceCallback<void(void)>;
// A callback type to return URLLoaderThrottles to be used by this loader.
using URLLoaderThrottlesGetter = base::RepeatingCallback<
std::vector<std::unique_ptr<blink::URLLoaderThrottle>>(void)>;
// Must only be constructed by a `KeepAliveURLLoaderService`.
//
// Note that calling ctor does not mean loading the request. `Start()` must
// also be called subsequently.
//
// `resource_request` must be a keepalive request from a renderer.
// `forwarding_client` should handle request loading results from the network
// service if it is still connected.
// `delete_callback` is a callback to delete this object.
// `policy_container_host` must not be null.
// `weak_document_ptr` should point to the document that initiates
// `resource_request`.
KeepAliveURLLoader(
int32_t request_id,
uint32_t options,
const network::ResourceRequest& resource_request,
mojo::PendingRemote<network::mojom::URLLoaderClient> forwarding_client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
scoped_refptr<network::SharedURLLoaderFactory> network_loader_factory,
scoped_refptr<PolicyContainerHost> policy_container_host,
WeakDocumentPtr weak_document_ptr,
BrowserContext* browser_context,
URLLoaderThrottlesGetter throttles_getter,
base::PassKey<KeepAliveURLLoaderService>,
std::unique_ptr<KeepAliveAttributionRequestHelper>
attribution_request_helper);
~KeepAliveURLLoader() override;
// Not copyable.
KeepAliveURLLoader(const KeepAliveURLLoader&) = delete;
KeepAliveURLLoader& operator=(const KeepAliveURLLoader&) = delete;
// Sets the callback to be invoked on errors which require closing the pipe.
// Running `on_delete_callback` will immediately delete `this`.
//
// Not an argument to constructor because the Mojo ReceiverId needs to be
// bound to the callback, but can only be obtained after creating `this`.
// Must be called immediately after creating a KeepAliveLoader.
void set_on_delete_callback(OnDeleteCallback on_delete_callback);
// Kicks off loading the request, including prepare for requests, and setting
// up communication with network service.
// This method must only be called when `IsStarted()` is false.
void Start();
// Called when the receiver of URLLoader implemented by this is disconnected.
void OnURLLoaderDisconnected();
// Called when the `browser_context_` is shutting down.
void Shutdown();
base::WeakPtr<KeepAliveURLLoader> GetWeakPtr();
// For testing only:
// TODO(crbug.com/40261761): Figure out alt to not rely on this in test.
class TestObserver : public base::RefCountedThreadSafe<TestObserver> {
public:
virtual void OnReceiveRedirectForwarded(KeepAliveURLLoader* loader) = 0;
virtual void OnReceiveRedirectProcessed(KeepAliveURLLoader* loader) = 0;
virtual void OnReceiveResponse(KeepAliveURLLoader* loader) = 0;
virtual void OnReceiveResponseForwarded(KeepAliveURLLoader* loader) = 0;
virtual void OnReceiveResponseProcessed(KeepAliveURLLoader* loader) = 0;
virtual void OnComplete(
KeepAliveURLLoader* loader,
const network::URLLoaderCompletionStatus& completion_status) = 0;
virtual void OnCompleteForwarded(
KeepAliveURLLoader* loader,
const network::URLLoaderCompletionStatus& completion_status) = 0;
virtual void OnCompleteProcessed(
KeepAliveURLLoader* loader,
const network::URLLoaderCompletionStatus& completion_status) = 0;
protected:
virtual ~TestObserver() = default;
friend class base::RefCountedThreadSafe<TestObserver>;
};
void SetObserverForTesting(scoped_refptr<TestObserver> observer);
private:
// Returns true if request loading has been started, i.e. `Start()` has been
// called. Otherwise, returns false by default.
bool IsStarted() const;
// Returns a pointer to the RenderFrameHostImpl of the request initiator
// document if it is still alive. Otherwise, returns nullptr;
RenderFrameHostImpl* GetInitiator() const;
// Receives actions from renderer.
// `network::mojom::URLLoader` overrides:
void FollowRedirect(
const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers,
const net::HttpRequestHeaders& modified_cors_exempt_headers,
const std::optional<GURL>& new_url) override;
void SetPriority(net::RequestPriority priority,
int intra_priority_value) override;
// Receives actions from network service, loaded by `url_loader_`.
// `blink::ThrottlingURLLoader::ClientReceiverDelegate` overrides:
void OnReceiveResponse(
network::mojom::URLResponseHeadPtr head,
mojo::ScopedDataPipeConsumerHandle body,
std::optional<mojo_base::BigBuffer> cached_metadata) override;
// Called after `url_loader_` has run throttles for OnReceiveRedirect().
void EndReceiveRedirect(const net::RedirectInfo& redirect_info,
network::mojom::URLResponseHeadPtr head) override;
void OnComplete(
const network::URLLoaderCompletionStatus& completion_status) override;
// Called when `url_loader_` is cancelled by throttles, or Browser<-Network
// pipe is disconnected.
void CancelWithStatus(
const network::URLLoaderCompletionStatus& completion_status) override;
// `blink::mojom::FetchLaterLoader` overrides:
void SendNow() override;
void Cancel() override;
// Whether `OnReceiveResponse()` has been called.
bool HasReceivedResponse() const;
// Forwards the stored chain of redriects, response, completion status to the
// renderer that initiates this loader, such that the renderer knows what URL
// the response come from when parsing the response.
//
// This method must be called when `IsRendererConnected()` is true.
// This method may be called more than one time until it deletes `this`.
// WARNING: Calling this method may result in the deletion of `this`.
// See also the "Proposed Call Sequences After Migration" section in
// https://docs.google.com/document/d/1ZzxMMBvpqn8VZBZKnb7Go8TWjnrGcXuLS_USwVVRUvY/edit?pli=1#heading=h.d006i46pmq9
void ForwardURLLoad();
// Tells if `ForwardURLLoad()` has ever been called.
bool IsForwardURLLoadStarted() const;
// Tells if this loader is still able to forward actions to the
// URLLoaderClient in renderer.
bool IsRendererConnected() const;
// Tells if this loader is constructed for a FetchLater request.
bool IsFetchLater() const;
// Returns net::OK to allow following the redirect. Otherwise, returns
// corresponding error code.
net::Error WillFollowRedirect(const net::RedirectInfo& redirect_info) const;
// Called when `forwarding_client_`, Browser->Renderer pipe, is disconnected.
void OnForwardingClientDisconnected();
// Called when `disconnected_loader_timer_` is fired.
void OnDisconnectedLoaderTimerFired();
void DeleteSelf();
friend class KeepAliveURLBrowserTestBase;
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
//
// Must remain in sync with FetchKeepAliveRequestMetricType in
// tools/metrics/histograms/enums.xml.
// LINT.IfChange
enum class FetchKeepAliveRequestMetricType {
kFetch = 0,
kBeacon = 1, // not used here.
kPing = 2,
kReporting = 3,
kAttribution = 4, // not used here.
kBackgroundFetchIcon = 5,
kMaxValue = kBackgroundFetchIcon,
};
// LINT.ThenChange(//third_party/blink/renderer/platform/loader/fetch/fetch_utils.cc)
// Logs in-browser keepalive request related metrics.
// Note that fetchLater requests will be skipped by this method.
// https://docs.google.com/document/d/15MHmkf_SN2S9WYra060yEChgjs3pgZW--aHUuiG8Y1Q/edit
void LogFetchKeepAliveRequestMetric(std::string_view request_state_name);
// The ID to identify the request being loaded by this loader.
const int32_t request_id_;
// The ID to identify the request used by DevTools
const std::string devtools_request_id_;
// A bitfield of the options of the request being loaded.
// See services/network/public/mojom/url_loader_factory.mojom.
const uint32_t options_;
// The request to be loaded by this loader.
// Set in the constructor and updated when redirected.
network::ResourceRequest resource_request_;
// See
// https://docs.google.com/document/d/1RKPgoLBrrLZBPn01XtwHJiLlH9rA7nIRXQJIR7BUqJA/edit#heading=h.y1og20bzkuf7
class ForwardingClient;
// Browser -> Renderer connection:
//
// Connects to the receiver URLLoaderClient implemented in the renderer.
// It is the client that this loader may forward the URLLoader response from
// the network service, i.e. message received by `url_loader_`, to.
// It may be disconnected if the renderer is dead. In such case, subsequent
// URLLoader response may be handled in browser.
const std::unique_ptr<ForwardingClient> forwarding_client_;
// Browser <- Renderer connection:
// Timer used for triggering cleaning up `this` after the receiver is
// disconnected from the remote of URLLoader in the renderer.
base::OneShotTimer disconnected_loader_timer_;
// The NetworkTrafficAnnotationTag for the request being loaded.
net::MutableNetworkTrafficAnnotationTag traffic_annotation_;
// A refptr to the URLLoaderFactory implementation that can actually create a
// URLLoader. An extra refptr is required here to support deferred loading.
scoped_refptr<network::SharedURLLoaderFactory> network_loader_factory_;
struct StoredURLLoad;
// Stores the chain of redriects, response, and completion status, such that
// they can be forwarded to renderer after handled in browser.
// See also `ForwardURLLoad()`.
std::unique_ptr<StoredURLLoad> stored_url_load_;
// A refptr to keep the `PolicyContainerHost` from the RenderFrameHost that
// initiates this loader alive until `this` is destroyed.
// It is never null.
scoped_refptr<PolicyContainerHost> policy_container_host_;
// Points to the document that initiates this loader.
// It may become null at any moment whenever the RenderFrameHost it points to
// is deleted or navigates to a different document. See its classdoc for more
// details.
WeakDocumentPtr weak_document_ptr_;
// The BrowserContext that initiates this loader.
// It is ensured to outlive this because it owns KeepAliveURLLoaderService
// which owns this loader.
const raw_ptr<BrowserContext> browser_context_;
// Tells if this loader has been started or not.
bool is_started_ = false;
// A callback to delete this loader object and clean up resource.
OnDeleteCallback on_delete_callback_;
// Records the initial request URL to help veryfing redirect request.
const GURL initial_url_;
// Records the latest URL to help veryfing redirect request.
GURL last_url_;
// A callback to obtain URLLoaderThrottle for this loader to start loading.
URLLoaderThrottlesGetter throttles_getter_;
// Connects bidirectionally with the network service, and may forward to
// the renderer:
// * Network <- (URLLoader) `url_loader_` <-(`this`)<- Renderer
// This object forwards the URL loading request to the network, and may
// forward further actions from the renderer.
// * Network -> (URLLoaderClient) `url_loader_` ->(`forwarding_client_`)->
// Renderer:
// It uses throttles from `throttles_getter_` to process the loading results
// from a receiver of URLLoaderClient connected with network, and may
// (1) continue interact with the network or (2) forward the processing
// results to the renderer via `forwarding_client_` if the request has
// completed.
// See also
// https://docs.google.com/document/d/1RKPgoLBrrLZBPn01XtwHJiLlH9rA7nIRXQJIR7BUqJA/edit#heading=h.y1og20bzkuf7
std::unique_ptr<blink::ThrottlingURLLoader> url_loader_;
// Request helper responsible for processing Attribution Reporting API
// operations (https://github.com/WICG/attribution-reporting-api). Only set if
// the request is related to attribution. When set, responses (redirects &
// final) handled by the loader will be forwarded to the helper.
std::unique_ptr<KeepAliveAttributionRequestHelper>
attribution_request_helper_;
// For testing only:
// Not owned.
scoped_refptr<TestObserver> observer_for_testing_ = nullptr;
// Must be the last field.
base::WeakPtrFactory<KeepAliveURLLoader> weak_ptr_factory_{this};
};
} // namespace content
#endif // CONTENT_BROWSER_LOADER_KEEP_ALIVE_URL_LOADER_H_