blob: 5458967f87ebfaaab20308aeab389974cc17b21d [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/platform/loader/fetch/resource_loader.h"
#include <string>
#include <utility>
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "mojo/public/c/system/data_pipe.h"
#include "services/network/public/mojom/fetch_api.mojom-blink.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
#include "third_party/blink/public/platform/resource_load_info_notifier_wrapper.h"
#include "third_party/blink/public/platform/web_back_forward_cache_loader_helper.h"
#include "third_party/blink/public/platform/web_url_loader.h"
#include "third_party/blink/public/platform/web_url_loader_factory.h"
#include "third_party/blink/public/platform/web_url_request_extra_data.h"
#include "third_party/blink/renderer/platform/exported/wrapped_resource_response.h"
#include "third_party/blink/renderer/platform/loader/fetch/raw_resource.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
#include "third_party/blink/renderer/platform/loader/testing/bytes_consumer_test_reader.h"
#include "third_party/blink/renderer/platform/loader/testing/mock_fetch_context.h"
#include "third_party/blink/renderer/platform/loader/testing/test_resource_fetcher_properties.h"
#include "third_party/blink/renderer/platform/testing/mock_context_lifecycle_notifier.h"
#include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink {
const char kCnameAliasHadAliasesHistogram[] =
"SubresourceFilter.CnameAlias.Renderer.HadAliases";
const char kCnameAliasIsInvalidCountHistogram[] =
"SubresourceFilter.CnameAlias.Renderer.InvalidCount";
const char kCnameAliasIsRedundantCountHistogram[] =
"SubresourceFilter.CnameAlias.Renderer.RedundantCount";
const char kCnameAliasListLengthHistogram[] =
"SubresourceFilter.CnameAlias.Renderer.ListLength";
const char kCnameAliasWasAdTaggedHistogram[] =
"SubresourceFilter.CnameAlias.Renderer.WasAdTaggedBasedOnAlias";
const char kCnameAliasWasBlockedHistogram[] =
"SubresourceFilter.CnameAlias.Renderer.WasBlockedBasedOnAlias";
class ResourceLoaderTest : public testing::Test {
DISALLOW_COPY_AND_ASSIGN(ResourceLoaderTest);
public:
enum class From {
kServiceWorker,
kNetwork,
};
ResourceLoaderTest()
: foo_url_("https://foo.test"), bar_url_("https://bar.test") {}
protected:
using RequestMode = network::mojom::RequestMode;
using FetchResponseType = network::mojom::FetchResponseType;
struct TestCase {
const KURL url;
const RequestMode request_mode;
const From from;
const scoped_refptr<const SecurityOrigin> allowed_origin;
const FetchResponseType original_response_type;
const FetchResponseType expectation;
};
ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler>
platform_;
const KURL foo_url_;
const KURL bar_url_;
class NoopLoaderFactory final : public ResourceFetcher::LoaderFactory {
std::unique_ptr<WebURLLoader> CreateURLLoader(
const ResourceRequest& request,
const ResourceLoaderOptions& options,
scoped_refptr<base::SingleThreadTaskRunner> freezable_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> unfreezable_task_runner,
WebBackForwardCacheLoaderHelper back_forward_cache_loader_helper)
override {
return std::make_unique<NoopWebURLLoader>(
std::move(freezable_task_runner));
}
std::unique_ptr<WebCodeCacheLoader> CreateCodeCacheLoader() override {
return Platform::Current()->CreateCodeCacheLoader();
}
};
static scoped_refptr<base::SingleThreadTaskRunner> CreateTaskRunner() {
return base::MakeRefCounted<scheduler::FakeTaskRunner>();
}
ResourceFetcher* MakeResourceFetcher(
TestResourceFetcherProperties* properties,
FetchContext* context) {
return MakeGarbageCollected<ResourceFetcher>(ResourceFetcherInit(
properties->MakeDetachable(), context, CreateTaskRunner(),
CreateTaskRunner(), MakeGarbageCollected<NoopLoaderFactory>(),
MakeGarbageCollected<MockContextLifecycleNotifier>(),
nullptr /* back_forward_cache_loader_helper */));
}
private:
class NoopWebURLLoader final : public WebURLLoader {
public:
explicit NoopWebURLLoader(
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: task_runner_(task_runner) {}
~NoopWebURLLoader() override = default;
void LoadSynchronously(
std::unique_ptr<network::ResourceRequest> request,
scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
int requestor_id,
bool pass_response_pipe_to_client,
bool no_mime_sniffing,
base::TimeDelta timeout_interval,
WebURLLoaderClient*,
WebURLResponse&,
base::Optional<WebURLError>&,
WebData&,
int64_t& encoded_data_length,
int64_t& encoded_body_length,
WebBlobInfo& downloaded_blob,
std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper>
resource_load_info_notifier_wrapper) override {
NOTREACHED();
}
void LoadAsynchronously(
std::unique_ptr<network::ResourceRequest> request,
scoped_refptr<WebURLRequestExtraData> url_request_extra_data,
int requestor_id,
bool no_mime_sniffing,
std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper>
resource_load_info_notifier_wrapper,
WebURLLoaderClient*) override {}
void SetDefersLoading(WebURLLoader::DeferType) override {}
void DidChangePriority(WebURLRequest::Priority, int) override {
NOTREACHED();
}
scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunnerForBodyLoader()
override {
return task_runner_;
}
private:
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
};
};
std::ostream& operator<<(std::ostream& o, const ResourceLoaderTest::From& f) {
switch (f) {
case ResourceLoaderTest::From::kServiceWorker:
o << "service worker";
break;
case ResourceLoaderTest::From::kNetwork:
o << "network";
break;
}
return o;
}
TEST_F(ResourceLoaderTest, LoadResponseBody) {
auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>();
FetchContext* context = MakeGarbageCollected<MockFetchContext>();
auto* fetcher = MakeResourceFetcher(properties, context);
KURL url("https://www.example.com/");
ResourceRequest request(url);
request.SetRequestContext(mojom::blink::RequestContextType::FETCH);
FetchParameters params = FetchParameters::CreateForTest(std::move(request));
Resource* resource = RawResource::Fetch(params, fetcher, nullptr);
ResourceLoader* loader = resource->Loader();
ResourceResponse response(url);
response.SetHttpStatusCode(200);
mojo::ScopedDataPipeProducerHandle producer;
mojo::ScopedDataPipeConsumerHandle consumer;
MojoCreateDataPipeOptions options;
options.struct_size = sizeof(MojoCreateDataPipeOptions);
options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
options.element_num_bytes = 1;
options.capacity_num_bytes = 3;
MojoResult result = CreateDataPipe(&options, producer, consumer);
ASSERT_EQ(result, MOJO_RESULT_OK);
loader->DidReceiveResponse(WrappedResourceResponse(response));
loader->DidStartLoadingResponseBody(std::move(consumer));
loader->DidFinishLoading(base::TimeTicks(), 0, 0, 0, false);
uint32_t num_bytes = 2;
result = producer->WriteData("he", &num_bytes, MOJO_WRITE_DATA_FLAG_NONE);
ASSERT_EQ(result, MOJO_RESULT_OK);
ASSERT_EQ(num_bytes, 2u);
static_cast<scheduler::FakeTaskRunner*>(fetcher->GetTaskRunner().get())
->RunUntilIdle();
num_bytes = 3;
result = producer->WriteData("llo", &num_bytes, MOJO_WRITE_DATA_FLAG_NONE);
ASSERT_EQ(result, MOJO_RESULT_OK);
ASSERT_EQ(num_bytes, 3u);
static_cast<scheduler::FakeTaskRunner*>(fetcher->GetTaskRunner().get())
->RunUntilIdle();
EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
producer.reset();
static_cast<scheduler::FakeTaskRunner*>(fetcher->GetTaskRunner().get())
->RunUntilIdle();
EXPECT_EQ(resource->GetStatus(), ResourceStatus::kCached);
scoped_refptr<const SharedBuffer> buffer = resource->ResourceBuffer();
StringBuilder data;
for (const auto& span : *buffer) {
data.Append(span.data(), span.size());
}
EXPECT_EQ(data.ToString(), "hello");
}
TEST_F(ResourceLoaderTest, LoadDataURL_AsyncAndNonStream) {
auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>();
FetchContext* context = MakeGarbageCollected<MockFetchContext>();
auto* fetcher = MakeResourceFetcher(properties, context);
// Fetch a data url.
KURL url("data:text/plain,Hello%20World!");
ResourceRequest request(url);
request.SetRequestContext(mojom::blink::RequestContextType::FETCH);
FetchParameters params = FetchParameters::CreateForTest(std::move(request));
Resource* resource = RawResource::Fetch(params, fetcher, nullptr);
EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
static_cast<scheduler::FakeTaskRunner*>(fetcher->GetTaskRunner().get())
->RunUntilIdle();
// The resource has a parsed body.
EXPECT_EQ(resource->GetStatus(), ResourceStatus::kCached);
scoped_refptr<const SharedBuffer> buffer = resource->ResourceBuffer();
StringBuilder data;
for (const auto& span : *buffer) {
data.Append(span.data(), span.size());
}
EXPECT_EQ(data.ToString(), "Hello World!");
}
// Helper class which stores a BytesConsumer passed by RawResource and reads the
// bytes when ReadThroughBytesConsumer is called.
class TestRawResourceClient final
: public GarbageCollected<TestRawResourceClient>,
public RawResourceClient {
public:
TestRawResourceClient() = default;
// Implements RawResourceClient.
void ResponseBodyReceived(Resource* resource,
BytesConsumer& bytes_consumer) override {
body_ = &bytes_consumer;
}
String DebugName() const override { return "TestRawResourceClient"; }
void Trace(Visitor* visitor) const override {
visitor->Trace(body_);
RawResourceClient::Trace(visitor);
}
BytesConsumer* body() { return body_; }
private:
Member<BytesConsumer> body_;
};
TEST_F(ResourceLoaderTest, LoadDataURL_AsyncAndStream) {
auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>();
FetchContext* context = MakeGarbageCollected<MockFetchContext>();
auto* fetcher = MakeResourceFetcher(properties, context);
scheduler::FakeTaskRunner* task_runner =
static_cast<scheduler::FakeTaskRunner*>(fetcher->GetTaskRunner().get());
// Fetch a data url as a stream on response.
KURL url("data:text/plain,Hello%20World!");
ResourceRequest request(url);
request.SetRequestContext(mojom::blink::RequestContextType::FETCH);
request.SetUseStreamOnResponse(true);
FetchParameters params = FetchParameters::CreateForTest(std::move(request));
auto* raw_resource_client = MakeGarbageCollected<TestRawResourceClient>();
Resource* resource = RawResource::Fetch(params, fetcher, raw_resource_client);
EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
task_runner->RunUntilIdle();
// It's still pending because we don't read the body yet.
EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
// Read through the bytes consumer passed back from the ResourceLoader.
auto* test_reader = MakeGarbageCollected<BytesConsumerTestReader>(
raw_resource_client->body());
Vector<char> body;
BytesConsumer::Result result;
std::tie(result, body) = test_reader->Run(task_runner);
EXPECT_EQ(result, BytesConsumer::Result::kDone);
EXPECT_EQ(resource->GetStatus(), ResourceStatus::kCached);
EXPECT_EQ(std::string(body.data(), body.size()), "Hello World!");
// The body is not set to ResourceBuffer since the response body is requested
// as a stream.
scoped_refptr<const SharedBuffer> buffer = resource->ResourceBuffer();
EXPECT_FALSE(buffer);
}
TEST_F(ResourceLoaderTest, LoadDataURL_AsyncEmptyData) {
auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>();
FetchContext* context = MakeGarbageCollected<MockFetchContext>();
auto* fetcher = MakeResourceFetcher(properties, context);
// Fetch an empty data url.
KURL url("data:text/html,");
ResourceRequest request(url);
request.SetRequestContext(mojom::blink::RequestContextType::FETCH);
FetchParameters params = FetchParameters::CreateForTest(std::move(request));
Resource* resource = RawResource::Fetch(params, fetcher, nullptr);
EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
static_cast<scheduler::FakeTaskRunner*>(fetcher->GetTaskRunner().get())
->RunUntilIdle();
// It successfully finishes, and no buffer is propagated.
EXPECT_EQ(resource->GetStatus(), ResourceStatus::kCached);
scoped_refptr<const SharedBuffer> buffer = resource->ResourceBuffer();
EXPECT_FALSE(buffer);
}
TEST_F(ResourceLoaderTest, LoadDataURL_Sync) {
auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>();
FetchContext* context = MakeGarbageCollected<MockFetchContext>();
auto* fetcher = MakeResourceFetcher(properties, context);
// Fetch a data url synchronously.
KURL url("data:text/plain,Hello%20World!");
ResourceRequest request(url);
request.SetRequestContext(mojom::blink::RequestContextType::FETCH);
FetchParameters params = FetchParameters::CreateForTest(std::move(request));
Resource* resource =
RawResource::FetchSynchronously(params, fetcher, nullptr);
// The resource has a parsed body.
EXPECT_EQ(resource->GetStatus(), ResourceStatus::kCached);
scoped_refptr<const SharedBuffer> buffer = resource->ResourceBuffer();
StringBuilder data;
for (const auto& span : *buffer) {
data.Append(span.data(), span.size());
}
EXPECT_EQ(data.ToString(), "Hello World!");
}
TEST_F(ResourceLoaderTest, LoadDataURL_SyncEmptyData) {
auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>();
FetchContext* context = MakeGarbageCollected<MockFetchContext>();
auto* fetcher = MakeResourceFetcher(properties, context);
// Fetch an empty data url synchronously.
KURL url("data:text/html,");
ResourceRequest request(url);
request.SetRequestContext(mojom::blink::RequestContextType::FETCH);
FetchParameters params = FetchParameters::CreateForTest(std::move(request));
Resource* resource =
RawResource::FetchSynchronously(params, fetcher, nullptr);
// It successfully finishes, and no buffer is propagated.
EXPECT_EQ(resource->GetStatus(), ResourceStatus::kCached);
scoped_refptr<const SharedBuffer> buffer = resource->ResourceBuffer();
EXPECT_FALSE(buffer);
}
TEST_F(ResourceLoaderTest, LoadDataURL_DefersAsyncAndNonStream) {
auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>();
FetchContext* context = MakeGarbageCollected<MockFetchContext>();
auto* fetcher = MakeResourceFetcher(properties, context);
scheduler::FakeTaskRunner* task_runner =
static_cast<scheduler::FakeTaskRunner*>(fetcher->GetTaskRunner().get());
// Fetch a data url.
KURL url("data:text/plain,Hello%20World!");
ResourceRequest request(url);
request.SetRequestContext(mojom::blink::RequestContextType::FETCH);
FetchParameters params = FetchParameters::CreateForTest(std::move(request));
Resource* resource = RawResource::Fetch(params, fetcher, nullptr);
EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
// The resource should still be pending since it's deferred.
fetcher->SetDefersLoading(WebURLLoader::DeferType::kDeferred);
task_runner->RunUntilIdle();
EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
// The resource should still be pending since it's deferred again.
fetcher->SetDefersLoading(WebURLLoader::DeferType::kDeferred);
task_runner->RunUntilIdle();
EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
// The resource should still be pending if it's unset and set in a single
// task.
fetcher->SetDefersLoading(WebURLLoader::DeferType::kNotDeferred);
fetcher->SetDefersLoading(WebURLLoader::DeferType::kDeferred);
task_runner->RunUntilIdle();
EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
// The resource has a parsed body.
fetcher->SetDefersLoading(WebURLLoader::DeferType::kNotDeferred);
task_runner->RunUntilIdle();
EXPECT_EQ(resource->GetStatus(), ResourceStatus::kCached);
scoped_refptr<const SharedBuffer> buffer = resource->ResourceBuffer();
StringBuilder data;
for (const auto& span : *buffer) {
data.Append(span.data(), span.size());
}
EXPECT_EQ(data.ToString(), "Hello World!");
}
TEST_F(ResourceLoaderTest, LoadDataURL_DefersAsyncAndStream) {
auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>();
FetchContext* context = MakeGarbageCollected<MockFetchContext>();
auto* fetcher = MakeResourceFetcher(properties, context);
scheduler::FakeTaskRunner* task_runner =
static_cast<scheduler::FakeTaskRunner*>(fetcher->GetTaskRunner().get());
// Fetch a data url as a stream on response.
KURL url("data:text/plain,Hello%20World!");
ResourceRequest request(url);
request.SetRequestContext(mojom::blink::RequestContextType::FETCH);
request.SetUseStreamOnResponse(true);
FetchParameters params = FetchParameters::CreateForTest(std::move(request));
auto* raw_resource_client = MakeGarbageCollected<TestRawResourceClient>();
Resource* resource = RawResource::Fetch(params, fetcher, raw_resource_client);
EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
fetcher->SetDefersLoading(WebURLLoader::DeferType::kDeferred);
task_runner->RunUntilIdle();
// It's still pending because the body should not provided yet.
EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
EXPECT_FALSE(raw_resource_client->body());
// The body should be provided since not deferring now, but it's still pending
// since we haven't read the body yet.
fetcher->SetDefersLoading(WebURLLoader::DeferType::kNotDeferred);
task_runner->RunUntilIdle();
EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
EXPECT_TRUE(raw_resource_client->body());
// The resource should still be pending when it's set to deferred again. No
// body is provided when deferred.
fetcher->SetDefersLoading(WebURLLoader::DeferType::kDeferred);
task_runner->RunUntilIdle();
EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
const char* buffer;
size_t available;
BytesConsumer::Result result =
raw_resource_client->body()->BeginRead(&buffer, &available);
EXPECT_EQ(BytesConsumer::Result::kShouldWait, result);
// The resource should still be pending if it's unset and set in a single
// task. No body is provided when deferred.
fetcher->SetDefersLoading(WebURLLoader::DeferType::kNotDeferred);
fetcher->SetDefersLoading(WebURLLoader::DeferType::kDeferred);
task_runner->RunUntilIdle();
EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
result = raw_resource_client->body()->BeginRead(&buffer, &available);
EXPECT_EQ(BytesConsumer::Result::kShouldWait, result);
// Read through the bytes consumer passed back from the ResourceLoader.
fetcher->SetDefersLoading(WebURLLoader::DeferType::kNotDeferred);
task_runner->RunUntilIdle();
auto* test_reader = MakeGarbageCollected<BytesConsumerTestReader>(
raw_resource_client->body());
Vector<char> body;
std::tie(result, body) = test_reader->Run(task_runner);
EXPECT_EQ(resource->GetStatus(), ResourceStatus::kCached);
EXPECT_EQ(std::string(body.data(), body.size()), "Hello World!");
// The body is not set to ResourceBuffer since the response body is requested
// as a stream.
EXPECT_FALSE(resource->ResourceBuffer());
}
class ResourceLoaderIsolatedCodeCacheTest : public ResourceLoaderTest {
protected:
bool LoadAndCheckIsolatedCodeCache(ResourceResponse response) {
const scoped_refptr<const SecurityOrigin> origin =
SecurityOrigin::Create(foo_url_);
auto* properties =
MakeGarbageCollected<TestResourceFetcherProperties>(origin);
FetchContext* context = MakeGarbageCollected<MockFetchContext>();
auto* fetcher = MakeResourceFetcher(properties, context);
ResourceRequest request;
request.SetUrl(foo_url_);
request.SetRequestContext(mojom::blink::RequestContextType::FETCH);
FetchParameters fetch_parameters =
FetchParameters::CreateForTest(std::move(request));
Resource* resource = RawResource::Fetch(fetch_parameters, fetcher, nullptr);
ResourceLoader* loader = resource->Loader();
loader->DidReceiveResponse(WrappedResourceResponse(response));
return loader->should_use_isolated_code_cache_;
}
};
TEST_F(ResourceLoaderIsolatedCodeCacheTest, ResponseFromNetwork) {
ResourceResponse response(foo_url_);
response.SetHttpStatusCode(200);
EXPECT_EQ(true, LoadAndCheckIsolatedCodeCache(response));
}
TEST_F(ResourceLoaderIsolatedCodeCacheTest,
SyntheticResponseFromServiceWorker) {
ResourceResponse response(foo_url_);
response.SetHttpStatusCode(200);
response.SetWasFetchedViaServiceWorker(true);
EXPECT_EQ(false, LoadAndCheckIsolatedCodeCache(response));
}
TEST_F(ResourceLoaderIsolatedCodeCacheTest,
PassThroughResponseFromServiceWorker) {
ResourceResponse response(foo_url_);
response.SetHttpStatusCode(200);
response.SetWasFetchedViaServiceWorker(true);
response.SetUrlListViaServiceWorker(Vector<KURL>(1, foo_url_));
EXPECT_EQ(true, LoadAndCheckIsolatedCodeCache(response));
}
TEST_F(ResourceLoaderIsolatedCodeCacheTest,
DifferentUrlResponseFromServiceWorker) {
ResourceResponse response(foo_url_);
response.SetHttpStatusCode(200);
response.SetWasFetchedViaServiceWorker(true);
response.SetUrlListViaServiceWorker(Vector<KURL>(1, bar_url_));
EXPECT_EQ(false, LoadAndCheckIsolatedCodeCache(response));
}
TEST_F(ResourceLoaderIsolatedCodeCacheTest, CacheResponseFromServiceWorker) {
ResourceResponse response(foo_url_);
response.SetHttpStatusCode(200);
response.SetWasFetchedViaServiceWorker(true);
response.SetCacheStorageCacheName("dummy");
// The browser does support code cache for cache_storage Responses, but they
// are loaded via a different mechanism. So the ResourceLoader code caching
// value should be false here.
EXPECT_EQ(false, LoadAndCheckIsolatedCodeCache(response));
}
class ResourceLoaderSubresourceFilterCnameAliasTest
: public ResourceLoaderTest {
public:
ResourceLoaderSubresourceFilterCnameAliasTest() = default;
~ResourceLoaderSubresourceFilterCnameAliasTest() override = default;
void SetUp() override {
feature_list_.InitAndEnableFeature(
features::kSendCnameAliasesToSubresourceFilterFromRenderer);
ResourceLoaderTest::SetUp();
}
base::HistogramTester* histogram_tester() { return &histogram_tester_; }
void SetMockSubresourceFilterBlockLists(Vector<String> blocked_urls,
Vector<String> tagged_urls) {
blocked_urls_ = blocked_urls;
tagged_urls_ = tagged_urls;
}
Resource* CreateResource(ResourceRequest request) {
FetchParameters params = FetchParameters::CreateForTest(std::move(request));
auto* fetcher = MakeResourceFetcherWithMockSubresourceFilter();
return RawResource::Fetch(params, fetcher, nullptr);
}
void GiveResponseToLoader(ResourceResponse response, ResourceLoader* loader) {
CreateMojoDataPipe();
loader->DidReceiveResponse(WrappedResourceResponse(response));
}
protected:
FetchContext* MakeFetchContextWithMockSubresourceFilter(
Vector<String> blocked_urls,
Vector<String> tagged_urls) {
auto* context = MakeGarbageCollected<MockFetchContext>();
context->set_blocked_urls(blocked_urls);
context->set_tagged_urls(tagged_urls);
return context;
}
ResourceFetcher* MakeResourceFetcherWithMockSubresourceFilter() {
auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>();
FetchContext* context =
MakeFetchContextWithMockSubresourceFilter(blocked_urls_, tagged_urls_);
return MakeResourceFetcher(properties, context);
}
void CreateMojoDataPipe() {
mojo::ScopedDataPipeProducerHandle producer;
mojo::ScopedDataPipeConsumerHandle consumer;
MojoCreateDataPipeOptions options;
options.struct_size = sizeof(MojoCreateDataPipeOptions);
options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
options.element_num_bytes = 1;
options.capacity_num_bytes = 3;
MojoResult result = CreateDataPipe(&options, producer, consumer);
ASSERT_EQ(result, MOJO_RESULT_OK);
}
void ExpectHistogramsMatching(CnameAliasMetricInfo info) {
histogram_tester()->ExpectUniqueSample(kCnameAliasHadAliasesHistogram,
info.has_aliases, 1);
if (info.has_aliases) {
histogram_tester()->ExpectUniqueSample(kCnameAliasWasAdTaggedHistogram,
info.was_ad_tagged_based_on_alias,
1);
histogram_tester()->ExpectUniqueSample(
kCnameAliasWasBlockedHistogram, info.was_blocked_based_on_alias, 1);
histogram_tester()->ExpectUniqueSample(kCnameAliasListLengthHistogram,
info.list_length, 1);
histogram_tester()->ExpectUniqueSample(kCnameAliasIsInvalidCountHistogram,
info.invalid_count, 1);
histogram_tester()->ExpectUniqueSample(
kCnameAliasIsRedundantCountHistogram, info.redundant_count, 1);
}
}
private:
base::test::ScopedFeatureList feature_list_;
base::HistogramTester histogram_tester_;
Vector<String> blocked_urls_;
Vector<String> tagged_urls_;
};
TEST_F(ResourceLoaderSubresourceFilterCnameAliasTest,
DnsAliasesCheckedBySubresourceFilterDisallowed_TaggedAndBlocked) {
// Set the blocklists: the first for blocking, the second for ad-tagging.
Vector<String> blocked_urls = {"https://bad-ad.com/some_path.html"};
Vector<String> tagged_urls = {"https://ad.com/some_path.html"};
SetMockSubresourceFilterBlockLists(blocked_urls, tagged_urls);
// Create the request.
KURL url("https://www.example.com/some_path.html");
ResourceRequest request(url);
request.SetRequestContext(mojom::blink::RequestContextType::FETCH);
// Create the resource and loader.
Resource* resource = CreateResource(std::move(request));
ResourceLoader* loader = resource->Loader();
// Create the response.
ResourceResponse response(url);
response.SetHttpStatusCode(200);
// Set the CNAME aliases.
Vector<String> aliases({"ad.com", "bad-ad.com", "alias3.com"});
response.SetDnsAliases(aliases);
// Give the response to the loader.
GiveResponseToLoader(response, loader);
// Test the histograms to verify that the CNAME aliases were detected.
// Expect that the resource was tagged as a ad, due to first alias.
// Expect that the resource was blocked, due to second alias.
CnameAliasMetricInfo info = {.has_aliases = true,
.was_ad_tagged_based_on_alias = true,
.was_blocked_based_on_alias = true,
.list_length = 3,
.invalid_count = 0,
.redundant_count = 0};
ExpectHistogramsMatching(info);
}
TEST_F(ResourceLoaderSubresourceFilterCnameAliasTest,
DnsAliasesCheckedBySubresourceFilterDisallowed_BlockedOnly) {
// Set the blocklists: the first for blocking, the second for ad-tagging.
Vector<String> blocked_urls = {"https://bad-ad.com/some_path.html"};
Vector<String> tagged_urls = {};
SetMockSubresourceFilterBlockLists(blocked_urls, tagged_urls);
// Create the request.
KURL url("https://www.example.com/some_path.html");
ResourceRequest request(url);
request.SetRequestContext(mojom::blink::RequestContextType::FETCH);
// Create the resource and loader.
Resource* resource = CreateResource(std::move(request));
ResourceLoader* loader = resource->Loader();
// Create the response.
ResourceResponse response(url);
response.SetHttpStatusCode(200);
// Set the CNAME aliases.
Vector<String> aliases({"ad.com", "bad-ad.com", "alias3.com"});
response.SetDnsAliases(aliases);
// Give the response to the loader.
GiveResponseToLoader(response, loader);
// Test the histograms to verify that the CNAME aliases were detected.
// Expect that the resource was blocked, due to second alias.
CnameAliasMetricInfo info = {.has_aliases = true,
.was_ad_tagged_based_on_alias = false,
.was_blocked_based_on_alias = true,
.list_length = 3,
.invalid_count = 0,
.redundant_count = 0};
ExpectHistogramsMatching(info);
}
TEST_F(ResourceLoaderSubresourceFilterCnameAliasTest,
DnsAliasesCheckedBySubresourceFilterDisallowed_TaggedOnly) {
// Set the blocklists: the first for blocking, the second for ad-tagging.
Vector<String> blocked_urls = {};
Vector<String> tagged_urls = {"https://bad-ad.com/some_path.html"};
SetMockSubresourceFilterBlockLists(blocked_urls, tagged_urls);
// Create the request.
KURL url("https://www.example.com/some_path.html");
ResourceRequest request(url);
request.SetRequestContext(mojom::blink::RequestContextType::FETCH);
// Create the resource and loader.
Resource* resource = CreateResource(std::move(request));
ResourceLoader* loader = resource->Loader();
// Create the response.
ResourceResponse response(url);
response.SetHttpStatusCode(200);
// Set the CNAME aliases.
Vector<String> aliases({"ad.com", "", "alias3.com", "bad-ad.com"});
response.SetDnsAliases(aliases);
// Give the response to the loader.
GiveResponseToLoader(response, loader);
// Test the histograms to verify that the CNAME aliases were detected.
// Expect that the resource was tagged, due to fourth alias.
// Expect that the invalid empty alias is counted as such.
CnameAliasMetricInfo info = {.has_aliases = true,
.was_ad_tagged_based_on_alias = true,
.was_blocked_based_on_alias = false,
.list_length = 4,
.invalid_count = 1,
.redundant_count = 0};
ExpectHistogramsMatching(info);
}
TEST_F(ResourceLoaderSubresourceFilterCnameAliasTest,
DnsAliasesCheckedBySubresourceFilterAllowed_NotBlockedOrTagged) {
// Set the blocklists: the first for blocking, the second for ad-tagging.
Vector<String> blocked_urls = {};
Vector<String> tagged_urls = {};
SetMockSubresourceFilterBlockLists(blocked_urls, tagged_urls);
// Create the request.
KURL url("https://www.example.com/some_path.html");
ResourceRequest request(url);
request.SetRequestContext(mojom::blink::RequestContextType::FETCH);
// Create the resource and loader.
Resource* resource = CreateResource(std::move(request));
ResourceLoader* loader = resource->Loader();
// Create the response.
ResourceResponse response(url);
response.SetHttpStatusCode(200);
// Set the CNAME aliases.
Vector<String> aliases(
{"non-ad.com", "?", "alias3.com", "not-an-ad.com", "www.example.com"});
response.SetDnsAliases(aliases);
// Give the response to the loader.
GiveResponseToLoader(response, loader);
// Test the histograms to verify that the CNAME aliases were detected.
// Expect that the resource was neither tagged nor blocked.
// Expect that the invalid alias is counted as such.
// Expect that the redundant (i.e. matching the request URL) fifth alias to be
// counted as such.
CnameAliasMetricInfo info = {.has_aliases = true,
.was_ad_tagged_based_on_alias = false,
.was_blocked_based_on_alias = false,
.list_length = 5,
.invalid_count = 1,
.redundant_count = 1};
ExpectHistogramsMatching(info);
}
TEST_F(ResourceLoaderSubresourceFilterCnameAliasTest,
DnsAliasesCheckedBySubresourceFilterNoAliases_NoneDetected) {
// Set the blocklists: the first for blocking, the second for ad-tagging.
Vector<String> blocked_urls = {};
Vector<String> tagged_urls = {};
SetMockSubresourceFilterBlockLists(blocked_urls, tagged_urls);
// Create the request.
KURL url("https://www.example.com/some_path.html");
ResourceRequest request(url);
request.SetRequestContext(mojom::blink::RequestContextType::FETCH);
// Create the resource and loader.
Resource* resource = CreateResource(std::move(request));
ResourceLoader* loader = resource->Loader();
// Create the response.
ResourceResponse response(url);
response.SetHttpStatusCode(200);
// Set the CNAME aliases.
Vector<String> aliases;
response.SetDnsAliases(aliases);
// Give the response to the loader.
GiveResponseToLoader(response, loader);
// Test the histogram to verify that no aliases were detected.
CnameAliasMetricInfo info = {.has_aliases = false};
ExpectHistogramsMatching(info);
}
} // namespace blink