blob: 03a77b7c091d767e1e1ee58a4b57df86438e6f48 [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 <memory>
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/path_service.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "chrome/browser/content_settings/cookie_settings_factory.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/extensions/api/content_settings/content_settings_api.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/permissions/permission_manager_factory.h"
#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "components/content_settings/core/browser/content_settings_uma_util.h"
#include "components/content_settings/core/browser/cookie_settings.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/permissions/permission_manager.h"
#include "components/prefs/pref_service.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/webplugininfo.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/process_manager.h"
#include "extensions/browser/test_extension_registry_observer.h"
#include "extensions/common/extension_features.h"
#include "extensions/test/test_extension_dir.h"
#include "net/base/schemeful_site.h"
#include "net/dns/mock_host_resolver.h"
#if !BUILDFLAG(IS_ANDROID)
#include "components/keep_alive_registry/keep_alive_types.h"
#include "components/keep_alive_registry/scoped_keep_alive.h"
#endif
#if BUILDFLAG(ENABLE_PLUGINS)
#include "content/public/browser/plugin_service.h"
#endif
namespace extensions {
using ContextType = extensions::browser_test_util::ContextType;
class ExtensionContentSettingsApiTest : public ExtensionApiTest {
public:
explicit ExtensionContentSettingsApiTest(
ContextType context_type = ContextType::kNone)
: ExtensionApiTest(context_type) {}
~ExtensionContentSettingsApiTest() override = default;
ExtensionContentSettingsApiTest(const ExtensionContentSettingsApiTest&) =
delete;
ExtensionContentSettingsApiTest& operator=(
const ExtensionContentSettingsApiTest&) = delete;
void SetUpOnMainThread() override {
ExtensionApiTest::SetUpOnMainThread();
// The browser might get closed later (and therefore be destroyed), so we
// save the profile.
profile_ = profile();
#if !BUILDFLAG(IS_ANDROID)
// Closing the last browser window also releases a KeepAlive. Make
// sure it's not the last one, so the message loop doesn't quit
// unexpectedly.
keep_alive_ = std::make_unique<ScopedKeepAlive>(
KeepAliveOrigin::BROWSER, KeepAliveRestartOption::DISABLED);
#endif
profile_keep_alive_ = std::make_unique<ScopedProfileKeepAlive>(
profile_, ProfileKeepAliveOrigin::kBrowserWindow);
}
void TearDownOnMainThread() override {
profile_keep_alive_.reset();
#if !BUILDFLAG(IS_ANDROID)
// BrowserProcess::Shutdown() needs to be called in a message loop, so we
// post a task to release the keep alive, then run the message loop.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&std::unique_ptr<ScopedKeepAlive>::reset,
base::Unretained(&keep_alive_), nullptr));
content::RunAllPendingInMessageLoop();
#endif
ExtensionApiTest::TearDownOnMainThread();
}
protected:
void CheckContentSettingsSet() {
HostContentSettingsMap* map =
HostContentSettingsMapFactory::GetForProfile(profile_);
content_settings::CookieSettings* cookie_settings =
CookieSettingsFactory::GetForProfile(profile_).get();
// Check default content settings by using an unknown URL.
GURL example_url("http://www.example.com");
EXPECT_TRUE(cookie_settings->IsFullCookieAccessAllowed(
example_url, net::SiteForCookies::FromUrl(example_url),
url::Origin::Create(example_url), net::CookieSettingOverrides(),
/*cookie_partition_key=*/std::nullopt));
EXPECT_TRUE(cookie_settings->IsCookieSessionOnly(example_url));
EXPECT_EQ(CONTENT_SETTING_ALLOW,
map->GetContentSetting(example_url, example_url,
ContentSettingsType::IMAGES));
EXPECT_EQ(CONTENT_SETTING_BLOCK,
map->GetContentSetting(example_url, example_url,
ContentSettingsType::JAVASCRIPT));
EXPECT_EQ(CONTENT_SETTING_BLOCK,
map->GetContentSetting(example_url, example_url,
ContentSettingsType::POPUPS));
EXPECT_EQ(CONTENT_SETTING_ASK,
map->GetContentSetting(example_url, example_url,
ContentSettingsType::GEOLOCATION));
EXPECT_EQ(CONTENT_SETTING_ASK,
map->GetContentSetting(example_url, example_url,
ContentSettingsType::NOTIFICATIONS));
EXPECT_EQ(CONTENT_SETTING_ASK,
map->GetContentSetting(example_url, example_url,
ContentSettingsType::MEDIASTREAM_MIC));
EXPECT_EQ(CONTENT_SETTING_ASK,
map->GetContentSetting(example_url, example_url,
ContentSettingsType::MEDIASTREAM_CAMERA));
EXPECT_EQ(CONTENT_SETTING_ASK,
map->GetContentSetting(example_url, example_url,
ContentSettingsType::AUTOMATIC_DOWNLOADS));
EXPECT_EQ(CONTENT_SETTING_ALLOW,
map->GetContentSetting(example_url, example_url,
ContentSettingsType::AUTOPLAY));
EXPECT_EQ(CONTENT_SETTING_ALLOW,
map->GetContentSetting(example_url, example_url,
ContentSettingsType::SOUND));
EXPECT_EQ(CONTENT_SETTING_BLOCK,
map->GetContentSetting(example_url, example_url,
ContentSettingsType::ANTI_ABUSE));
EXPECT_EQ(
CONTENT_SETTING_ASK,
map->GetContentSetting(example_url, example_url,
ContentSettingsType::CLIPBOARD_READ_WRITE));
// Check content settings for www.google.com
GURL url("http://www.google.com");
EXPECT_FALSE(cookie_settings->IsFullCookieAccessAllowed(
url, net::SiteForCookies::FromUrl(url), url::Origin::Create(url),
net::CookieSettingOverrides(), /*cookie_partition_key=*/std::nullopt));
EXPECT_EQ(CONTENT_SETTING_ALLOW,
map->GetContentSetting(url, url, ContentSettingsType::IMAGES));
EXPECT_EQ(
CONTENT_SETTING_BLOCK,
map->GetContentSetting(url, url, ContentSettingsType::JAVASCRIPT));
EXPECT_EQ(CONTENT_SETTING_ALLOW,
map->GetContentSetting(url, url, ContentSettingsType::POPUPS));
EXPECT_EQ(
CONTENT_SETTING_BLOCK,
map->GetContentSetting(url, url, ContentSettingsType::GEOLOCATION));
EXPECT_EQ(
CONTENT_SETTING_BLOCK,
map->GetContentSetting(url, url, ContentSettingsType::NOTIFICATIONS));
EXPECT_EQ(
CONTENT_SETTING_BLOCK,
map->GetContentSetting(url, url, ContentSettingsType::MEDIASTREAM_MIC));
EXPECT_EQ(CONTENT_SETTING_BLOCK,
map->GetContentSetting(url, url,
ContentSettingsType::MEDIASTREAM_CAMERA));
EXPECT_EQ(CONTENT_SETTING_BLOCK,
map->GetContentSetting(url, url,
ContentSettingsType::AUTOMATIC_DOWNLOADS));
EXPECT_EQ(CONTENT_SETTING_ALLOW,
map->GetContentSetting(url, url, ContentSettingsType::AUTOPLAY));
EXPECT_EQ(CONTENT_SETTING_BLOCK,
map->GetContentSetting(url, url, ContentSettingsType::SOUND));
EXPECT_EQ(
CONTENT_SETTING_BLOCK,
map->GetContentSetting(url, url, ContentSettingsType::ANTI_ABUSE));
EXPECT_EQ(base::FeatureList::IsEnabled(
extensions_features::kApiContentSettingsClipboard)
? CONTENT_SETTING_BLOCK
: CONTENT_SETTING_ASK,
map->GetContentSetting(
url, url, ContentSettingsType::CLIPBOARD_READ_WRITE));
}
void CheckContentSettingsDefault() {
HostContentSettingsMap* map =
HostContentSettingsMapFactory::GetForProfile(profile_);
content_settings::CookieSettings* cookie_settings =
CookieSettingsFactory::GetForProfile(profile_).get();
// Check content settings for www.google.com
GURL url("http://www.google.com");
EXPECT_TRUE(cookie_settings->IsFullCookieAccessAllowed(
url, net::SiteForCookies::FromUrl(url), url::Origin::Create(url),
net::CookieSettingOverrides(), /*cookie_partition_key=*/std::nullopt));
EXPECT_FALSE(cookie_settings->IsCookieSessionOnly(url));
EXPECT_EQ(CONTENT_SETTING_ALLOW,
map->GetContentSetting(url, url, ContentSettingsType::IMAGES));
EXPECT_EQ(
CONTENT_SETTING_ALLOW,
map->GetContentSetting(url, url, ContentSettingsType::JAVASCRIPT));
EXPECT_EQ(CONTENT_SETTING_BLOCK,
map->GetContentSetting(url, url, ContentSettingsType::POPUPS));
EXPECT_EQ(
CONTENT_SETTING_ASK,
map->GetContentSetting(url, url, ContentSettingsType::GEOLOCATION));
EXPECT_EQ(
CONTENT_SETTING_ASK,
map->GetContentSetting(url, url, ContentSettingsType::NOTIFICATIONS));
EXPECT_EQ(
CONTENT_SETTING_ASK,
map->GetContentSetting(url, url, ContentSettingsType::MEDIASTREAM_MIC));
EXPECT_EQ(CONTENT_SETTING_ASK,
map->GetContentSetting(url, url,
ContentSettingsType::MEDIASTREAM_CAMERA));
EXPECT_EQ(CONTENT_SETTING_ASK,
map->GetContentSetting(url, url,
ContentSettingsType::AUTOMATIC_DOWNLOADS));
EXPECT_EQ(CONTENT_SETTING_ALLOW,
map->GetContentSetting(url, url, ContentSettingsType::AUTOPLAY));
EXPECT_EQ(CONTENT_SETTING_ALLOW,
map->GetContentSetting(url, url, ContentSettingsType::SOUND));
EXPECT_EQ(
CONTENT_SETTING_ALLOW,
map->GetContentSetting(url, url, ContentSettingsType::ANTI_ABUSE));
EXPECT_EQ(CONTENT_SETTING_ASK,
map->GetContentSetting(
url, url, ContentSettingsType::CLIPBOARD_READ_WRITE));
}
// Returns a snapshot of content settings for a given URL.
std::vector<int> GetContentSettingsSnapshot(const GURL& url) {
std::vector<int> content_settings;
HostContentSettingsMap* map =
HostContentSettingsMapFactory::GetForProfile(profile_);
content_settings::CookieSettings* cookie_settings =
CookieSettingsFactory::GetForProfile(profile_).get();
content_settings.push_back(cookie_settings->IsFullCookieAccessAllowed(
url, net::SiteForCookies::FromUrl(url), url::Origin::Create(url),
net::CookieSettingOverrides(), /*cookie_partition_key=*/std::nullopt));
content_settings.push_back(cookie_settings->IsCookieSessionOnly(url));
content_settings.push_back(
map->GetContentSetting(url, url, ContentSettingsType::IMAGES));
content_settings.push_back(
map->GetContentSetting(url, url, ContentSettingsType::JAVASCRIPT));
content_settings.push_back(
map->GetContentSetting(url, url, ContentSettingsType::POPUPS));
content_settings.push_back(
map->GetContentSetting(url, url, ContentSettingsType::GEOLOCATION));
content_settings.push_back(
map->GetContentSetting(url, url, ContentSettingsType::NOTIFICATIONS));
content_settings.push_back(
map->GetContentSetting(url, url, ContentSettingsType::MEDIASTREAM_MIC));
content_settings.push_back(map->GetContentSetting(
url, url, ContentSettingsType::MEDIASTREAM_CAMERA));
content_settings.push_back(map->GetContentSetting(
url, url, ContentSettingsType::AUTOMATIC_DOWNLOADS));
content_settings.push_back(
map->GetContentSetting(url, url, ContentSettingsType::AUTOPLAY));
content_settings.push_back(map->GetContentSetting(
url, url, ContentSettingsType::CLIPBOARD_READ_WRITE));
return content_settings;
}
private:
raw_ptr<Profile, AcrossTasksDanglingUntriaged> profile_ = nullptr;
#if !BUILDFLAG(IS_ANDROID)
// KeepAlive is not supported nor required on Android.
std::unique_ptr<ScopedKeepAlive> keep_alive_;
#endif
std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive_;
};
class ExtensionContentSettingsApiTestWithClipboard
: public ExtensionContentSettingsApiTest,
public testing::WithParamInterface<bool> {
public:
ExtensionContentSettingsApiTestWithClipboard()
: ExtensionContentSettingsApiTest() {
scoped_feature_list_.InitWithFeatureState(
extensions_features::kApiContentSettingsClipboard, GetParam());
}
~ExtensionContentSettingsApiTestWithClipboard() override = default;
ExtensionContentSettingsApiTestWithClipboard(
const ExtensionContentSettingsApiTestWithClipboard&) = delete;
ExtensionContentSettingsApiTestWithClipboard& operator=(
const ExtensionContentSettingsApiTestWithClipboard&) = delete;
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(All,
ExtensionContentSettingsApiTestWithClipboard,
::testing::Bool());
IN_PROC_BROWSER_TEST_P(ExtensionContentSettingsApiTestWithClipboard, Standard) {
CheckContentSettingsDefault();
static constexpr char kExtensionPath[] = "content_settings/standard";
EXPECT_TRUE(RunExtensionTest(kExtensionPath, {.extension_url = "test.html"}))
<< message_;
CheckContentSettingsSet();
// The settings should not be reset when the extension is reloaded.
ReloadExtension(last_loaded_extension_id());
CheckContentSettingsSet();
// Uninstalling and installing the extension (without running the test that
// calls the extension API) should clear the settings.
TestExtensionRegistryObserver observer(ExtensionRegistry::Get(profile()),
last_loaded_extension_id());
UninstallExtension(last_loaded_extension_id());
observer.WaitForExtensionUninstalled();
CheckContentSettingsDefault();
LoadExtension(test_data_dir_.AppendASCII(kExtensionPath));
CheckContentSettingsDefault();
}
IN_PROC_BROWSER_TEST_F(ExtensionContentSettingsApiTest,
UnsupportedDefaultSettings) {
const char kExtensionPath[] = "content_settings/unsupporteddefaultsettings";
EXPECT_TRUE(RunExtensionTest(kExtensionPath)) << message_;
}
// Tests if an extension clearing content settings for one content type leaves
// the others unchanged.
IN_PROC_BROWSER_TEST_F(ExtensionContentSettingsApiTest, ClearProperlyGranular) {
const char kExtensionPath[] = "content_settings/clearproperlygranular";
EXPECT_TRUE(RunExtensionTest(kExtensionPath)) << message_;
}
// Tests if changing permissions in incognito mode keeps the previous state of
// regular mode.
IN_PROC_BROWSER_TEST_F(ExtensionContentSettingsApiTest, IncognitoIsolation) {
GURL url("http://www.example.com");
// Record previous state of content settings.
std::vector<int> content_settings_before = GetContentSettingsSnapshot(url);
// Run extension, set all permissions to allow, and check if they are changed.
ASSERT_TRUE(RunExtensionTest("content_settings/incognitoisolation",
{.extension_url = "test.html",
.custom_arg = "allow",
.open_in_incognito = true},
{.allow_in_incognito = true}))
<< message_;
// Get content settings after running extension to ensure nothing is changed.
std::vector<int> content_settings_after = GetContentSettingsSnapshot(url);
EXPECT_EQ(content_settings_before, content_settings_after);
// Run extension, set all permissions to block, and check if they are changed.
ASSERT_TRUE(RunExtensionTest("content_settings/incognitoisolation",
{.extension_url = "test.html",
.custom_arg = "block",
.open_in_incognito = true},
{.allow_in_incognito = true}))
<< message_;
// Get content settings after running extension to ensure nothing is changed.
content_settings_after = GetContentSettingsSnapshot(url);
EXPECT_EQ(content_settings_before, content_settings_after);
}
// Tests if changing incognito mode permissions in regular profile are rejected.
IN_PROC_BROWSER_TEST_F(ExtensionContentSettingsApiTest,
IncognitoNotAllowedInRegular) {
EXPECT_FALSE(
RunExtensionTest("content_settings/incognitoisolation",
{.extension_url = "test.html", .custom_arg = "allow"}))
<< message_;
}
IN_PROC_BROWSER_TEST_F(ExtensionContentSettingsApiTest,
EmbeddedSettingsMetric) {
base::HistogramTester histogram_tester;
const char kExtensionPath[] = "content_settings/embeddedsettingsmetric";
EXPECT_TRUE(RunExtensionTest(kExtensionPath)) << message_;
int images_type =
content_settings_uma_util::ContentSettingTypeToHistogramValue(
ContentSettingsType::IMAGES);
int geolocation_type =
content_settings_uma_util::ContentSettingTypeToHistogramValue(
ContentSettingsType::GEOLOCATION);
int cookies_type =
content_settings_uma_util::ContentSettingTypeToHistogramValue(
ContentSettingsType::COOKIES);
histogram_tester.ExpectBucketCount(
"ContentSettings.ExtensionEmbeddedSettingSet", images_type, 1);
histogram_tester.ExpectBucketCount(
"ContentSettings.ExtensionEmbeddedSettingSet", geolocation_type, 1);
histogram_tester.ExpectTotalCount(
"ContentSettings.ExtensionEmbeddedSettingSet", 2);
histogram_tester.ExpectBucketCount(
"ContentSettings.ExtensionNonEmbeddedSettingSet", images_type, 1);
histogram_tester.ExpectBucketCount(
"ContentSettings.ExtensionNonEmbeddedSettingSet", cookies_type, 1);
histogram_tester.ExpectTotalCount(
"ContentSettings.ExtensionNonEmbeddedSettingSet", 2);
}
#if BUILDFLAG(ENABLE_PLUGINS)
IN_PROC_BROWSER_TEST_F(ExtensionContentSettingsApiTest, ConsoleErrorTest) {
constexpr char kExtensionPath[] = "content_settings/disablepluginsapi";
const extensions::Extension* extension =
LoadExtension(test_data_dir_.AppendASCII(kExtensionPath));
ASSERT_TRUE(extension);
auto* web_contents = extensions::ProcessManager::Get(profile())
->GetBackgroundHostForExtension(extension->id())
->host_contents();
content::WebContentsConsoleObserver console_observer(web_contents);
console_observer.SetPattern("*contentSettings.plugins is deprecated.*");
ExecuteScriptInBackgroundPageNoWait(extension->id(), "setPluginsSetting()");
ASSERT_TRUE(console_observer.Wait());
EXPECT_EQ(1u, console_observer.messages().size());
}
#endif // BUILDFLAG(ENABLE_PLUGINS)
class ImageContentSettingApiTest : public ExtensionApiTest {
public:
void LoadImageContentSettingExtension() {
static constexpr char kManifest[] =
R"({
"name": "MV3 ImageContentSetting",
"version": "0.1",
"manifest_version": 3,
"permissions": ["contentSettings"],
"background": {"service_worker": "background.js"}
})";
static constexpr char kBackgroundJs[] =
R"(
chrome.contentSettings['images'].set({
primaryPattern: 'http://*.example1.com/*',
setting: 'block',
scope: 'regular'
});
)";
test_extension_dir_.WriteManifest(kManifest);
test_extension_dir_.WriteFile(FILE_PATH_LITERAL("background.js"),
kBackgroundJs);
const Extension* extension =
LoadExtension(test_extension_dir_.UnpackedPath());
ASSERT_TRUE(extension);
}
void SetUpOnMainThread() override {
ExtensionApiTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
embedded_test_server()->ServeFilesFromDirectory(temp_dir_.GetPath());
ASSERT_TRUE(StartEmbeddedTestServer());
}
protected:
base::ScopedTempDir temp_dir_;
TestExtensionDir test_extension_dir_;
};
// Tests that image content setting primary pattern can be used to block image
// loads.
IN_PROC_BROWSER_TEST_F(ImageContentSettingApiTest, OriginBlocking) {
LoadImageContentSettingExtension();
std::string page_js =
R"(
<body onload="console.log('body load');">
<img src=$2 onload="console.log('example2 load');">
<img src=$1 onload="console.log('example1 load');">
</body>
)";
GURL example1_img =
embedded_test_server()->GetURL("example1.com", "/test.png");
GURL example2_img =
embedded_test_server()->GetURL("example2.com", "/test.png");
page_js = content::JsReplace(page_js, example1_img, example2_img);
{
base::ScopedAllowBlockingForTesting allow_blocking;
ASSERT_TRUE(base::WriteFile(temp_dir_.GetPath().AppendASCII("index.html"),
page_js));
base::FilePath test_data_dir;
base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir);
ASSERT_TRUE(
base::CopyFile(test_data_dir.AppendASCII("extensions/icon1.png"),
temp_dir_.GetPath().AppendASCII("test.png")));
}
content::WebContents* web_contents = GetActiveWebContents();
content::WebContentsConsoleObserver body_load_observer(web_contents);
body_load_observer.SetPattern("body load");
content::WebContentsConsoleObserver observer(web_contents);
GURL example1_index =
embedded_test_server()->GetURL("example1.com", "/index.html");
ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), example1_index));
// The onload event will fire when there are no more pending image loads. We
// should then have one messages -- one for the onload event. Neither "example
// 1" nor "example 2" should have loaded.
EXPECT_TRUE(body_load_observer.Wait());
std::vector<std::u16string> message_strings;
for (const auto& message : observer.messages()) {
message_strings.push_back(message.message);
}
EXPECT_THAT(message_strings, testing::UnorderedElementsAre(u"body load"));
}
} // namespace extensions