blob: 54613bfec78d18a8ce65d79e78b582e5fbdc1f5c [file] [log] [blame]
// Copyright 2014 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 "chrome/browser/permissions/permission_context_base.h"
#include <map>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/feature_list.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/field_trial.h"
#include "base/run_loop.h"
#include "base/test/histogram_tester.h"
#include "base/test/mock_entropy_provider.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/infobars/infobar_service.h"
#include "chrome/browser/permissions/permission_decision_auto_blocker.h"
#include "chrome/browser/permissions/permission_manager.h"
#include "chrome/browser/permissions/permission_queue_controller.h"
#include "chrome/browser/permissions/permission_request_id.h"
#include "chrome/browser/permissions/permission_request_manager.h"
#include "chrome/browser/permissions/permission_uma_util.h"
#include "chrome/browser/permissions/permission_util.h"
#include "chrome/browser/ui/permission_bubble/mock_permission_prompt_factory.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_profile.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/safe_browsing_db/database_manager.h"
#include "components/safe_browsing_db/test_database_manager.h"
#include "components/variations/variations_associated_data.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/mock_render_process_host.h"
#include "testing/gtest/include/gtest/gtest.h"
const char* const kPermissionsKillSwitchFieldStudy =
PermissionContextBase::kPermissionsKillSwitchFieldStudy;
const char* const kPermissionsKillSwitchBlockedValue =
PermissionContextBase::kPermissionsKillSwitchBlockedValue;
const char kPermissionsKillSwitchTestGroup[] = "TestGroup";
const char* const kPromptGroupName = kPermissionsKillSwitchTestGroup;
const char kPromptTrialName[] = "PermissionPromptsUX";
namespace {
class MockSafeBrowsingDatabaseManager
: public safe_browsing::TestSafeBrowsingDatabaseManager {
public:
explicit MockSafeBrowsingDatabaseManager(bool perform_callback)
: perform_callback_(perform_callback) {}
bool CheckApiBlacklistUrl(
const GURL& url,
safe_browsing::SafeBrowsingDatabaseManager::Client* client) override {
if (perform_callback_) {
safe_browsing::ThreatMetadata metadata;
const auto& blacklisted_permissions = permissions_blacklist_.find(url);
if (blacklisted_permissions != permissions_blacklist_.end())
metadata.api_permissions = blacklisted_permissions->second;
client->OnCheckApiBlacklistUrlResult(url, metadata);
}
// Returns false if scheme is HTTP/HTTPS and able to be checked.
return false;
}
bool CancelApiCheck(Client* client) override {
DCHECK(!perform_callback_);
// Returns true when client check could be stopped.
return true;
}
void BlacklistUrlPermissions(const GURL& url,
const std::set<std::string> permissions) {
permissions_blacklist_[url] = permissions;
}
protected:
~MockSafeBrowsingDatabaseManager() override {}
private:
bool perform_callback_;
std::map<GURL, std::set<std::string>> permissions_blacklist_;
DISALLOW_COPY_AND_ASSIGN(MockSafeBrowsingDatabaseManager);
};
} // namespace
class TestPermissionContext : public PermissionContextBase {
public:
TestPermissionContext(Profile* profile,
const ContentSettingsType content_settings_type)
: PermissionContextBase(profile,
content_settings_type,
blink::WebFeaturePolicyFeature::kNotFound),
tab_context_updated_(false) {}
~TestPermissionContext() override {}
#if defined(OS_ANDROID)
PermissionQueueController* GetInfoBarController() {
return GetQueueController();
}
#endif
const std::vector<ContentSetting>& decisions() const { return decisions_; }
bool tab_context_updated() const { return tab_context_updated_; }
// Once a decision for the requested permission has been made, run the
// callback.
void TrackPermissionDecision(ContentSetting content_setting) {
decisions_.push_back(content_setting);
// Null check required here as the quit_closure_ can also be run and reset
// first from within DecidePermission.
if (quit_closure_) {
quit_closure_.Run();
quit_closure_.Reset();
}
}
ContentSetting GetContentSettingFromMap(const GURL& url_a,
const GURL& url_b) {
auto* map = HostContentSettingsMapFactory::GetForProfile(profile());
return map->GetContentSetting(url_a.GetOrigin(), url_b.GetOrigin(),
content_settings_storage_type(),
std::string());
}
void RequestPermission(content::WebContents* web_contents,
const PermissionRequestID& id,
const GURL& requesting_frame,
bool user_gesture,
const BrowserPermissionCallback& callback) override {
base::RunLoop run_loop;
quit_closure_ = run_loop.QuitClosure();
PermissionContextBase::RequestPermission(web_contents, id, requesting_frame,
true /* user_gesture */, callback);
run_loop.Run();
}
void DecidePermission(content::WebContents* web_contents,
const PermissionRequestID& id,
const GURL& requesting_origin,
const GURL& embedding_origin,
bool user_gesture,
const BrowserPermissionCallback& callback) override {
PermissionContextBase::DecidePermission(web_contents, id, requesting_origin,
embedding_origin, user_gesture,
callback);
if (respond_permission_) {
respond_permission_.Run();
respond_permission_.Reset();
} else {
// Stop the run loop from spinning indefinitely if no response callback
// has been set, as is the case with TestParallelRequests.
quit_closure_.Run();
quit_closure_.Reset();
}
}
// Set the callback to run if the permission is being responded to in the
// test. This is left empty where no response is needed, such as in parallel
// requests, permissions blacklisting, invalid origin, and killswitch.
void SetRespondPermissionCallback(base::Closure callback) {
respond_permission_ = callback;
}
protected:
void UpdateTabContext(const PermissionRequestID& id,
const GURL& requesting_origin,
bool allowed) override {
tab_context_updated_ = true;
}
bool IsRestrictedToSecureOrigins() const override { return false; }
private:
std::vector<ContentSetting> decisions_;
bool tab_context_updated_;
base::Closure quit_closure_;
// Callback for responding to a permission once the request has been completed
// (valid URL, kill switch disabled, not blacklisted)
base::Closure respond_permission_;
DISALLOW_COPY_AND_ASSIGN(TestPermissionContext);
};
class TestKillSwitchPermissionContext : public TestPermissionContext {
public:
TestKillSwitchPermissionContext(
Profile* profile,
const ContentSettingsType content_settings_type)
: TestPermissionContext(profile, content_settings_type),
field_trial_list_(base::MakeUnique<base::FieldTrialList>(
base::MakeUnique<base::MockEntropyProvider>())) {}
void ResetFieldTrialList() {
// Destroy the existing FieldTrialList before creating a new one to avoid
// a DCHECK.
field_trial_list_.reset();
field_trial_list_ = base::MakeUnique<base::FieldTrialList>(
base::MakeUnique<base::MockEntropyProvider>());
variations::testing::ClearAllVariationParams();
}
private:
std::unique_ptr<base::FieldTrialList> field_trial_list_;
DISALLOW_COPY_AND_ASSIGN(TestKillSwitchPermissionContext);
};
enum class TestType {
PERMISSION_REQUEST_MANAGER,
PERMISSION_QUEUE_CONTROLLER,
};
class PermissionContextBaseTests
: public ChromeRenderViewHostTestHarness,
public ::testing::WithParamInterface<TestType> {
protected:
PermissionContextBaseTests() {}
~PermissionContextBaseTests() override {}
// Accept or dismiss the permission bubble or infobar.
void RespondToPermission(TestPermissionContext* context,
const PermissionRequestID& id,
const GURL& url,
bool persist,
ContentSetting response) {
DCHECK(response == CONTENT_SETTING_ALLOW ||
response == CONTENT_SETTING_BLOCK ||
response == CONTENT_SETTING_ASK);
if (GetParam() == TestType::PERMISSION_REQUEST_MANAGER) {
PermissionRequestManager* manager =
PermissionRequestManager::FromWebContents(web_contents());
manager->TogglePersist(persist);
using AutoResponseType = PermissionRequestManager::AutoResponseType;
AutoResponseType decision = AutoResponseType::DISMISS;
if (response == CONTENT_SETTING_ALLOW)
decision = AutoResponseType::ACCEPT_ALL;
else if (response == CONTENT_SETTING_BLOCK)
decision = AutoResponseType::DENY_ALL;
prompt_factory_->set_response_type(decision);
} else {
#if defined(OS_ANDROID)
PermissionAction decision = PermissionAction::DISMISSED;
if (response == CONTENT_SETTING_ALLOW)
decision = PermissionAction::GRANTED;
else if (response == CONTENT_SETTING_BLOCK)
decision = PermissionAction::DENIED;
context->GetInfoBarController()->OnPermissionSet(
id, url, url, false /* user_gesture */, persist, decision);
#endif
}
}
void TestAskAndDecide_TestContent(ContentSettingsType content_settings_type,
ContentSetting decision,
bool persist) {
TestPermissionContext permission_context(profile(), content_settings_type);
GURL url("https://www.google.com");
SetUpUrl(url);
base::HistogramTester histograms;
const PermissionRequestID id(
web_contents()->GetRenderProcessHost()->GetID(),
web_contents()->GetMainFrame()->GetRoutingID(), -1);
permission_context.SetRespondPermissionCallback(
base::Bind(&PermissionContextBaseTests::RespondToPermission,
base::Unretained(this), &permission_context, id, url,
persist, decision));
permission_context.RequestPermission(
web_contents(), id, url, true /* user_gesture */,
base::Bind(&TestPermissionContext::TrackPermissionDecision,
base::Unretained(&permission_context)));
ASSERT_EQ(1u, permission_context.decisions().size());
EXPECT_EQ(decision, permission_context.decisions()[0]);
EXPECT_TRUE(permission_context.tab_context_updated());
std::string decision_string;
if (decision == CONTENT_SETTING_ALLOW)
decision_string = "Accepted";
else if (decision == CONTENT_SETTING_BLOCK)
decision_string = "Denied";
else if (decision == CONTENT_SETTING_ASK)
decision_string = "Dismissed";
if (decision_string.size()) {
histograms.ExpectUniqueSample(
"Permissions.Prompt." + decision_string + ".PriorDismissCount." +
PermissionUtil::GetPermissionString(content_settings_type),
0, 1);
histograms.ExpectUniqueSample(
"Permissions.Prompt." + decision_string + ".PriorIgnoreCount." +
PermissionUtil::GetPermissionString(content_settings_type),
0, 1);
}
if (persist) {
EXPECT_EQ(decision,
permission_context.GetContentSettingFromMap(url, url));
} else {
EXPECT_EQ(CONTENT_SETTING_ASK,
permission_context.GetContentSettingFromMap(url, url));
}
histograms.ExpectUniqueSample(
"Permissions.AutoBlocker.EmbargoPromptSuppression",
static_cast<int>(PermissionEmbargoStatus::NOT_EMBARGOED), 1);
histograms.ExpectUniqueSample(
"Permissions.AutoBlocker.EmbargoStatus",
static_cast<int>(PermissionEmbargoStatus::NOT_EMBARGOED), 1);
}
void DismissMultipleTimesAndExpectBlock(
const GURL& url,
ContentSettingsType content_settings_type,
uint32_t iterations) {
base::HistogramTester histograms;
// Dismiss |iterations| times. The final dismiss should change the decision
// from dismiss to block, and hence change the persisted content setting.
for (uint32_t i = 0; i < iterations; ++i) {
TestPermissionContext permission_context(profile(),
content_settings_type);
const PermissionRequestID id(
web_contents()->GetRenderProcessHost()->GetID(),
web_contents()->GetMainFrame()->GetRoutingID(), i);
permission_context.SetRespondPermissionCallback(
base::Bind(&PermissionContextBaseTests::RespondToPermission,
base::Unretained(this), &permission_context, id, url,
false, CONTENT_SETTING_ASK));
permission_context.RequestPermission(
web_contents(), id, url, true /* user_gesture */,
base::Bind(&TestPermissionContext::TrackPermissionDecision,
base::Unretained(&permission_context)));
histograms.ExpectTotalCount(
"Permissions.Prompt.Dismissed.PriorDismissCount." +
PermissionUtil::GetPermissionString(content_settings_type),
i + 1);
histograms.ExpectBucketCount(
"Permissions.Prompt.Dismissed.PriorDismissCount." +
PermissionUtil::GetPermissionString(content_settings_type),
i, 1);
// On Android, repeatedly requesting and deciding permissions has the side
// effect of overcounting any metrics recorded in the
// PermissionInfoBarDelegate destructor. This is because we directly call
// PermissionQueueController::OnPermissionSet without setting the
// action_taken bit in PermissionInfoBarDelegate. When
// PermissionQueueController is deleted these expectations can be made
// unconditional.
if (GetParam() == TestType::PERMISSION_REQUEST_MANAGER) {
histograms.ExpectTotalCount("Permissions.AutoBlocker.EmbargoStatus",
i + 1);
}
PermissionResult result = permission_context.GetPermissionStatus(
nullptr /* render_frame_host */, url, url);
histograms.ExpectUniqueSample(
"Permissions.AutoBlocker.EmbargoPromptSuppression",
static_cast<int>(PermissionEmbargoStatus::NOT_EMBARGOED), i + 1);
if (i < 2) {
EXPECT_EQ(PermissionStatusSource::UNSPECIFIED, result.source);
EXPECT_EQ(CONTENT_SETTING_ASK, result.content_setting);
if (GetParam() == TestType::PERMISSION_REQUEST_MANAGER) {
histograms.ExpectUniqueSample(
"Permissions.AutoBlocker.EmbargoStatus",
static_cast<int>(PermissionEmbargoStatus::NOT_EMBARGOED), i + 1);
}
} else {
EXPECT_EQ(PermissionStatusSource::MULTIPLE_DISMISSALS, result.source);
EXPECT_EQ(CONTENT_SETTING_BLOCK, result.content_setting);
if (GetParam() == TestType::PERMISSION_REQUEST_MANAGER) {
histograms.ExpectBucketCount(
"Permissions.AutoBlocker.EmbargoStatus",
static_cast<int>(PermissionEmbargoStatus::REPEATED_DISMISSALS),
1);
}
}
ASSERT_EQ(1u, permission_context.decisions().size());
EXPECT_EQ(CONTENT_SETTING_ASK, permission_context.decisions()[0]);
EXPECT_TRUE(permission_context.tab_context_updated());
}
TestPermissionContext permission_context(profile(), content_settings_type);
const PermissionRequestID id(
web_contents()->GetRenderProcessHost()->GetID(),
web_contents()->GetMainFrame()->GetRoutingID(), -1);
permission_context.SetRespondPermissionCallback(
base::Bind(&PermissionContextBaseTests::RespondToPermission,
base::Unretained(this), &permission_context, id, url, false,
CONTENT_SETTING_ASK));
permission_context.RequestPermission(
web_contents(), id, url, true /* user_gesture */,
base::Bind(&TestPermissionContext::TrackPermissionDecision,
base::Unretained(&permission_context)));
PermissionResult result = permission_context.GetPermissionStatus(
nullptr /* render_frame_host */, url, url);
EXPECT_EQ(CONTENT_SETTING_BLOCK, result.content_setting);
EXPECT_EQ(PermissionStatusSource::MULTIPLE_DISMISSALS, result.source);
histograms.ExpectBucketCount(
"Permissions.AutoBlocker.EmbargoPromptSuppression",
static_cast<int>(PermissionEmbargoStatus::REPEATED_DISMISSALS), 1);
}
void TestBlockOnSeveralDismissals_TestContent() {
GURL url("https://www.google.com");
SetUpUrl(url);
base::HistogramTester histograms;
{
// Ensure that > 3 dismissals behaves correctly when the
// BlockPromptsIfDismissedOften feature is off.
base::test::ScopedFeatureList feature_list;
if (GetParam() == TestType::PERMISSION_REQUEST_MANAGER) {
feature_list.InitWithFeatures(
{features::kUseGroupedPermissionInfobars},
{features::kBlockPromptsIfDismissedOften});
} else {
feature_list.InitWithFeatures(
{}, {features::kUseGroupedPermissionInfobars,
features::kBlockPromptsIfDismissedOften});
}
for (uint32_t i = 0; i < 4; ++i) {
TestPermissionContext permission_context(
profile(), CONTENT_SETTINGS_TYPE_GEOLOCATION);
const PermissionRequestID id(
web_contents()->GetRenderProcessHost()->GetID(),
web_contents()->GetMainFrame()->GetRoutingID(), i);
permission_context.SetRespondPermissionCallback(
base::Bind(&PermissionContextBaseTests::RespondToPermission,
base::Unretained(this), &permission_context, id, url,
false, CONTENT_SETTING_ASK));
permission_context.RequestPermission(
web_contents(), id, url, true /* user_gesture */,
base::Bind(&TestPermissionContext::TrackPermissionDecision,
base::Unretained(&permission_context)));
histograms.ExpectTotalCount(
"Permissions.Prompt.Dismissed.PriorDismissCount.Geolocation",
i + 1);
histograms.ExpectBucketCount(
"Permissions.Prompt.Dismissed.PriorDismissCount.Geolocation", i, 1);
histograms.ExpectUniqueSample(
"Permissions.AutoBlocker.EmbargoPromptSuppression",
static_cast<int>(PermissionEmbargoStatus::NOT_EMBARGOED), i + 1);
// On Android, repeatedly requesting and deciding permissions has the
// side effect of overcounting any metrics recorded in the
// PermissionInfoBarDelegate destructor. This is because we directly
// call PermissionQueueController::OnPermissionSet without setting the
// action_taken bit in PermissionInfoBarDelegate. When
// PermissionQueueController is deleted this expectation can be made
// unconditional.
if (GetParam() == TestType::PERMISSION_REQUEST_MANAGER) {
histograms.ExpectUniqueSample(
"Permissions.AutoBlocker.EmbargoStatus",
static_cast<int>(PermissionEmbargoStatus::NOT_EMBARGOED), i + 1);
}
ASSERT_EQ(1u, permission_context.decisions().size());
EXPECT_EQ(CONTENT_SETTING_ASK, permission_context.decisions()[0]);
EXPECT_TRUE(permission_context.tab_context_updated());
EXPECT_EQ(CONTENT_SETTING_ASK,
permission_context.GetContentSettingFromMap(url, url));
}
// Flush the dismissal counts.
auto* map = HostContentSettingsMapFactory::GetForProfile(profile());
map->ClearSettingsForOneType(
CONTENT_SETTINGS_TYPE_PERMISSION_AUTOBLOCKER_DATA);
}
EXPECT_TRUE(
base::FeatureList::IsEnabled(features::kBlockPromptsIfDismissedOften));
// Sanity check independence per permission type by checking two of them.
DismissMultipleTimesAndExpectBlock(url, CONTENT_SETTINGS_TYPE_GEOLOCATION,
3);
DismissMultipleTimesAndExpectBlock(url, CONTENT_SETTINGS_TYPE_NOTIFICATIONS,
3);
}
void TestVariationBlockOnSeveralDismissals_TestContent() {
GURL url("https://www.google.com");
SetUpUrl(url);
base::HistogramTester histograms;
// Set up the custom parameter and custom value.
base::FieldTrialList field_trials(nullptr);
base::FieldTrial* trial = base::FieldTrialList::CreateFieldTrial(
kPromptTrialName, kPromptGroupName);
std::map<std::string, std::string> params;
params[PermissionDecisionAutoBlocker::kPromptDismissCountKey] = "5";
ASSERT_TRUE(variations::AssociateVariationParams(kPromptTrialName,
kPromptGroupName, params));
std::unique_ptr<base::FeatureList> feature_list =
base::MakeUnique<base::FeatureList>();
if (GetParam() == TestType::PERMISSION_REQUEST_MANAGER) {
feature_list->InitializeFromCommandLine(
features::kUseGroupedPermissionInfobars.name, "");
} else {
feature_list->InitializeFromCommandLine(
"", features::kUseGroupedPermissionInfobars.name);
}
feature_list->RegisterFieldTrialOverride(
features::kBlockPromptsIfDismissedOften.name,
base::FeatureList::OVERRIDE_ENABLE_FEATURE, trial);
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatureList(std::move(feature_list));
EXPECT_EQ(base::FeatureList::GetFieldTrial(
features::kBlockPromptsIfDismissedOften),
trial);
{
std::map<std::string, std::string> actual_params;
EXPECT_TRUE(variations::GetVariationParamsByFeature(
features::kBlockPromptsIfDismissedOften, &actual_params));
EXPECT_EQ(params, actual_params);
}
for (uint32_t i = 0; i < 5; ++i) {
TestPermissionContext permission_context(
profile(), CONTENT_SETTINGS_TYPE_MIDI_SYSEX);
const PermissionRequestID id(
web_contents()->GetRenderProcessHost()->GetID(),
web_contents()->GetMainFrame()->GetRoutingID(), i);
permission_context.SetRespondPermissionCallback(
base::Bind(&PermissionContextBaseTests::RespondToPermission,
base::Unretained(this), &permission_context, id, url,
false, CONTENT_SETTING_ASK));
permission_context.RequestPermission(
web_contents(), id, url, true /* user_gesture */,
base::Bind(&TestPermissionContext::TrackPermissionDecision,
base::Unretained(&permission_context)));
EXPECT_EQ(1u, permission_context.decisions().size());
ASSERT_EQ(CONTENT_SETTING_ASK, permission_context.decisions()[0]);
EXPECT_TRUE(permission_context.tab_context_updated());
PermissionResult result = permission_context.GetPermissionStatus(
nullptr /* render_frame_host */, url, url);
histograms.ExpectTotalCount(
"Permissions.Prompt.Dismissed.PriorDismissCount.MidiSysEx", i + 1);
histograms.ExpectBucketCount(
"Permissions.Prompt.Dismissed.PriorDismissCount.MidiSysEx", i, 1);
histograms.ExpectUniqueSample(
"Permissions.AutoBlocker.EmbargoPromptSuppression",
static_cast<int>(PermissionEmbargoStatus::NOT_EMBARGOED), i + 1);
// On Android, repeatedly requesting and deciding permissions has the side
// effect of overcounting any metrics recorded in the
// PermissionInfoBarDelegate destructor. This is because we directly call
// PermissionQueueController::OnPermissionSet without setting the
// action_taken bit in PermissionInfoBarDelegate. When
// PermissionQueueController is deleted these expectations can be made
// unconditional.
if (GetParam() == TestType::PERMISSION_REQUEST_MANAGER) {
histograms.ExpectTotalCount("Permissions.AutoBlocker.EmbargoStatus",
i + 1);
}
if (i < 4) {
EXPECT_EQ(CONTENT_SETTING_ASK, result.content_setting);
EXPECT_EQ(PermissionStatusSource::UNSPECIFIED, result.source);
if (GetParam() == TestType::PERMISSION_REQUEST_MANAGER) {
histograms.ExpectUniqueSample(
"Permissions.AutoBlocker.EmbargoStatus",
static_cast<int>(PermissionEmbargoStatus::NOT_EMBARGOED), i + 1);
}
} else {
EXPECT_EQ(CONTENT_SETTING_BLOCK, result.content_setting);
EXPECT_EQ(PermissionStatusSource::MULTIPLE_DISMISSALS, result.source);
if (GetParam() == TestType::PERMISSION_REQUEST_MANAGER) {
histograms.ExpectBucketCount(
"Permissions.AutoBlocker.EmbargoStatus",
static_cast<int>(PermissionEmbargoStatus::REPEATED_DISMISSALS),
1);
}
}
}
// Ensure that we finish in the block state.
TestPermissionContext permission_context(profile(),
CONTENT_SETTINGS_TYPE_MIDI_SYSEX);
PermissionResult result = permission_context.GetPermissionStatus(
nullptr /* render_frame_host */, url, url);
EXPECT_EQ(CONTENT_SETTING_BLOCK, result.content_setting);
EXPECT_EQ(PermissionStatusSource::MULTIPLE_DISMISSALS, result.source);
variations::testing::ClearAllVariationParams();
}
void TestRequestPermissionInvalidUrl(
ContentSettingsType content_settings_type) {
base::HistogramTester histograms;
TestPermissionContext permission_context(profile(), content_settings_type);
GURL url;
ASSERT_FALSE(url.is_valid());
controller().LoadURL(url, content::Referrer(), ui::PAGE_TRANSITION_TYPED,
std::string());
const PermissionRequestID id(
web_contents()->GetRenderProcessHost()->GetID(),
web_contents()->GetMainFrame()->GetRoutingID(), -1);
permission_context.RequestPermission(
web_contents(), id, url, true /* user_gesture */,
base::Bind(&TestPermissionContext::TrackPermissionDecision,
base::Unretained(&permission_context)));
ASSERT_EQ(1u, permission_context.decisions().size());
EXPECT_EQ(CONTENT_SETTING_BLOCK, permission_context.decisions()[0]);
EXPECT_TRUE(permission_context.tab_context_updated());
EXPECT_EQ(CONTENT_SETTING_ASK,
permission_context.GetContentSettingFromMap(url, url));
histograms.ExpectTotalCount(
"Permissions.AutoBlocker.EmbargoPromptSuppression", 0);
}
void TestGrantAndRevoke_TestContent(ContentSettingsType content_settings_type,
ContentSetting expected_default) {
TestPermissionContext permission_context(profile(), content_settings_type);
GURL url("https://www.google.com");
SetUpUrl(url);
const PermissionRequestID id(
web_contents()->GetRenderProcessHost()->GetID(),
web_contents()->GetMainFrame()->GetRoutingID(), -1);
permission_context.SetRespondPermissionCallback(
base::Bind(&PermissionContextBaseTests::RespondToPermission,
base::Unretained(this), &permission_context, id, url, true,
CONTENT_SETTING_ALLOW));
permission_context.RequestPermission(
web_contents(), id, url, true /* user_gesture */,
base::Bind(&TestPermissionContext::TrackPermissionDecision,
base::Unretained(&permission_context)));
ASSERT_EQ(1u, permission_context.decisions().size());
EXPECT_EQ(CONTENT_SETTING_ALLOW, permission_context.decisions()[0]);
EXPECT_TRUE(permission_context.tab_context_updated());
EXPECT_EQ(CONTENT_SETTING_ALLOW,
permission_context.GetContentSettingFromMap(url, url));
// Try to reset permission.
permission_context.ResetPermission(url.GetOrigin(), url.GetOrigin());
ContentSetting setting_after_reset =
permission_context.GetContentSettingFromMap(url, url);
ContentSetting default_setting =
HostContentSettingsMapFactory::GetForProfile(profile())
->GetDefaultContentSetting(content_settings_type, nullptr);
EXPECT_EQ(default_setting, setting_after_reset);
}
void TestGlobalPermissionsKillSwitch(
ContentSettingsType content_settings_type) {
TestKillSwitchPermissionContext permission_context(profile(),
content_settings_type);
permission_context.ResetFieldTrialList();
EXPECT_FALSE(permission_context.IsPermissionKillSwitchOn());
std::map<std::string, std::string> params;
params[PermissionUtil::GetPermissionString(content_settings_type)] =
kPermissionsKillSwitchBlockedValue;
variations::AssociateVariationParams(kPermissionsKillSwitchFieldStudy,
kPermissionsKillSwitchTestGroup,
params);
base::FieldTrialList::CreateFieldTrial(kPermissionsKillSwitchFieldStudy,
kPermissionsKillSwitchTestGroup);
EXPECT_TRUE(permission_context.IsPermissionKillSwitchOn());
}
// Don't call this more than once in the same test, as it persists data to
// HostContentSettingsMap.
void TestParallelRequests(ContentSetting response) {
TestPermissionContext permission_context(
profile(), CONTENT_SETTINGS_TYPE_NOTIFICATIONS);
GURL url("http://www.google.com");
SetUpUrl(url);
const PermissionRequestID id0(
web_contents()->GetRenderProcessHost()->GetID(),
web_contents()->GetMainFrame()->GetRoutingID(), 0);
const PermissionRequestID id1(
web_contents()->GetRenderProcessHost()->GetID(),
web_contents()->GetMainFrame()->GetRoutingID(), 1);
bool persist = (response == CONTENT_SETTING_ALLOW ||
response == CONTENT_SETTING_BLOCK);
// Request a permission without setting the callback to DecidePermission.
permission_context.RequestPermission(
web_contents(), id0, url, true /* user_gesture */,
base::Bind(&TestPermissionContext::TrackPermissionDecision,
base::Unretained(&permission_context)));
EXPECT_EQ(0u, permission_context.decisions().size());
// Set the callback, and make a second permission request.
permission_context.SetRespondPermissionCallback(
base::Bind(&PermissionContextBaseTests::RespondToPermission,
base::Unretained(this), &permission_context, id0, url,
persist, response));
permission_context.RequestPermission(
web_contents(), id1, url, true /* user_gesture */,
base::Bind(&TestPermissionContext::TrackPermissionDecision,
base::Unretained(&permission_context)));
ASSERT_EQ(2u, permission_context.decisions().size());
EXPECT_EQ(response, permission_context.decisions()[0]);
EXPECT_EQ(response, permission_context.decisions()[1]);
EXPECT_TRUE(permission_context.tab_context_updated());
EXPECT_EQ(response, permission_context.GetContentSettingFromMap(url, url));
}
void TestPermissionsBlacklisting(
ContentSettingsType content_settings_type,
scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager> db_manager,
const GURL& url,
int timeout,
ContentSetting expected_permission_status,
PermissionEmbargoStatus expected_embargo_reason) {
SetUpUrl(url);
base::HistogramTester histograms;
base::test::ScopedFeatureList scoped_feature_list;
if (GetParam() == TestType::PERMISSION_REQUEST_MANAGER) {
scoped_feature_list.InitWithFeatures(
{features::kUseGroupedPermissionInfobars,
features::kPermissionsBlacklist},
{});
} else {
scoped_feature_list.InitWithFeatures(
{features::kPermissionsBlacklist},
{features::kUseGroupedPermissionInfobars});
}
TestPermissionContext permission_context(profile(), content_settings_type);
PermissionDecisionAutoBlocker::GetForProfile(profile())
->SetSafeBrowsingDatabaseManagerAndTimeoutForTesting(db_manager,
timeout);
const PermissionRequestID id(
web_contents()->GetRenderProcessHost()->GetID(),
web_contents()->GetMainFrame()->GetRoutingID(), -1);
// A response only needs to be made to the permission request if we do not
// expect the permission to be blacklisted.
if (expected_permission_status == CONTENT_SETTING_ALLOW) {
permission_context.SetRespondPermissionCallback(
base::Bind(&PermissionContextBaseTests::RespondToPermission,
base::Unretained(this), &permission_context, id, url,
true /* persist */, expected_permission_status));
}
permission_context.RequestPermission(
web_contents(), id, url, true /* user_gesture */,
base::Bind(&TestPermissionContext::TrackPermissionDecision,
base::Unretained(&permission_context)));
PermissionResult result = permission_context.GetPermissionStatus(
nullptr /* render_frame_host */, url, url);
EXPECT_EQ(expected_permission_status, result.content_setting);
if (expected_permission_status == CONTENT_SETTING_ALLOW) {
ASSERT_EQ(1u, permission_context.decisions().size());
EXPECT_EQ(expected_permission_status, permission_context.decisions()[0]);
EXPECT_EQ(PermissionStatusSource::UNSPECIFIED, result.source);
} else {
EXPECT_EQ(PermissionStatusSource::SAFE_BROWSING_BLACKLIST, result.source);
}
histograms.ExpectUniqueSample(
"Permissions.AutoBlocker.EmbargoPromptSuppression",
static_cast<int>(expected_embargo_reason), 1);
histograms.ExpectUniqueSample("Permissions.AutoBlocker.EmbargoStatus",
static_cast<int>(expected_embargo_reason), 1);
}
void SetUpUrl(const GURL& url) {
NavigateAndCommit(url);
if (GetParam() == TestType::PERMISSION_REQUEST_MANAGER)
prompt_factory_->DocumentOnLoadCompletedInMainFrame();
}
private:
// ChromeRenderViewHostTestHarness:
void SetUp() override {
ChromeRenderViewHostTestHarness::SetUp();
if (GetParam() == TestType::PERMISSION_QUEUE_CONTROLLER) {
scoped_feature_list_.InitAndDisableFeature(
features::kUseGroupedPermissionInfobars);
InfoBarService::CreateForWebContents(web_contents());
} else {
scoped_feature_list_.InitAndEnableFeature(
features::kUseGroupedPermissionInfobars);
PermissionRequestManager::CreateForWebContents(web_contents());
PermissionRequestManager* manager =
PermissionRequestManager::FromWebContents(web_contents());
prompt_factory_.reset(new MockPermissionPromptFactory(manager));
manager->DisplayPendingRequests();
}
}
void TearDown() override {
prompt_factory_.reset();
ChromeRenderViewHostTestHarness::TearDown();
}
std::unique_ptr<MockPermissionPromptFactory> prompt_factory_;
// For testing the PermissionRequestManager on Android
base::test::ScopedFeatureList scoped_feature_list_;
DISALLOW_COPY_AND_ASSIGN(PermissionContextBaseTests);
};
// Simulates clicking Accept. The permission should be granted and
// saved for future use.
TEST_P(PermissionContextBaseTests, TestAskAndGrantPersist) {
TestAskAndDecide_TestContent(CONTENT_SETTINGS_TYPE_NOTIFICATIONS,
CONTENT_SETTING_ALLOW, true);
}
// Simulates clicking Accept. The permission should be granted, but not
// persisted.
TEST_P(PermissionContextBaseTests, TestAskAndGrantNoPersist) {
TestAskAndDecide_TestContent(CONTENT_SETTINGS_TYPE_NOTIFICATIONS,
CONTENT_SETTING_ALLOW, false);
}
// Simulates clicking Block. The permission should be denied and
// saved for future use.
TEST_P(PermissionContextBaseTests, TestAskAndBlockPersist) {
TestAskAndDecide_TestContent(CONTENT_SETTINGS_TYPE_GEOLOCATION,
CONTENT_SETTING_BLOCK, true);
}
// Simulates clicking Block. The permission should be denied, but not persisted.
TEST_P(PermissionContextBaseTests, TestAskAndBlockNoPersist) {
TestAskAndDecide_TestContent(CONTENT_SETTINGS_TYPE_GEOLOCATION,
CONTENT_SETTING_BLOCK, false);
}
// Simulates clicking Dismiss (X) in the infobar/bubble.
// The permission should be denied but not saved for future use.
TEST_P(PermissionContextBaseTests, TestAskAndDismiss) {
TestAskAndDecide_TestContent(CONTENT_SETTINGS_TYPE_MIDI_SYSEX,
CONTENT_SETTING_ASK, false);
}
// Simulates clicking Dismiss (X) in the infobar/bubble with the block on too
// many dismissals feature active. The permission should be blocked after
// several dismissals.
TEST_P(PermissionContextBaseTests, TestDismissUntilBlocked) {
TestBlockOnSeveralDismissals_TestContent();
}
// Test setting a custom number of dismissals before block via variations.
TEST_P(PermissionContextBaseTests, TestDismissVariations) {
TestVariationBlockOnSeveralDismissals_TestContent();
}
// Test that too many dismissals for push messaging will result in notifications
// being embargoed.
TEST_P(PermissionContextBaseTests, PushMessagingEmbargoEmbargoesNotifications) {
GURL url("https://www.google.com");
SetUpUrl(url);
DismissMultipleTimesAndExpectBlock(url, CONTENT_SETTINGS_TYPE_PUSH_MESSAGING,
3);
PermissionManager* permission_manager = PermissionManager::Get(profile());
// Check push messaging is now embargoed.
PermissionResult result = permission_manager->GetPermissionStatus(
CONTENT_SETTINGS_TYPE_PUSH_MESSAGING, url, url);
EXPECT_EQ(CONTENT_SETTING_BLOCK, result.content_setting);
EXPECT_EQ(PermissionStatusSource::MULTIPLE_DISMISSALS, result.source);
// Check notifications yields the same result.
result = permission_manager->GetPermissionStatus(
CONTENT_SETTINGS_TYPE_NOTIFICATIONS, url, url);
EXPECT_EQ(CONTENT_SETTING_BLOCK, result.content_setting);
EXPECT_EQ(PermissionStatusSource::MULTIPLE_DISMISSALS, result.source);
}
// Simulates non-valid requesting URL.
// The permission should be denied but not saved for future use.
TEST_P(PermissionContextBaseTests, TestNonValidRequestingUrl) {
TestRequestPermissionInvalidUrl(CONTENT_SETTINGS_TYPE_GEOLOCATION);
TestRequestPermissionInvalidUrl(CONTENT_SETTINGS_TYPE_NOTIFICATIONS);
TestRequestPermissionInvalidUrl(CONTENT_SETTINGS_TYPE_MIDI_SYSEX);
TestRequestPermissionInvalidUrl(CONTENT_SETTINGS_TYPE_PUSH_MESSAGING);
#if defined(OS_ANDROID) || defined(OS_CHROMEOS)
TestRequestPermissionInvalidUrl(
CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER);
#endif
}
// Simulates granting and revoking of permissions.
TEST_P(PermissionContextBaseTests, TestGrantAndRevoke) {
TestGrantAndRevoke_TestContent(CONTENT_SETTINGS_TYPE_GEOLOCATION,
CONTENT_SETTING_ASK);
TestGrantAndRevoke_TestContent(CONTENT_SETTINGS_TYPE_MIDI_SYSEX,
CONTENT_SETTING_ASK);
#if defined(OS_ANDROID)
TestGrantAndRevoke_TestContent(
CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER, CONTENT_SETTING_ASK);
// TODO(timvolodine): currently no test for
// CONTENT_SETTINGS_TYPE_NOTIFICATIONS because notification permissions work
// differently with infobars as compared to bubbles (crbug.com/453784).
#else
TestGrantAndRevoke_TestContent(CONTENT_SETTINGS_TYPE_NOTIFICATIONS,
CONTENT_SETTING_ASK);
#endif
}
// Tests the global kill switch by enabling/disabling the Field Trials.
TEST_P(PermissionContextBaseTests, TestGlobalKillSwitch) {
TestGlobalPermissionsKillSwitch(CONTENT_SETTINGS_TYPE_GEOLOCATION);
TestGlobalPermissionsKillSwitch(CONTENT_SETTINGS_TYPE_NOTIFICATIONS);
TestGlobalPermissionsKillSwitch(CONTENT_SETTINGS_TYPE_MIDI_SYSEX);
TestGlobalPermissionsKillSwitch(CONTENT_SETTINGS_TYPE_PUSH_MESSAGING);
TestGlobalPermissionsKillSwitch(CONTENT_SETTINGS_TYPE_DURABLE_STORAGE);
#if defined(OS_ANDROID) || defined(OS_CHROMEOS)
TestGlobalPermissionsKillSwitch(
CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER);
#endif
TestGlobalPermissionsKillSwitch(CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC);
TestGlobalPermissionsKillSwitch(CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA);
}
TEST_P(PermissionContextBaseTests, TestParallelRequestsAllowed) {
TestParallelRequests(CONTENT_SETTING_ALLOW);
}
TEST_P(PermissionContextBaseTests, TestParallelRequestsBlocked) {
TestParallelRequests(CONTENT_SETTING_BLOCK);
}
TEST_P(PermissionContextBaseTests, TestParallelRequestsDismissed) {
TestParallelRequests(CONTENT_SETTING_ASK);
}
// Tests a blacklisted (URL, permission) pair has had its permission request
// blocked.
TEST_P(PermissionContextBaseTests, TestPermissionsBlacklistingBlocked) {
scoped_refptr<MockSafeBrowsingDatabaseManager> db_manager =
new MockSafeBrowsingDatabaseManager(true /* perform_callback */);
const GURL url("https://www.example.com");
std::set<std::string> blacklisted_permissions{"GEOLOCATION"};
db_manager->BlacklistUrlPermissions(url, blacklisted_permissions);
TestPermissionsBlacklisting(
CONTENT_SETTINGS_TYPE_GEOLOCATION, db_manager, url, 2000 /* timeout */,
CONTENT_SETTING_BLOCK, PermissionEmbargoStatus::PERMISSIONS_BLACKLISTING);
}
// Tests that a URL that is blacklisted for one permission can still request
// another and grant another.
TEST_P(PermissionContextBaseTests, TestPermissionsBlacklistingAllowed) {
scoped_refptr<MockSafeBrowsingDatabaseManager> db_manager =
new MockSafeBrowsingDatabaseManager(true /* perform_callback */);
const GURL url("https://www.example.com");
std::set<std::string> blacklisted_permissions{"GEOLOCATION"};
db_manager->BlacklistUrlPermissions(url, blacklisted_permissions);
TestPermissionsBlacklisting(CONTENT_SETTINGS_TYPE_NOTIFICATIONS, db_manager,
url, 2000 /* timeout */, CONTENT_SETTING_ALLOW,
PermissionEmbargoStatus::NOT_EMBARGOED);
}
#if defined(OS_ANDROID)
INSTANTIATE_TEST_CASE_P(
PermissionContextBaseTestsInstance,
PermissionContextBaseTests,
::testing::Values(TestType::PERMISSION_REQUEST_MANAGER,
TestType::PERMISSION_QUEUE_CONTROLLER));
#else
INSTANTIATE_TEST_CASE_P(
PermissionContextBaseTestsInstance,
PermissionContextBaseTests,
::testing::Values(TestType::PERMISSION_REQUEST_MANAGER));
#endif