blob: 6e7dd6c84b67d405646ddd5795f0c751a94584a3 [file] [log] [blame]
// Copyright 2015 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 "components/permissions/permission_manager.h"
#include <memory>
#include "base/bind.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/field_trial_params.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/permissions/features.h"
#include "components/permissions/permission_context_base.h"
#include "components/permissions/permission_request_manager.h"
#include "components/permissions/permission_result.h"
#include "components/permissions/test/mock_permission_prompt_factory.h"
#include "components/permissions/test/permission_test_util.h"
#include "components/permissions/test/test_permissions_client.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/common/content_client.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_renderer_host.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"
#if BUILDFLAG(IS_ANDROID)
#include "base/android/build_info.h"
#endif // BUILDFLAG(IS_ANDROID)
using blink::PermissionType;
using blink::mojom::PermissionsPolicyFeature;
using blink::mojom::PermissionStatus;
namespace permissions {
namespace {
class ScopedPartitionedOriginBrowserClient
: public content::ContentBrowserClient {
public:
explicit ScopedPartitionedOriginBrowserClient(const GURL& app_origin)
: app_origin_(url::Origin::Create(app_origin)) {
old_client_ = content::SetBrowserClientForTesting(this);
}
~ScopedPartitionedOriginBrowserClient() override {
content::SetBrowserClientForTesting(old_client_);
}
content::StoragePartitionConfig GetStoragePartitionConfigForSite(
content::BrowserContext* browser_context,
const GURL& site) override {
if (url::Origin::Create(site) == app_origin_) {
return content::StoragePartitionConfig::Create(
browser_context, "test_partition", /*partition_name=*/std::string(),
/*in_memory=*/false);
}
return content::StoragePartitionConfig::CreateDefault(browser_context);
}
private:
url::Origin app_origin_;
content::ContentBrowserClient* old_client_;
};
#if BUILDFLAG(IS_ANDROID)
// See https://crbug.com/904883.
auto GetDefaultProtectedMediaIdentifierPermissionStatus() {
return base::android::BuildInfo::GetInstance()->sdk_int() >=
base::android::SDK_VERSION_MARSHMALLOW
? PermissionStatus::GRANTED
: PermissionStatus::ASK;
}
auto GetDefaultProtectedMediaIdentifierContentSetting() {
return base::android::BuildInfo::GetInstance()->sdk_int() >=
base::android::SDK_VERSION_MARSHMALLOW
? CONTENT_SETTING_ALLOW
: CONTENT_SETTING_ASK;
}
#endif // BUILDFLAG(IS_ANDROID)
} // namespace
class PermissionManagerTest : public content::RenderViewHostTestHarness {
public:
void OnPermissionChange(PermissionStatus permission) {
if (!quit_closure_.is_null())
std::move(quit_closure_).Run();
callback_called_ = true;
callback_count_++;
callback_result_ = permission;
}
protected:
PermissionManagerTest()
: url_("https://example.com"), other_url_("https://foo.com") {}
PermissionManager* GetPermissionManager() {
return static_cast<PermissionManager*>(
browser_context_->GetPermissionControllerDelegate());
}
HostContentSettingsMap* GetHostContentSettingsMap() {
return PermissionsClient::Get()->GetSettingsMap(browser_context_.get());
}
void CheckPermissionStatus(PermissionType type, PermissionStatus expected) {
EXPECT_EQ(expected, GetPermissionManager()->GetPermissionStatus(
type, url_.DeprecatedGetOriginAsURL(),
url_.DeprecatedGetOriginAsURL()));
}
void CheckPermissionResult(ContentSettingsType type,
ContentSetting expected_status,
PermissionStatusSource expected_status_source) {
PermissionResult result =
GetPermissionManager()->GetPermissionStatusDeprecated(
type, url_.DeprecatedGetOriginAsURL(),
url_.DeprecatedGetOriginAsURL());
EXPECT_EQ(expected_status, result.content_setting);
EXPECT_EQ(expected_status_source, result.source);
}
void SetPermission(ContentSettingsType type, ContentSetting value) {
SetPermission(url_, type, value);
}
void SetPermission(const GURL& origin,
ContentSettingsType type,
ContentSetting value) {
GetHostContentSettingsMap()->SetContentSettingDefaultScope(origin, origin,
type, value);
}
void RequestPermissionFromCurrentDocument(PermissionType type,
content::RenderFrameHost* rfh) {
base::RunLoop loop;
quit_closure_ = loop.QuitClosure();
GetPermissionManager()->RequestPermissionsFromCurrentDocument(
std::vector(1, type), rfh, true,
base::BindOnce(
[](base::OnceCallback<void(blink::mojom::PermissionStatus)>
callback,
const std::vector<blink::mojom::PermissionStatus>& state) {
DCHECK_EQ(state.size(), 1U);
std::move(callback).Run(state[0]);
},
base::BindOnce(&PermissionManagerTest::OnPermissionChange,
base::Unretained(this))));
loop.Run();
}
void RequestPermissionFromCurrentDocumentNonBlocking(
PermissionType type,
content::RenderFrameHost* rfh) {
GetPermissionManager()->RequestPermissionsFromCurrentDocument(
std::vector(1, type), rfh, true,
base::BindOnce(
[](base::OnceCallback<void(blink::mojom::PermissionStatus)>
callback,
const std::vector<blink::mojom::PermissionStatus>& state) {
DCHECK_EQ(state.size(), 1U);
std::move(callback).Run(state[0]);
},
base::BindOnce(&PermissionManagerTest::OnPermissionChange,
base::Unretained(this))));
}
PermissionStatus GetPermissionStatusForCurrentDocument(
PermissionType permission,
content::RenderFrameHost* render_frame_host) {
return GetPermissionManager()->GetPermissionStatusForCurrentDocument(
permission, render_frame_host);
}
PermissionStatus GetPermissionStatusForWorker(
PermissionType permission,
content::RenderProcessHost* render_process_host,
const GURL& worker_origin) {
return GetPermissionManager()->GetPermissionStatusForWorker(
permission, render_process_host, worker_origin);
}
content::PermissionControllerDelegate::SubscriptionId
SubscribePermissionStatusChange(
PermissionType permission,
content::RenderProcessHost* render_process_host,
content::RenderFrameHost* render_frame_host,
const GURL& requesting_origin,
base::RepeatingCallback<void(PermissionStatus)> callback) {
return GetPermissionManager()->SubscribePermissionStatusChange(
permission, render_process_host, render_frame_host, requesting_origin,
std::move(callback));
}
void UnsubscribePermissionStatusChange(
content::PermissionControllerDelegate::SubscriptionId subscription_id) {
GetPermissionManager()->UnsubscribePermissionStatusChange(subscription_id);
}
bool IsPermissionOverridableByDevTools(
PermissionType permission,
const absl::optional<url::Origin>& origin) {
return GetPermissionManager()->IsPermissionOverridableByDevTools(permission,
origin);
}
void ResetPermission(PermissionType permission,
const GURL& requesting_origin,
const GURL& embedding_origin) {
GetPermissionManager()->ResetPermission(permission, requesting_origin,
embedding_origin);
}
const GURL& url() const { return url_; }
const GURL& other_url() const { return other_url_; }
bool callback_called() const { return callback_called_; }
int callback_count() const { return callback_count_; }
PermissionStatus callback_result() const { return callback_result_; }
void Reset() {
callback_called_ = false;
callback_count_ = 0;
callback_result_ = PermissionStatus::ASK;
}
bool PendingRequestsEmpty() {
return GetPermissionManager()->pending_requests_.IsEmpty();
}
// The header policy should only be set once on page load, so we refresh the
// page to simulate that.
void RefreshPageAndSetHeaderPolicy(content::RenderFrameHost** rfh,
PermissionsPolicyFeature feature,
const std::vector<std::string>& origins) {
content::RenderFrameHost* current = *rfh;
auto navigation = content::NavigationSimulator::CreateRendererInitiated(
current->GetLastCommittedURL(), current);
std::vector<url::Origin> parsed_origins;
for (const std::string& origin : origins)
parsed_origins.push_back(url::Origin::Create(GURL(origin)));
navigation->SetPermissionsPolicyHeader(
{{feature, parsed_origins, false, false}});
navigation->Commit();
*rfh = navigation->GetFinalRenderFrameHost();
}
content::RenderFrameHost* AddChildRFH(
content::RenderFrameHost* parent,
const GURL& origin,
PermissionsPolicyFeature feature = PermissionsPolicyFeature::kNotFound) {
blink::ParsedPermissionsPolicy frame_policy = {};
if (feature != PermissionsPolicyFeature::kNotFound) {
frame_policy.push_back(
{feature, std::vector<url::Origin>{url::Origin::Create(origin)},
false, false});
}
content::RenderFrameHost* result =
content::RenderFrameHostTester::For(parent)->AppendChildWithPolicy(
"", frame_policy);
content::RenderFrameHostTester::For(result)
->InitializeRenderFrameIfNeeded();
SimulateNavigation(&result, origin);
return result;
}
private:
void SetUp() override {
RenderViewHostTestHarness::SetUp();
browser_context_ = std::make_unique<content::TestBrowserContext>();
browser_context_->SetPermissionControllerDelegate(
permissions::GetPermissionControllerDelegate(browser_context_.get()));
NavigateAndCommit(url());
}
void TearDown() override {
GetPermissionManager()->Shutdown();
browser_context_ = nullptr;
RenderViewHostTestHarness::TearDown();
}
void SimulateNavigation(content::RenderFrameHost** rfh, const GURL& url) {
auto navigation_simulator =
content::NavigationSimulator::CreateRendererInitiated(url, *rfh);
navigation_simulator->Commit();
*rfh = navigation_simulator->GetFinalRenderFrameHost();
}
const GURL url_;
const GURL other_url_;
bool callback_called_ = false;
int callback_count_ = 0;
PermissionStatus callback_result_ = PermissionStatus::ASK;
base::OnceClosure quit_closure_;
std::unique_ptr<content::TestBrowserContext> browser_context_;
TestPermissionsClient client_;
};
TEST_F(PermissionManagerTest, GetPermissionStatusDefault) {
CheckPermissionStatus(PermissionType::MIDI_SYSEX, PermissionStatus::ASK);
CheckPermissionStatus(PermissionType::NOTIFICATIONS, PermissionStatus::ASK);
CheckPermissionStatus(PermissionType::GEOLOCATION, PermissionStatus::ASK);
#if BUILDFLAG(IS_ANDROID)
CheckPermissionStatus(PermissionType::PROTECTED_MEDIA_IDENTIFIER,
GetDefaultProtectedMediaIdentifierPermissionStatus());
#endif
}
TEST_F(PermissionManagerTest, GetPermissionStatusAfterSet) {
SetPermission(ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
CheckPermissionStatus(PermissionType::GEOLOCATION, PermissionStatus::GRANTED);
SetPermission(ContentSettingsType::NOTIFICATIONS, CONTENT_SETTING_ALLOW);
CheckPermissionStatus(PermissionType::NOTIFICATIONS,
PermissionStatus::GRANTED);
SetPermission(ContentSettingsType::MIDI_SYSEX, CONTENT_SETTING_ALLOW);
CheckPermissionStatus(PermissionType::MIDI_SYSEX, PermissionStatus::GRANTED);
#if BUILDFLAG(IS_ANDROID)
SetPermission(ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER,
CONTENT_SETTING_ALLOW);
CheckPermissionStatus(PermissionType::PROTECTED_MEDIA_IDENTIFIER,
PermissionStatus::GRANTED);
#endif
}
TEST_F(PermissionManagerTest, CheckPermissionResultDefault) {
CheckPermissionResult(ContentSettingsType::MIDI_SYSEX, CONTENT_SETTING_ASK,
PermissionStatusSource::UNSPECIFIED);
CheckPermissionResult(ContentSettingsType::NOTIFICATIONS, CONTENT_SETTING_ASK,
PermissionStatusSource::UNSPECIFIED);
CheckPermissionResult(ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ASK,
PermissionStatusSource::UNSPECIFIED);
#if BUILDFLAG(IS_ANDROID)
CheckPermissionResult(ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER,
GetDefaultProtectedMediaIdentifierContentSetting(),
PermissionStatusSource::UNSPECIFIED);
#endif
}
TEST_F(PermissionManagerTest, CheckPermissionResultAfterSet) {
SetPermission(ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
CheckPermissionResult(ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW,
PermissionStatusSource::UNSPECIFIED);
SetPermission(ContentSettingsType::NOTIFICATIONS, CONTENT_SETTING_ALLOW);
CheckPermissionResult(ContentSettingsType::NOTIFICATIONS,
CONTENT_SETTING_ALLOW,
PermissionStatusSource::UNSPECIFIED);
SetPermission(ContentSettingsType::MIDI_SYSEX, CONTENT_SETTING_ALLOW);
CheckPermissionResult(ContentSettingsType::MIDI_SYSEX, CONTENT_SETTING_ALLOW,
PermissionStatusSource::UNSPECIFIED);
#if BUILDFLAG(IS_ANDROID)
SetPermission(ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER,
CONTENT_SETTING_ALLOW);
CheckPermissionResult(ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER,
CONTENT_SETTING_ALLOW,
PermissionStatusSource::UNSPECIFIED);
#endif
}
TEST_F(PermissionManagerTest, SubscriptionDestroyedCleanlyWithoutUnsubscribe) {
// Test that the PermissionManager shuts down cleanly with subscriptions that
// haven't been removed, crbug.com/720071.
SubscribePermissionStatusChange(
PermissionType::GEOLOCATION, /*render_process_host=*/nullptr, main_rfh(),
url(),
base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
base::Unretained(this)));
}
TEST_F(PermissionManagerTest, SubscribeUnsubscribeAfterShutdown) {
content::PermissionControllerDelegate::SubscriptionId subscription_id =
SubscribePermissionStatusChange(
PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
main_rfh(), url(),
base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
base::Unretained(this)));
// Simulate Keyed Services shutdown pass. Note: Shutdown will be called second
// time during browser_context destruction. This is ok for now: Shutdown is
// reenterant.
GetPermissionManager()->Shutdown();
UnsubscribePermissionStatusChange(subscription_id);
// Check that subscribe/unsubscribe after shutdown don't crash.
content::PermissionControllerDelegate::SubscriptionId subscription2_id =
SubscribePermissionStatusChange(
PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
main_rfh(), url(),
base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
base::Unretained(this)));
UnsubscribePermissionStatusChange(subscription2_id);
}
TEST_F(PermissionManagerTest, SameTypeChangeNotifies) {
content::PermissionControllerDelegate::SubscriptionId subscription_id =
SubscribePermissionStatusChange(
PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
main_rfh(), url(),
base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
base::Unretained(this)));
GetHostContentSettingsMap()->SetContentSettingDefaultScope(
url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
EXPECT_TRUE(callback_called());
EXPECT_EQ(PermissionStatus::GRANTED, callback_result());
UnsubscribePermissionStatusChange(subscription_id);
}
TEST_F(PermissionManagerTest, DifferentTypeChangeDoesNotNotify) {
content::PermissionControllerDelegate::SubscriptionId subscription_id =
SubscribePermissionStatusChange(
PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
main_rfh(), url(),
base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
base::Unretained(this)));
GetHostContentSettingsMap()->SetContentSettingDefaultScope(
url(), GURL(), ContentSettingsType::NOTIFICATIONS, CONTENT_SETTING_ALLOW);
EXPECT_FALSE(callback_called());
UnsubscribePermissionStatusChange(subscription_id);
}
TEST_F(PermissionManagerTest, ChangeAfterUnsubscribeDoesNotNotify) {
content::PermissionControllerDelegate::SubscriptionId subscription_id =
SubscribePermissionStatusChange(
PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
main_rfh(), url(),
base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
base::Unretained(this)));
UnsubscribePermissionStatusChange(subscription_id);
GetHostContentSettingsMap()->SetContentSettingDefaultScope(
url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
EXPECT_FALSE(callback_called());
}
TEST_F(PermissionManagerTest,
ChangeAfterUnsubscribeOnlyNotifiesActiveSubscribers) {
content::PermissionControllerDelegate::SubscriptionId subscription_id =
SubscribePermissionStatusChange(
PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
main_rfh(), url(),
base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
base::Unretained(this)));
SubscribePermissionStatusChange(
PermissionType::GEOLOCATION, /*render_process_host=*/nullptr, main_rfh(),
url(),
base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
base::Unretained(this)));
UnsubscribePermissionStatusChange(subscription_id);
GetHostContentSettingsMap()->SetContentSettingDefaultScope(
url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
EXPECT_EQ(callback_count(), 1);
}
TEST_F(PermissionManagerTest, DifferentPrimaryUrlDoesNotNotify) {
content::PermissionControllerDelegate::SubscriptionId subscription_id =
SubscribePermissionStatusChange(
PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
main_rfh(), url(),
base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
base::Unretained(this)));
GetHostContentSettingsMap()->SetContentSettingDefaultScope(
other_url(), url(), ContentSettingsType::GEOLOCATION,
CONTENT_SETTING_ALLOW);
EXPECT_FALSE(callback_called());
UnsubscribePermissionStatusChange(subscription_id);
}
TEST_F(PermissionManagerTest, DifferentSecondaryUrlDoesNotNotify) {
content::PermissionControllerDelegate::SubscriptionId subscription_id =
SubscribePermissionStatusChange(
PermissionType::STORAGE_ACCESS_GRANT, /*render_process_host=*/nullptr,
main_rfh(), url(),
base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
base::Unretained(this)));
GetHostContentSettingsMap()->SetContentSettingDefaultScope(
url(), other_url(), ContentSettingsType::STORAGE_ACCESS,
CONTENT_SETTING_ALLOW);
EXPECT_FALSE(callback_called());
UnsubscribePermissionStatusChange(subscription_id);
}
TEST_F(PermissionManagerTest, WildCardPatternNotifies) {
content::PermissionControllerDelegate::SubscriptionId subscription_id =
SubscribePermissionStatusChange(
PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
main_rfh(), url(),
base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
base::Unretained(this)));
GetHostContentSettingsMap()->SetDefaultContentSetting(
ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
EXPECT_TRUE(callback_called());
EXPECT_EQ(PermissionStatus::GRANTED, callback_result());
UnsubscribePermissionStatusChange(subscription_id);
}
TEST_F(PermissionManagerTest, ClearSettingsNotifies) {
GetHostContentSettingsMap()->SetContentSettingDefaultScope(
url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
content::PermissionControllerDelegate::SubscriptionId subscription_id =
SubscribePermissionStatusChange(
PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
main_rfh(), url(),
base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
base::Unretained(this)));
GetHostContentSettingsMap()->ClearSettingsForOneType(
ContentSettingsType::GEOLOCATION);
EXPECT_TRUE(callback_called());
EXPECT_EQ(PermissionStatus::ASK, callback_result());
UnsubscribePermissionStatusChange(subscription_id);
}
TEST_F(PermissionManagerTest, NewValueCorrectlyPassed) {
content::PermissionControllerDelegate::SubscriptionId subscription_id =
SubscribePermissionStatusChange(
PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
main_rfh(), url(),
base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
base::Unretained(this)));
GetHostContentSettingsMap()->SetContentSettingDefaultScope(
url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_BLOCK);
EXPECT_TRUE(callback_called());
EXPECT_EQ(PermissionStatus::DENIED, callback_result());
UnsubscribePermissionStatusChange(subscription_id);
}
TEST_F(PermissionManagerTest, ChangeWithoutPermissionChangeDoesNotNotify) {
GetHostContentSettingsMap()->SetContentSettingDefaultScope(
url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
content::PermissionControllerDelegate::SubscriptionId subscription_id =
SubscribePermissionStatusChange(
PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
main_rfh(), url(),
base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
base::Unretained(this)));
GetHostContentSettingsMap()->SetContentSettingDefaultScope(
url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
EXPECT_FALSE(callback_called());
UnsubscribePermissionStatusChange(subscription_id);
}
TEST_F(PermissionManagerTest, ChangesBackAndForth) {
GetHostContentSettingsMap()->SetContentSettingDefaultScope(
url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ASK);
content::PermissionControllerDelegate::SubscriptionId subscription_id =
SubscribePermissionStatusChange(
PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
main_rfh(), url(),
base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
base::Unretained(this)));
GetHostContentSettingsMap()->SetContentSettingDefaultScope(
url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
EXPECT_TRUE(callback_called());
EXPECT_EQ(PermissionStatus::GRANTED, callback_result());
Reset();
GetHostContentSettingsMap()->SetContentSettingDefaultScope(
url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ASK);
EXPECT_TRUE(callback_called());
EXPECT_EQ(PermissionStatus::ASK, callback_result());
UnsubscribePermissionStatusChange(subscription_id);
}
TEST_F(PermissionManagerTest, ChangesBackAndForthWorker) {
GetHostContentSettingsMap()->SetContentSettingDefaultScope(
url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ASK);
content::PermissionControllerDelegate::SubscriptionId subscription_id =
SubscribePermissionStatusChange(
PermissionType::GEOLOCATION, process(), /*render_frame_host=*/nullptr,
url(),
base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
base::Unretained(this)));
GetHostContentSettingsMap()->SetContentSettingDefaultScope(
url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
EXPECT_TRUE(callback_called());
EXPECT_EQ(PermissionStatus::GRANTED, callback_result());
Reset();
GetHostContentSettingsMap()->SetContentSettingDefaultScope(
url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ASK);
EXPECT_TRUE(callback_called());
EXPECT_EQ(PermissionStatus::ASK, callback_result());
UnsubscribePermissionStatusChange(subscription_id);
}
TEST_F(PermissionManagerTest, SubscribeMIDIPermission) {
content::PermissionControllerDelegate::SubscriptionId subscription_id =
SubscribePermissionStatusChange(
PermissionType::MIDI, /*render_process_host=*/nullptr, main_rfh(),
url(),
base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
base::Unretained(this)));
CheckPermissionStatus(PermissionType::GEOLOCATION, PermissionStatus::ASK);
GetHostContentSettingsMap()->SetContentSettingDefaultScope(
url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
CheckPermissionStatus(PermissionType::GEOLOCATION, PermissionStatus::GRANTED);
EXPECT_FALSE(callback_called());
UnsubscribePermissionStatusChange(subscription_id);
}
TEST_F(PermissionManagerTest, PermissionIgnoredCleanup) {
content::WebContents* contents = web_contents();
PermissionRequestManager::CreateForWebContents(contents);
PermissionRequestManager* manager =
PermissionRequestManager::FromWebContents(contents);
auto prompt_factory = std::make_unique<MockPermissionPromptFactory>(manager);
NavigateAndCommit(url());
RequestPermissionFromCurrentDocumentNonBlocking(PermissionType::GEOLOCATION,
main_rfh());
EXPECT_FALSE(PendingRequestsEmpty());
NavigateAndCommit(GURL("https://foobar.com"));
EXPECT_TRUE(callback_called());
EXPECT_TRUE(PendingRequestsEmpty());
}
// Check PermissionResult shows requests denied due to insecure
// origins.
TEST_F(PermissionManagerTest, InsecureOrigin) {
GURL insecure_frame("http://www.example.com/geolocation");
NavigateAndCommit(insecure_frame);
PermissionResult result =
GetPermissionManager()->GetPermissionStatusForCurrentDocument(
ContentSettingsType::GEOLOCATION, web_contents()->GetMainFrame());
EXPECT_EQ(CONTENT_SETTING_BLOCK, result.content_setting);
EXPECT_EQ(PermissionStatusSource::INSECURE_ORIGIN, result.source);
GURL secure_frame("https://www.example.com/geolocation");
NavigateAndCommit(secure_frame);
result = GetPermissionManager()->GetPermissionStatusForCurrentDocument(
ContentSettingsType::GEOLOCATION, web_contents()->GetMainFrame());
EXPECT_EQ(CONTENT_SETTING_ASK, result.content_setting);
EXPECT_EQ(PermissionStatusSource::UNSPECIFIED, result.source);
}
TEST_F(PermissionManagerTest, InsecureOriginIsNotOverridable) {
const url::Origin kInsecureOrigin =
url::Origin::Create(GURL("http://example.com/geolocation"));
const url::Origin kSecureOrigin =
url::Origin::Create(GURL("https://example.com/geolocation"));
EXPECT_FALSE(IsPermissionOverridableByDevTools(PermissionType::GEOLOCATION,
kInsecureOrigin));
EXPECT_TRUE(IsPermissionOverridableByDevTools(PermissionType::GEOLOCATION,
kSecureOrigin));
}
TEST_F(PermissionManagerTest, MissingContextIsNotOverridable) {
// Permissions that are not implemented should be denied overridability.
#if !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_ANDROID)
EXPECT_FALSE(IsPermissionOverridableByDevTools(
PermissionType::PROTECTED_MEDIA_IDENTIFIER,
url::Origin::Create(GURL("http://localhost"))));
#endif
EXPECT_TRUE(IsPermissionOverridableByDevTools(
PermissionType::MIDI_SYSEX,
url::Origin::Create(GURL("http://localhost"))));
}
TEST_F(PermissionManagerTest, KillSwitchOnIsNotOverridable) {
const url::Origin kLocalHost = url::Origin::Create(GURL("http://localhost"));
EXPECT_TRUE(IsPermissionOverridableByDevTools(PermissionType::GEOLOCATION,
kLocalHost));
// Turn on kill switch for GEOLOCATION.
std::map<std::string, std::string> params;
params[PermissionUtil::GetPermissionString(
ContentSettingsType::GEOLOCATION)] =
PermissionContextBase::kPermissionsKillSwitchBlockedValue;
base::AssociateFieldTrialParams(
PermissionContextBase::kPermissionsKillSwitchFieldStudy, "TestGroup",
params);
base::FieldTrialList::CreateFieldTrial(
PermissionContextBase::kPermissionsKillSwitchFieldStudy, "TestGroup");
EXPECT_FALSE(IsPermissionOverridableByDevTools(PermissionType::GEOLOCATION,
kLocalHost));
}
TEST_F(PermissionManagerTest, GetPermissionStatusDelegation) {
const char* kOrigin1 = "https://example.com";
const char* kOrigin2 = "https://google.com";
NavigateAndCommit(GURL(kOrigin1));
content::RenderFrameHost* parent = main_rfh();
content::RenderFrameHost* child = AddChildRFH(parent, GURL(kOrigin2));
// By default the parent should be able to request access, but not the child.
EXPECT_EQ(PermissionStatus::ASK, GetPermissionStatusForCurrentDocument(
PermissionType::GEOLOCATION, parent));
EXPECT_EQ(PermissionStatus::DENIED, GetPermissionStatusForCurrentDocument(
PermissionType::GEOLOCATION, child));
// Enabling geolocation by FP should allow the child to request access also.
child = AddChildRFH(parent, GURL(kOrigin2),
PermissionsPolicyFeature::kGeolocation);
EXPECT_EQ(PermissionStatus::ASK, GetPermissionStatusForCurrentDocument(
PermissionType::GEOLOCATION, child));
// When the child requests location a prompt should be displayed for the
// parent.
PermissionRequestManager::CreateForWebContents(web_contents());
PermissionRequestManager* manager =
PermissionRequestManager::FromWebContents(web_contents());
auto prompt_factory = std::make_unique<MockPermissionPromptFactory>(manager);
prompt_factory->set_response_type(PermissionRequestManager::ACCEPT_ALL);
prompt_factory->DocumentOnLoadCompletedInPrimaryMainFrame();
RequestPermissionFromCurrentDocument(PermissionType::GEOLOCATION, child);
EXPECT_TRUE(prompt_factory->RequestOriginSeen(GURL(kOrigin1)));
// Now the child frame should have location, as well as the parent frame.
EXPECT_EQ(PermissionStatus::GRANTED,
GetPermissionStatusForCurrentDocument(PermissionType::GEOLOCATION,
parent));
EXPECT_EQ(PermissionStatus::GRANTED, GetPermissionStatusForCurrentDocument(
PermissionType::GEOLOCATION, child));
// Revoking access from the parent should cause the child not to have access
// either.
ResetPermission(PermissionType::GEOLOCATION, GURL(kOrigin1), GURL(kOrigin1));
EXPECT_EQ(PermissionStatus::ASK, GetPermissionStatusForCurrentDocument(
PermissionType::GEOLOCATION, parent));
EXPECT_EQ(PermissionStatus::ASK, GetPermissionStatusForCurrentDocument(
PermissionType::GEOLOCATION, child));
// If the parent changes its policy, the child should be blocked.
RefreshPageAndSetHeaderPolicy(&parent, PermissionsPolicyFeature::kGeolocation,
{kOrigin1});
child = AddChildRFH(parent, GURL(kOrigin2));
EXPECT_EQ(PermissionStatus::ASK, GetPermissionStatusForCurrentDocument(
PermissionType::GEOLOCATION, parent));
EXPECT_EQ(PermissionStatus::DENIED, GetPermissionStatusForCurrentDocument(
PermissionType::GEOLOCATION, child));
prompt_factory.reset();
}
TEST_F(PermissionManagerTest, SubscribeWithPermissionDelegation) {
const char* kOrigin1 = "https://example.com";
const char* kOrigin2 = "https://google.com";
NavigateAndCommit(GURL(kOrigin1));
content::RenderFrameHost* parent = main_rfh();
content::RenderFrameHost* child = AddChildRFH(parent, GURL(kOrigin2));
content::PermissionControllerDelegate::SubscriptionId subscription_id =
SubscribePermissionStatusChange(
PermissionType::GEOLOCATION, /*render_process_host=*/nullptr, child,
GURL(kOrigin2),
base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
base::Unretained(this)));
EXPECT_FALSE(callback_called());
// Location should be blocked for the child because it's not delegated.
EXPECT_EQ(PermissionStatus::DENIED, GetPermissionStatusForCurrentDocument(
PermissionType::GEOLOCATION, child));
// Allow access for the top level origin.
GetHostContentSettingsMap()->SetContentSettingDefaultScope(
url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
// The child's permission should still be block and no callback should be run.
EXPECT_EQ(PermissionStatus::DENIED, GetPermissionStatusForCurrentDocument(
PermissionType::GEOLOCATION, child));
EXPECT_FALSE(callback_called());
// Enabling geolocation by FP should allow the child to request access also.
child = AddChildRFH(parent, GURL(kOrigin2),
PermissionsPolicyFeature::kGeolocation);
EXPECT_EQ(PermissionStatus::GRANTED, GetPermissionStatusForCurrentDocument(
PermissionType::GEOLOCATION, child));
subscription_id = SubscribePermissionStatusChange(
PermissionType::GEOLOCATION, /*render_process_host=*/nullptr, child,
GURL(kOrigin2),
base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
base::Unretained(this)));
EXPECT_FALSE(callback_called());
// Blocking access to the parent should trigger the callback to be run for the
// child also.
GetHostContentSettingsMap()->SetContentSettingDefaultScope(
url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_BLOCK);
EXPECT_TRUE(callback_called());
EXPECT_EQ(PermissionStatus::DENIED, callback_result());
EXPECT_EQ(PermissionStatus::DENIED, GetPermissionStatusForCurrentDocument(
PermissionType::GEOLOCATION, child));
UnsubscribePermissionStatusChange(subscription_id);
}
TEST_F(PermissionManagerTest, SubscribeUnsubscribeAndResubscribe) {
const char* kOrigin1 = "https://example.com";
NavigateAndCommit(GURL(kOrigin1));
content::PermissionControllerDelegate::SubscriptionId subscription_id =
SubscribePermissionStatusChange(
PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
main_rfh(), GURL(kOrigin1),
base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
base::Unretained(this)));
EXPECT_EQ(callback_count(), 0);
GetHostContentSettingsMap()->SetContentSettingDefaultScope(
url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
EXPECT_EQ(callback_count(), 1);
EXPECT_EQ(PermissionStatus::GRANTED, callback_result());
UnsubscribePermissionStatusChange(subscription_id);
// ensure no callbacks are received when unsubscribed.
GetHostContentSettingsMap()->SetContentSettingDefaultScope(
url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_BLOCK);
GetHostContentSettingsMap()->SetContentSettingDefaultScope(
url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
EXPECT_EQ(callback_count(), 1);
content::PermissionControllerDelegate::SubscriptionId subscription_id_2 =
SubscribePermissionStatusChange(
PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
main_rfh(), GURL(kOrigin1),
base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
base::Unretained(this)));
EXPECT_EQ(callback_count(), 1);
GetHostContentSettingsMap()->SetContentSettingDefaultScope(
url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_BLOCK);
EXPECT_EQ(callback_count(), 2);
EXPECT_EQ(PermissionStatus::DENIED, callback_result());
UnsubscribePermissionStatusChange(subscription_id_2);
}
TEST_F(PermissionManagerTest, GetCanonicalOrigin) {
GURL requesting("https://requesting.example.com");
GURL embedding("https://embedding.example.com");
EXPECT_EQ(embedding,
GetPermissionManager()->GetCanonicalOrigin(
ContentSettingsType::COOKIES, requesting, embedding));
EXPECT_EQ(requesting,
GetPermissionManager()->GetCanonicalOrigin(
ContentSettingsType::NOTIFICATIONS, requesting, embedding));
EXPECT_EQ(requesting,
GetPermissionManager()->GetCanonicalOrigin(
ContentSettingsType::STORAGE_ACCESS, requesting, embedding));
}
TEST_F(PermissionManagerTest, RequestPermissionInDifferentStoragePartition) {
const GURL kOrigin("https://example.com");
const GURL kOrigin2("https://example2.com");
const GURL kPartitionedOrigin("https://partitioned.com");
ScopedPartitionedOriginBrowserClient browser_client(kPartitionedOrigin);
SetPermission(kOrigin, ContentSettingsType::GEOLOCATION,
ContentSetting::CONTENT_SETTING_ALLOW);
SetPermission(kOrigin2, ContentSettingsType::GEOLOCATION,
ContentSetting::CONTENT_SETTING_BLOCK);
SetPermission(kOrigin2, ContentSettingsType::NOTIFICATIONS,
ContentSetting::CONTENT_SETTING_ALLOW);
SetPermission(kPartitionedOrigin, ContentSettingsType::GEOLOCATION,
ContentSetting::CONTENT_SETTING_BLOCK);
SetPermission(kPartitionedOrigin, ContentSettingsType::NOTIFICATIONS,
ContentSetting::CONTENT_SETTING_ALLOW);
NavigateAndCommit(kOrigin);
content::RenderFrameHost* parent = main_rfh();
content::RenderFrameHost* child =
AddChildRFH(parent, kOrigin2, PermissionsPolicyFeature::kGeolocation);
content::RenderFrameHost* partitioned_child = AddChildRFH(
parent, kPartitionedOrigin, PermissionsPolicyFeature::kGeolocation);
// The parent should have geolocation access which is delegated to child and
// partitioned_child.
EXPECT_EQ(PermissionStatus::GRANTED,
GetPermissionStatusForCurrentDocument(PermissionType::GEOLOCATION,
parent));
EXPECT_EQ(PermissionStatus::GRANTED, GetPermissionStatusForCurrentDocument(
PermissionType::GEOLOCATION, child));
EXPECT_EQ(PermissionStatus::GRANTED,
GetPermissionStatusForCurrentDocument(PermissionType::GEOLOCATION,
partitioned_child));
// The parent should not have notification permission.
EXPECT_EQ(PermissionStatus::ASK, GetPermissionStatusForCurrentDocument(
PermissionType::NOTIFICATIONS, parent));
EXPECT_EQ(PermissionStatus::ASK,
GetPermissionStatusForWorker(
PermissionType::NOTIFICATIONS, parent->GetProcess(),
parent->GetLastCommittedOrigin().GetURL()));
// The non-partitioned child should have notification permission.
EXPECT_EQ(PermissionStatus::GRANTED,
GetPermissionStatusForCurrentDocument(PermissionType::NOTIFICATIONS,
child));
EXPECT_EQ(PermissionStatus::GRANTED,
GetPermissionStatusForWorker(
PermissionType::NOTIFICATIONS, child->GetProcess(),
child->GetLastCommittedOrigin().GetURL()));
// The partitioned child should not have notification permission because it
// belongs to a different StoragePartition, even though its origin would have
// permission if loaded in a main frame.
EXPECT_EQ(PermissionStatus::DENIED,
GetPermissionStatusForCurrentDocument(PermissionType::NOTIFICATIONS,
partitioned_child));
EXPECT_EQ(PermissionStatus::DENIED,
GetPermissionStatusForWorker(
PermissionType::NOTIFICATIONS, partitioned_child->GetProcess(),
partitioned_child->GetLastCommittedOrigin().GetURL()));
}
} // namespace permissions