blob: 2f954394732a45ffae00d2af439c3bea3bc518ed [file]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/content_settings/renderer/content_settings_agent_impl.h"
#include <stddef.h>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_utils.h"
#include "components/persistent_cache/pending_backend.h"
#include "content/public/common/url_constants.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/test/render_view_test.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "net/cookies/site_for_cookies.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/navigation/navigation_params.h"
#include "third_party/blink/public/mojom/loader/code_cache.mojom.h"
#include "third_party/blink/public/platform/browser_interface_broker_proxy.h"
#include "third_party/blink/public/platform/web_url.h"
#include "third_party/blink/public/test/test_web_frame_content_dumper.h"
#include "third_party/blink/public/web/web_view.h"
#include "url/gurl.h"
#include "url/url_util.h"
namespace content_settings {
namespace {
constexpr char kAllowlistScheme[] = "foo";
class MockContentSettingsManagerImpl : public mojom::ContentSettingsManager {
public:
struct Log {
int allow_storage_access_count = 0;
int on_content_blocked_count = 0;
ContentSettingsType on_content_blocked_type = ContentSettingsType::DEFAULT;
};
explicit MockContentSettingsManagerImpl(Log* log) : log_(log) {}
~MockContentSettingsManagerImpl() override = default;
// mojom::ContentSettingsManager methods:
void Clone(
mojo::PendingReceiver<mojom::ContentSettingsManager> receiver) override {
ADD_FAILURE() << "Not reached";
}
void AllowStorageAccess(const blink::LocalFrameToken& frame_token,
StorageType storage_type,
const url::Origin& origin,
const net::SiteForCookies& site_for_cookies,
const url::Origin& top_frame_origin,
base::OnceCallback<void(bool)> callback) override {
++log_->allow_storage_access_count;
std::move(callback).Run(true);
}
void OnContentBlocked(const blink::LocalFrameToken& frame_token,
ContentSettingsType type) override {
++log_->on_content_blocked_count;
log_->on_content_blocked_type = type;
}
private:
raw_ptr<Log> log_;
};
class MockContentSettingsAgentDelegate
: public ContentSettingsAgentImpl::Delegate {
public:
bool IsSchemeAllowlisted(const std::string& scheme) override {
return scheme == kAllowlistScheme;
}
};
class MockContentSettingsAgentImpl : public ContentSettingsAgentImpl {
public:
explicit MockContentSettingsAgentImpl(content::RenderFrame* render_frame);
MockContentSettingsAgentImpl(const MockContentSettingsAgentImpl&) = delete;
MockContentSettingsAgentImpl& operator=(const MockContentSettingsAgentImpl&) =
delete;
~MockContentSettingsAgentImpl() override = default;
const GURL& image_url() const { return image_url_; }
const std::string& image_origin() const { return image_origin_; }
// ContentSettingAgentImpl methods:
void BindContentSettingsManager(
mojo::Remote<mojom::ContentSettingsManager>* manager) override;
int allow_storage_access_count() const {
return log_.allow_storage_access_count;
}
int on_content_blocked_count() const { return log_.on_content_blocked_count; }
ContentSettingsType on_content_blocked_type() const {
return log_.on_content_blocked_type;
}
private:
MockContentSettingsManagerImpl::Log log_;
const GURL image_url_;
const std::string image_origin_;
};
MockContentSettingsAgentImpl::MockContentSettingsAgentImpl(
content::RenderFrame* render_frame)
: ContentSettingsAgentImpl(
render_frame,
std::make_unique<MockContentSettingsAgentDelegate>()),
image_url_("http://www.foo.com/image.jpg"),
image_origin_("http://www.foo.com") {}
void MockContentSettingsAgentImpl::BindContentSettingsManager(
mojo::Remote<mojom::ContentSettingsManager>* manager) {
mojo::MakeSelfOwnedReceiver(
std::make_unique<MockContentSettingsManagerImpl>(&log_),
manager->BindNewPipeAndPassReceiver());
}
// Evaluates a boolean `predicate` every time a provisional load is committed in
// the given `frame` while the instance of this class is in scope, and verifies
// that the result matches the `expectation`.
class CommitTimeConditionChecker : public content::RenderFrameObserver {
public:
using Predicate = base::RepeatingCallback<bool()>;
CommitTimeConditionChecker(content::RenderFrame* frame,
const Predicate& predicate,
bool expectation)
: content::RenderFrameObserver(frame),
predicate_(predicate),
expectation_(expectation) {}
CommitTimeConditionChecker(const CommitTimeConditionChecker&) = delete;
CommitTimeConditionChecker& operator=(const CommitTimeConditionChecker&) =
delete;
protected:
// RenderFrameObserver:
void OnDestruct() override {}
void DidCommitProvisionalLoad(ui::PageTransition transition) override {
EXPECT_EQ(expectation_, predicate_.Run());
}
private:
const Predicate predicate_;
const bool expectation_;
};
} // namespace
enum class BackgroundResourceFetchTestCase {
kBackgroundResourceFetchEnabled,
kBackgroundResourceFetchDisabled,
};
class ContentSettingsAgentImplBrowserTest
: public content::RenderViewTest,
public testing::WithParamInterface<BackgroundResourceFetchTestCase> {
protected:
void SetUp() override {
std::vector<base::test::FeatureRef> enabled_features;
std::vector<base::test::FeatureRef> disabled_features;
if (IsBackgroundResourceFetchEnabled()) {
enabled_features.push_back(blink::features::kBackgroundResourceFetch);
} else {
disabled_features.push_back(blink::features::kBackgroundResourceFetch);
}
feature_background_resource_fetch_.InitWithFeatures(enabled_features,
disabled_features);
RenderViewTest::SetUp();
// Set up a fake url loader factory to ensure that script loader can create
// a URLLoader.
CreateFakeURLLoaderFactory();
// Unbind the ContentSettingsAgent interface that would be registered by
// the ContentSettingsAgentImpl created when the render frame is created.
GetMainRenderFrame()->GetAssociatedInterfaceRegistry()->RemoveInterface(
mojom::ContentSettingsAgent::Name_);
// Bind a FakeCodeCacheHost which handles FetchCachedCode() method, because
// script loading is blocked until the callback of FetchCachedCode() is
// called.
GetMainRenderFrame()->GetBrowserInterfaceBroker().SetBinderForTesting(
blink::mojom::CodeCacheHost::Name_,
base::BindRepeating(
&ContentSettingsAgentImplBrowserTest::OnCodeCacheHostRequest,
base::Unretained(this)));
}
private:
class FakeCodeCacheHost : public blink::mojom::CodeCacheHost {
public:
explicit FakeCodeCacheHost(
mojo::PendingReceiver<blink::mojom::CodeCacheHost> receiver) {
receiver_.Bind(std::move(receiver));
}
void GetPendingBackend(blink::mojom::CodeCacheType cache_type,
GetPendingBackendCallback callback) override {
std::move(callback).Run(std::nullopt);
}
void DidGenerateCacheableMetadata(blink::mojom::CodeCacheType cache_type,
const GURL& url,
base::Time expected_response_time,
mojo_base::BigBuffer data) override {}
void FetchCachedCode(blink::mojom::CodeCacheType cache_type,
const GURL& url,
FetchCachedCodeCallback callback) override {
std::move(callback).Run(base::Time(), {});
}
void ClearCodeCacheEntry(blink::mojom::CodeCacheType cache_type,
const GURL& url) override {}
void DidGenerateCacheableMetadataInCacheStorage(
const GURL& url,
base::Time expected_response_time,
mojo_base::BigBuffer data,
const std::string& cache_storage_cache_name) override {}
private:
mojo::Receiver<blink::mojom::CodeCacheHost> receiver_{this};
};
bool IsBackgroundResourceFetchEnabled() const {
return GetParam() ==
BackgroundResourceFetchTestCase::kBackgroundResourceFetchEnabled;
}
void OnCodeCacheHostRequest(mojo::ScopedMessagePipeHandle handle) {
fake_code_cache_hosts_.emplace_back(std::make_unique<FakeCodeCacheHost>(
mojo::PendingReceiver<blink::mojom::CodeCacheHost>(std::move(handle))));
}
std::vector<std::unique_ptr<FakeCodeCacheHost>> fake_code_cache_hosts_;
base::test::ScopedFeatureList feature_background_resource_fetch_;
};
INSTANTIATE_TEST_SUITE_P(
All,
ContentSettingsAgentImplBrowserTest,
testing::ValuesIn(
{BackgroundResourceFetchTestCase::kBackgroundResourceFetchEnabled,
BackgroundResourceFetchTestCase::kBackgroundResourceFetchDisabled}),
[](const testing::TestParamInfo<BackgroundResourceFetchTestCase>& info) {
switch (info.param) {
case (BackgroundResourceFetchTestCase::kBackgroundResourceFetchEnabled):
return "BackgroundResourceFetchEnabled";
case (
BackgroundResourceFetchTestCase::kBackgroundResourceFetchDisabled):
return "BackgroundResourceFetchDisabled";
}
});
TEST_P(ContentSettingsAgentImplBrowserTest, DidBlockContentType) {
MockContentSettingsAgentImpl mock_agent(GetMainRenderFrame());
mock_agent.DidBlockContentType(ContentSettingsType::COOKIES);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, mock_agent.on_content_blocked_count());
EXPECT_EQ(ContentSettingsType::COOKIES, mock_agent.on_content_blocked_type());
// Blocking the same content type a second time shouldn't send a notification.
mock_agent.DidBlockContentType(ContentSettingsType::COOKIES);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, mock_agent.on_content_blocked_count());
}
// Tests that multiple invocations of AllowStorageAccessSync result in a single
// IPC.
TEST_P(ContentSettingsAgentImplBrowserTest, AllowStorageAccessSync) {
// Load some HTML, so we have a valid security origin.
LoadHTMLWithUrlOverride("<html></html>", "https://example.com/");
MockContentSettingsAgentImpl mock_agent(GetMainRenderFrame());
mock_agent.AllowStorageAccessSync(
blink::WebContentSettingsClient::StorageType::kLocalStorage);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, mock_agent.allow_storage_access_count());
// Accessing localStorage from the same origin again shouldn't result in a
// new IPC.
mock_agent.AllowStorageAccessSync(
blink::WebContentSettingsClient::StorageType::kLocalStorage);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, mock_agent.allow_storage_access_count());
}
// Tests that multiple invocations of AllowStorageAccess result in a single IPC.
TEST_P(ContentSettingsAgentImplBrowserTest, AllowStorageAccess) {
// Load some HTML, so we have a valid security origin.
LoadHTMLWithUrlOverride("<html></html>", "https://example.com/");
MockContentSettingsAgentImpl mock_agent(GetMainRenderFrame());
mock_agent.AllowStorageAccess(
blink::WebContentSettingsClient::StorageType::kLocalStorage,
base::DoNothing());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, mock_agent.allow_storage_access_count());
// Accessing localStorage from the same origin again shouldn't result in a
// new IPC.
mock_agent.AllowStorageAccess(
blink::WebContentSettingsClient::StorageType::kLocalStorage,
base::DoNothing());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, mock_agent.allow_storage_access_count());
}
TEST_P(ContentSettingsAgentImplBrowserTest, MixedAutoupgradesDisabledByRules) {
MockContentSettingsAgentImpl mock_agent(GetMainRenderFrame());
LoadHTMLWithUrlOverride("<html></html>", "https://example.com/");
// Set the default mixed content blocking setting.
RendererContentSettingRules content_setting_rules;
ContentSettingsForOneType& mixed_content_setting_rules =
content_setting_rules.mixed_content_rules;
mixed_content_setting_rules.push_back(ContentSettingPatternSource(
ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
ContentSettingToValue(CONTENT_SETTING_BLOCK), ProviderType::kNone,
false));
ContentSettingsAgentImpl* agent =
ContentSettingsAgentImpl::Get(GetMainRenderFrame());
agent->SetRendererContentSettingRulesForTest(content_setting_rules);
EXPECT_TRUE(agent->ShouldAutoupgradeMixedContent());
// Create an exception which allows mixed content.
mixed_content_setting_rules.insert(
mixed_content_setting_rules.begin(),
ContentSettingPatternSource(
ContentSettingsPattern::FromString("https://example.com/"),
ContentSettingsPattern::Wildcard(),
ContentSettingToValue(CONTENT_SETTING_ALLOW), ProviderType::kNone,
false));
agent->SetRendererContentSettingRulesForTest(content_setting_rules);
EXPECT_FALSE(agent->ShouldAutoupgradeMixedContent());
}
TEST_P(ContentSettingsAgentImplBrowserTest, MixedAutoupgradesNoSettingsSet) {
MockContentSettingsAgentImpl mock_agent(GetMainRenderFrame());
ContentSettingsAgentImpl* agent =
ContentSettingsAgentImpl::Get(GetMainRenderFrame());
EXPECT_TRUE(agent->ShouldAutoupgradeMixedContent());
}
} // namespace content_settings