blob: 620e6397095ea5e561a72d6ef139802b95a16ece [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_PRELOADING_PREFETCH_PREFETCH_TEST_UTIL_INTERNAL_H_
#define CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_TEST_UTIL_INTERNAL_H_
#include <memory>
#include <ostream>
#include <string>
#include "base/test/metrics/histogram_tester.h"
#include "base/test/test_future.h"
#include "content/browser/preloading/prefetch/prefetch_service.h"
#include "content/browser/preloading/prefetch/prefetch_status.h"
#include "content/browser/preloading/prefetch/prefetch_streaming_url_loader_common_types.h"
#include "content/public/test/preloading_test_util.h"
#include "content/public/test/test_renderer_host.h"
#include "content/test/test_content_browser_client.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "mojo/public/cpp/system/data_pipe_drainer.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "services/network/public/mojom/early_hints.mojom.h"
#include "services/network/public/mojom/url_loader.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom-forward.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace base {
class RunLoop;
} // namespace base
namespace content {
using OnPrefetchCompleteTestFuture =
base::test::TestFuture<network::URLLoaderCompletionStatus>;
using OnPrefetchReceiveRedirectTestFuture =
base::test::TestFuture<net::RedirectInfo,
network::mojom::URLResponseHeadPtr>;
struct NotReachedTagForTests {};
// Used to specify the test behavior on a specific callback.
// - If `NotReachedTagForTests()`: the callback shouldn't be called.
// - Otherwise: `T` (either `RunLoop*` or `TestFuture*`) is unblocked when the
// callback is called, if non-nullptr.
template <typename T>
using NotReachedTagForTestsOr = std::variant<T, NotReachedTagForTests>;
// The centralized helper to create `PrefetchStreamingURLLoader` and its
// corresponding `PrefetchResponseReader`, without `PrefetchContainer`.
std::tuple<scoped_refptr<PrefetchResponseReader>,
base::WeakPtr<PrefetchStreamingURLLoader>>
CreateStreamingURLLoaderWithoutPrefetchContainerForTests(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
const network::ResourceRequest& prefetch_request,
NotReachedTagForTestsOr<base::RunLoop*> on_response_received,
NotReachedTagForTestsOr<OnPrefetchCompleteTestFuture*> on_complete,
NotReachedTagForTestsOr<OnPrefetchReceiveRedirectTestFuture*>
on_receive_redirect,
NotReachedTagForTestsOr<base::RunLoop*> on_head_received,
std::optional<PrefetchErrorOnResponseReceived> error_on_response_received =
std::nullopt,
base::TimeDelta timeout_duration = {});
// The centralized helper to create `PrefetchStreamingURLLoader` associated with
// - `PrefetchContainer` (possibly nullptr) and
// - `PrefetchResponseReader` (always non-nullptr).
base::WeakPtr<PrefetchStreamingURLLoader> CreateStreamingURLLoaderForTests(
base::WeakPtr<PrefetchContainer> prefetch_container,
base::WeakPtr<PrefetchResponseReader> response_reader,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
const network::ResourceRequest& prefetch_request,
NotReachedTagForTestsOr<base::RunLoop*> on_response_received,
NotReachedTagForTestsOr<OnPrefetchCompleteTestFuture*> on_complete,
NotReachedTagForTestsOr<OnPrefetchReceiveRedirectTestFuture*>
on_receive_redirect,
NotReachedTagForTestsOr<base::RunLoop*> on_head_received,
std::optional<PrefetchErrorOnResponseReceived> error_on_response_received =
std::nullopt,
base::TimeDelta timeout_duration = {});
void MakeServableStreamingURLLoaderForTest(
PrefetchContainer* prefetch_container,
network::mojom::URLResponseHeadPtr head,
const std::string body,
network::URLLoaderCompletionStatus status =
network::URLLoaderCompletionStatus(net::OK));
network::TestURLLoaderFactory::PendingRequest
MakeManuallyServableStreamingURLLoaderForTest(
PrefetchContainer* prefetch_container);
void MakeServableStreamingURLLoaderWithRedirectForTest(
PrefetchContainer* prefetch_container,
const GURL& original_url,
const GURL& redirect_url);
void MakeServableStreamingURLLoadersWithNetworkTransitionRedirectForTest(
PrefetchContainer* prefetch_container,
const GURL& original_url,
const GURL& redirect_url);
class PrefetchTestURLLoaderClient : public network::mojom::URLLoaderClient,
public mojo::DataPipeDrainer::Client {
public:
PrefetchTestURLLoaderClient();
~PrefetchTestURLLoaderClient() override;
PrefetchTestURLLoaderClient(const PrefetchTestURLLoaderClient&) = delete;
PrefetchTestURLLoaderClient& operator=(const PrefetchTestURLLoaderClient&) =
delete;
mojo::PendingReceiver<network::mojom::URLLoader>
BindURLloaderAndGetReceiver();
mojo::PendingRemote<network::mojom::URLLoaderClient>
BindURLLoaderClientAndGetRemote();
void DisconnectMojoPipes();
// By default, auto draining is enabled, i.e. body data pipe is started
// draining when received. If auto draining is disabled by
// `SetAutoDraining(false)`, `StartDraining()` should be explicitly called
// only once.
void SetAutoDraining(bool auto_draining) { auto_draining_ = auto_draining; }
void StartDraining();
std::string body_content() { return body_content_; }
uint32_t total_bytes_read() { return total_bytes_read_; }
bool body_finished() { return body_finished_; }
int32_t total_transfer_size_diff() { return total_transfer_size_diff_; }
std::optional<network::URLLoaderCompletionStatus> completion_status() {
return completion_status_;
}
const std::vector<
std::pair<net::RedirectInfo, network::mojom::URLResponseHeadPtr>>&
received_redirects() {
return received_redirects_;
}
private:
// network::mojom::URLLoaderClient
void OnReceiveEarlyHints(network::mojom::EarlyHintsPtr early_hints) override;
void OnReceiveResponse(
network::mojom::URLResponseHeadPtr head,
mojo::ScopedDataPipeConsumerHandle body,
std::optional<mojo_base::BigBuffer> cached_metadata) override;
void OnReceiveRedirect(const net::RedirectInfo& redirect_info,
network::mojom::URLResponseHeadPtr head) override;
void OnUploadProgress(int64_t current_position,
int64_t total_size,
OnUploadProgressCallback callback) override;
void OnTransferSizeUpdated(int32_t transfer_size_diff) override;
void OnComplete(const network::URLLoaderCompletionStatus& status) override;
// mojo::DataPipeDrainer::Client
void OnDataAvailable(base::span<const uint8_t> data) override;
void OnDataComplete() override;
mojo::Remote<network::mojom::URLLoader> remote_;
mojo::Receiver<network::mojom::URLLoaderClient> receiver_{this};
std::unique_ptr<mojo::DataPipeDrainer> pipe_drainer_;
bool auto_draining_{true};
mojo::ScopedDataPipeConsumerHandle body_;
std::string body_content_;
uint32_t total_bytes_read_{0};
bool body_finished_{false};
int32_t total_transfer_size_diff_{0};
std::optional<network::URLLoaderCompletionStatus> completion_status_;
std::vector<std::pair<net::RedirectInfo, network::mojom::URLResponseHeadPtr>>
received_redirects_;
};
class ScopedMockContentBrowserClient : public TestContentBrowserClient {
public:
ScopedMockContentBrowserClient();
~ScopedMockContentBrowserClient() override;
MOCK_METHOD(
void,
WillCreateURLLoaderFactory,
(BrowserContext * browser_context,
RenderFrameHost* frame,
int render_process_id,
URLLoaderFactoryType type,
const url::Origin& request_initiator,
const net::IsolationInfo& isolation_info,
std::optional<int64_t> navigation_id,
ukm::SourceIdObj ukm_source_id,
network::URLLoaderFactoryBuilder& factory_builder,
mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>*
header_client,
bool* bypass_redirect_checks,
bool* disable_secure_dns,
network::mojom::URLLoaderFactoryOverridePtr* factory_override,
scoped_refptr<base::SequencedTaskRunner>
navigation_response_task_runner),
(override));
private:
raw_ptr<ContentBrowserClient> old_browser_client_;
};
class TestPrefetchService final : public PrefetchService {
public:
explicit TestPrefetchService(BrowserContext* browser_context);
~TestPrefetchService() override;
void PrefetchUrl(
base::WeakPtr<PrefetchContainer> prefetch_container) override;
void OnPrefetchCompletedOrFailed(
PrefetchContainer& prefetch_container,
const network::URLLoaderCompletionStatus& completion_status,
const std::optional<int>& response_code) override;
void EvictPrefetch(size_t index);
std::vector<base::WeakPtr<PrefetchContainer>> prefetches_;
};
// Helper for testing prefetching-side (i.e. not serving-side) metrics including
// "PrefetchProxy.Prefetch.*" UMAs, `PrefetchReferringPageMetrics` and
// Preloading_Attempt UKMs.
class PrefetchingMetricsTestBase : public RenderViewHostTestHarness {
public:
PrefetchingMetricsTestBase();
~PrefetchingMetricsTestBase() override;
const int kTotalTimeDuration = 4321;
const int kConnectTimeDuration = 123;
ukm::TestAutoSetUkmRecorder* test_ukm_recorder() {
return test_ukm_recorder_.get();
}
const test::PreloadingAttemptUkmEntryBuilder* attempt_entry_builder() {
return attempt_entry_builder_.get();
}
void SetUp() override;
void TearDown() override;
// Prefetch didn't receive any net errors nor non-redirect responses.
// Use more specific methods below to check UKMs, if applicable.
void ExpectPrefetchNoNetErrorOrResponseReceived(
const base::HistogramTester& histogram_tester,
bool is_eligible,
bool browser_initiated_prefetch = false);
// Prefetch was not started because it was not eligible.
void ExpectPrefetchNotEligible(const base::HistogramTester& histogram_tester,
PreloadingEligibility expected_eligibility,
bool is_accurate = false,
bool browser_initiated_prefetch = false);
// Prefetch was started but failed before the final response nor any network
// error is received.
void ExpectPrefetchFailedBeforeResponseReceived(
const base::HistogramTester& histogram_tester,
PrefetchStatus expected_prefetch_status,
bool is_accurate = false);
// Prefetch was started but failed due to a network error, before the final
// response is received.
void ExpectPrefetchFailedNetError(
const base::HistogramTester& histogram_tester,
int expected_net_error_code,
blink::mojom::SpeculationEagerness eagerness =
blink::mojom::SpeculationEagerness::kImmediate,
bool is_accurate_triggering = false,
bool browser_initiated_prefetch = false);
// Prefetch was started but failed on or after the final response is
// received.
void ExpectPrefetchFailedAfterResponseReceived(
const base::HistogramTester& histogram_tester,
net::HttpStatusCode expected_response_code,
int expected_body_length,
PrefetchStatus expected_prefetch_status);
void ExpectPrefetchSuccess(const base::HistogramTester& histogram_tester,
int expected_body_length,
blink::mojom::SpeculationEagerness eagerness =
blink::mojom::SpeculationEagerness::kImmediate,
bool is_accurate = false);
// `navigate_url` is used as `MockNavigationHandle`'s URL to simulate a
// navigation possibly using the prefetch. It is passed outside
// `ExpectCorrectUkmLogsArgs` to keep `ExpectCorrectUkmLogsArgs` non-complex.
ukm::SourceId ForceLogsUploadAndGetUkmId(
GURL navigate_url = GURL("http://Not.Accurate.Trigger.Url/"));
struct ExpectCorrectUkmLogsArgs {
PreloadingEligibility eligibility = PreloadingEligibility::kEligible;
PreloadingHoldbackStatus holdback = PreloadingHoldbackStatus::kAllowed;
PreloadingTriggeringOutcome outcome = PreloadingTriggeringOutcome::kReady;
PreloadingFailureReason failure = PreloadingFailureReason::kUnspecified;
bool is_accurate = false;
bool expect_ready_time = false;
blink::mojom::SpeculationEagerness eagerness =
blink::mojom::SpeculationEagerness::kImmediate;
};
void ExpectCorrectUkmLogs(
ExpectCorrectUkmLogsArgs args,
GURL navigate_url = GURL("http://Not.Accurate.Trigger.Url/"));
private:
std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;
std::unique_ptr<test::PreloadingAttemptUkmEntryBuilder>
attempt_entry_builder_;
};
} // namespace content
#endif // CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_TEST_UTIL_INTERNAL_H_