1 // Copyright 2016 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/permissions/permission_uma_util.h"
7 #include "base/files/scoped_temp_dir.h"
8 #include "base/strings/strcat.h"
9 #include "base/test/metrics/histogram_tester.h"
10 #include "base/test/scoped_feature_list.h"
11 #include "base/test/simple_test_clock.h"
12 #include "base/time/clock.h"
13 #include "base/time/time.h"
14 #include "base/values.h"
15 #include "components/content_settings/core/browser/content_settings_uma_util.h"
16 #include "components/content_settings/core/browser/host_content_settings_map.h"
17 #include "components/content_settings/core/common/content_settings_pattern.h"
18 #include "components/content_settings/core/common/content_settings_types.h"
19 #include "components/content_settings/core/common/features.h"
20 #include "components/permissions/constants.h"
21 #include "components/permissions/permission_request_manager.h"
22 #include "components/permissions/permission_util.h"
23 #include "components/permissions/test/test_permissions_client.h"
24 #include "components/ukm/content/source_url_recorder.h"
25 #include "components/ukm/test_ukm_recorder.h"
26 #include "content/public/browser/render_frame_host.h"
27 #include "content/public/browser/web_contents.h"
28 #include "content/public/test/browser_task_environment.h"
29 #include "content/public/test/navigation_simulator.h"
30 #include "content/public/test/test_browser_context.h"
31 #include "content/public/test/test_renderer_host.h"
32 #include "content/test/test_render_frame_host.h"
33 #include "testing/gtest/include/gtest/gtest.h"
34 #include "third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom.h"
36 namespace permissions {
40 constexpr const char* kTopLevelUrl = "https://google.com";
41 constexpr const char* kSameOriginFrameUrl = "https://google.com/a/same.html";
42 constexpr const char* kCrossOriginFrameUrl = "https://embedded.google.com";
43 constexpr const char* kCrossOriginFrameUrl2 = "https://embedded2.google.com";
45 constexpr const char* kGeolocationUsageHistogramName =
46 "Permissions.Experimental.Usage.Geolocation.IsCrossOriginFrame";
47 constexpr const char* kGeolocationPermissionsPolicyUsageHistogramName =
48 "Permissions.Experimental.Usage.Geolocation.CrossOriginFrame."
49 "TopLevelHeaderPolicy";
50 constexpr const char* kGeolocationPermissionsPolicyActionHistogramName =
51 "Permissions.Action.Geolocation.CrossOriginFrame."
52 "TopLevelHeaderPolicy";
54 blink::ParsedPermissionsPolicy CreatePermissionsPolicy(
55 blink::mojom::PermissionsPolicyFeature feature,
56 const std::vector<std::string>& origins,
57 bool matches_all_origins = false) {
58 std::vector<blink::OriginWithPossibleWildcards> allow_origins;
59 for (const auto& origin : origins) {
60 allow_origins.emplace_back(*blink::OriginWithPossibleWildcards::FromOrigin(
61 url::Origin::Create(GURL(origin))));
63 return {{feature, allow_origins, /*self_if_matches=*/absl::nullopt,
65 /*matches_opaque_src*/ false}};
68 PermissionRequestManager* SetupRequestManager(
69 content::WebContents* web_contents) {
70 PermissionRequestManager::CreateForWebContents(web_contents);
71 return PermissionRequestManager::FromWebContents(web_contents);
74 struct PermissionsDelegationTestConfig {
75 ContentSettingsType type;
76 PermissionAction action;
77 absl::optional<blink::mojom::PermissionsPolicyFeature> feature_overriden;
79 bool matches_all_origins;
80 std::vector<std::string> origins;
82 // Expected resulting permissions policy configuration.
83 absl::optional<PermissionHeaderPolicyForUMA> expected_configuration;
86 #if !BUILDFLAG(IS_ANDROID)
87 ContentSettingsForOneType GetRevokedUnusedPermissions(
88 HostContentSettingsMap* hcsm) {
89 return hcsm->GetSettingsForOneType(
90 ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS);
94 // Wrapper class so that we can pass a closure to the PermissionRequest
95 // ctor, to handle all dtor paths (avoid crash in dtor of WebContent)
96 class PermissionRequestWrapper {
98 explicit PermissionRequestWrapper(permissions::RequestType type,
100 const bool user_gesture = true;
101 auto decided = [](ContentSetting, bool, bool) {};
102 request_ = std::make_unique<permissions::PermissionRequest>(
103 GURL(url), type, user_gesture, base::BindRepeating(decided),
104 base::BindOnce(&PermissionRequestWrapper::DeleteThis,
105 base::Unretained(this)));
108 PermissionRequestWrapper(const PermissionRequestWrapper&) = delete;
109 PermissionRequestWrapper& operator=(const PermissionRequestWrapper&) = delete;
111 permissions::PermissionRequest* request() { return request_.get(); }
114 void DeleteThis() { delete this; }
116 std::unique_ptr<permissions::PermissionRequest> request_;
121 class PermissionsDelegationUmaUtilTest
122 : public content::RenderViewHostTestHarness,
123 public testing::WithParamInterface<PermissionsDelegationTestConfig> {
125 void SetUp() override { RenderViewHostTestHarness::SetUp(); }
127 content::RenderFrameHost* GetMainFrameAndNavigate(const char* origin) {
128 content::RenderFrameHost* result = web_contents()->GetPrimaryMainFrame();
129 content::RenderFrameHostTester::For(result)
130 ->InitializeRenderFrameIfNeeded();
131 SimulateNavigation(&result, GURL(origin));
135 content::RenderFrameHost* AddChildFrameWithPermissionsPolicy(
136 content::RenderFrameHost* parent,
138 blink::ParsedPermissionsPolicy policy) {
139 content::RenderFrameHost* result =
140 content::RenderFrameHostTester::For(parent)->AppendChildWithPolicy(
142 content::RenderFrameHostTester::For(result)
143 ->InitializeRenderFrameIfNeeded();
144 SimulateNavigation(&result, GURL(origin));
148 // The permissions policy is invariant and required the page to be
150 void RefreshAndSetPermissionsPolicy(content::RenderFrameHost** rfh,
151 blink::ParsedPermissionsPolicy policy) {
152 content::RenderFrameHost* current = *rfh;
153 auto navigation = content::NavigationSimulator::CreateRendererInitiated(
154 current->GetLastCommittedURL(), current);
155 navigation->SetPermissionsPolicyHeader(policy);
156 navigation->Commit();
157 *rfh = navigation->GetFinalRenderFrameHost();
160 // Simulates navigation and returns the final RenderFrameHost.
161 void SimulateNavigation(content::RenderFrameHost** rfh, const GURL& url) {
162 auto navigation_simulator =
163 content::NavigationSimulator::CreateRendererInitiated(url, *rfh);
164 navigation_simulator->Commit();
165 *rfh = navigation_simulator->GetFinalRenderFrameHost();
169 TestPermissionsClient permissions_client_;
172 class PermissionUmaUtilTest : public testing::Test {
174 content::BrowserTaskEnvironment task_environment_;
175 TestPermissionsClient permissions_client_;
178 TEST_F(PermissionUmaUtilTest, ScopedRevocationReporter) {
179 content::TestBrowserContext browser_context;
181 // TODO(tsergeant): Add more comprehensive tests of PermissionUmaUtil.
182 base::HistogramTester histograms;
183 HostContentSettingsMap* map =
184 PermissionsClient::Get()->GetSettingsMap(&browser_context);
185 GURL host("https://example.com");
186 ContentSettingsPattern host_pattern =
187 ContentSettingsPattern::FromURLNoWildcard(host);
188 ContentSettingsPattern host_containing_wildcards_pattern =
189 ContentSettingsPattern::FromString("https://[*.]example.com/");
190 ContentSettingsType type = ContentSettingsType::GEOLOCATION;
191 PermissionSourceUI source_ui = PermissionSourceUI::SITE_SETTINGS;
193 // Allow->Block triggers a revocation.
194 map->SetContentSettingDefaultScope(host, host, type, CONTENT_SETTING_ALLOW);
196 PermissionUmaUtil::ScopedRevocationReporter scoped_revocation_reporter(
197 &browser_context, host, host, type, source_ui);
198 map->SetContentSettingDefaultScope(host, host, type, CONTENT_SETTING_BLOCK);
200 histograms.ExpectBucketCount("Permissions.Action.Geolocation",
201 static_cast<int>(PermissionAction::REVOKED), 1);
203 // Block->Allow does not trigger a revocation.
205 PermissionUmaUtil::ScopedRevocationReporter scoped_revocation_reporter(
206 &browser_context, host, host, type, source_ui);
207 map->SetContentSettingDefaultScope(host, host, type, CONTENT_SETTING_ALLOW);
209 histograms.ExpectBucketCount("Permissions.Action.Geolocation",
210 static_cast<int>(PermissionAction::REVOKED), 1);
212 // Allow->Default triggers a revocation when default is 'ask'.
213 map->SetDefaultContentSetting(type, CONTENT_SETTING_ASK);
215 PermissionUmaUtil::ScopedRevocationReporter scoped_revocation_reporter(
216 &browser_context, host, host, type, source_ui);
217 map->SetContentSettingDefaultScope(host, host, type,
218 CONTENT_SETTING_DEFAULT);
220 histograms.ExpectBucketCount("Permissions.Action.Geolocation",
221 static_cast<int>(PermissionAction::REVOKED), 2);
223 // Allow->Default does not trigger a revocation when default is 'allow'.
224 map->SetDefaultContentSetting(type, CONTENT_SETTING_ALLOW);
226 PermissionUmaUtil::ScopedRevocationReporter scoped_revocation_reporter(
227 &browser_context, host, host, type, source_ui);
228 map->SetContentSettingDefaultScope(host, host, type,
229 CONTENT_SETTING_DEFAULT);
231 histograms.ExpectBucketCount("Permissions.Action.Geolocation",
232 static_cast<int>(PermissionAction::REVOKED), 2);
234 // Allow->Block with url pattern string triggers a revocation.
235 map->SetContentSettingDefaultScope(host, host, type, CONTENT_SETTING_ALLOW);
237 PermissionUmaUtil::ScopedRevocationReporter scoped_revocation_reporter(
238 &browser_context, host_pattern, host_pattern, type, source_ui);
239 map->SetContentSettingCustomScope(host_pattern,
240 ContentSettingsPattern::Wildcard(), type,
241 CONTENT_SETTING_BLOCK);
243 histograms.ExpectBucketCount("Permissions.Action.Geolocation",
244 static_cast<int>(PermissionAction::REVOKED), 3);
246 // Allow->Block with non url pattern string does not trigger a revocation.
247 map->SetContentSettingDefaultScope(host, host, type, CONTENT_SETTING_ALLOW);
249 PermissionUmaUtil::ScopedRevocationReporter scoped_revocation_reporter(
250 &browser_context, host_containing_wildcards_pattern, host_pattern, type,
252 map->SetContentSettingCustomScope(host_containing_wildcards_pattern,
253 ContentSettingsPattern::Wildcard(), type,
254 CONTENT_SETTING_BLOCK);
256 histograms.ExpectBucketCount("Permissions.Action.Geolocation",
257 static_cast<int>(PermissionAction::REVOKED), 3);
260 TEST_F(PermissionUmaUtilTest, CrowdDenyVersionTest) {
261 base::HistogramTester histograms;
263 const absl::optional<base::Version> empty_version;
264 PermissionUmaUtil::RecordCrowdDenyVersionAtAbuseCheckTime(empty_version);
265 histograms.ExpectBucketCount(
266 "Permissions.CrowdDeny.PreloadData.VersionAtAbuseCheckTime", 0, 1);
268 const absl::optional<base::Version> valid_version =
269 base::Version({2020, 10, 11, 1234});
270 PermissionUmaUtil::RecordCrowdDenyVersionAtAbuseCheckTime(valid_version);
271 histograms.ExpectBucketCount(
272 "Permissions.CrowdDeny.PreloadData.VersionAtAbuseCheckTime", 20201011, 1);
274 const absl::optional<base::Version> valid_old_version =
275 base::Version({2019, 10, 10, 1234});
276 PermissionUmaUtil::RecordCrowdDenyVersionAtAbuseCheckTime(valid_old_version);
277 histograms.ExpectBucketCount(
278 "Permissions.CrowdDeny.PreloadData.VersionAtAbuseCheckTime", 1, 1);
280 const absl::optional<base::Version> valid_future_version =
281 base::Version({2021, 1, 1, 1234});
282 PermissionUmaUtil::RecordCrowdDenyVersionAtAbuseCheckTime(
283 valid_future_version);
284 histograms.ExpectBucketCount(
285 "Permissions.CrowdDeny.PreloadData.VersionAtAbuseCheckTime", 20210101, 1);
287 const absl::optional<base::Version> invalid_version =
288 base::Version({2020, 10, 11});
289 PermissionUmaUtil::RecordCrowdDenyVersionAtAbuseCheckTime(valid_version);
290 histograms.ExpectBucketCount(
291 "Permissions.CrowdDeny.PreloadData.VersionAtAbuseCheckTime", 1, 1);
294 // Test that the appropriate UMA metrics have been recorded when the DSE is
296 TEST_F(PermissionUmaUtilTest, MetricsAreRecordedWhenAutoDSEPermissionReverted) {
297 const std::string kTransitionHistogramPrefix =
298 "Permissions.DSE.AutoPermissionRevertTransition.";
301 ContentSetting backed_up_setting;
302 ContentSetting effective_setting;
303 ContentSetting end_state_setting;
304 permissions::AutoDSEPermissionRevertTransition expected_transition;
306 // Expected valid combinations.
307 {CONTENT_SETTING_ASK, CONTENT_SETTING_ALLOW, CONTENT_SETTING_ASK,
308 permissions::AutoDSEPermissionRevertTransition::NO_DECISION_ASK},
309 {CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW,
310 permissions::AutoDSEPermissionRevertTransition::PRESERVE_ALLOW},
311 {CONTENT_SETTING_BLOCK, CONTENT_SETTING_ALLOW, CONTENT_SETTING_ASK,
312 permissions::AutoDSEPermissionRevertTransition::CONFLICT_ASK},
313 {CONTENT_SETTING_ASK, CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK,
314 permissions::AutoDSEPermissionRevertTransition::PRESERVE_BLOCK_ASK},
315 {CONTENT_SETTING_ALLOW, CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK,
316 permissions::AutoDSEPermissionRevertTransition::PRESERVE_BLOCK_ALLOW},
317 {CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK,
318 permissions::AutoDSEPermissionRevertTransition::PRESERVE_BLOCK_BLOCK},
321 // We test every combination of test case for notifications and geolocation to
322 // basically test the entire possible transition space.
323 for (const auto& test : kTests) {
324 for (const auto type : {ContentSettingsType::NOTIFICATIONS,
325 ContentSettingsType::GEOLOCATION}) {
326 const std::string type_string = type == ContentSettingsType::NOTIFICATIONS
329 base::HistogramTester histograms;
330 PermissionUmaUtil::RecordAutoDSEPermissionReverted(
331 type, test.backed_up_setting, test.effective_setting,
332 test.end_state_setting);
334 // Test that the expected samples are recorded in histograms.
335 histograms.ExpectBucketCount(kTransitionHistogramPrefix + type_string,
336 test.expected_transition, 1);
337 histograms.ExpectTotalCount(kTransitionHistogramPrefix + type_string, 1);
342 TEST_F(PermissionsDelegationUmaUtilTest, UsageAndPromptInTopLevelFrame) {
343 base::HistogramTester histograms;
344 auto* main_frame = GetMainFrameAndNavigate(kTopLevelUrl);
345 histograms.ExpectTotalCount(kGeolocationUsageHistogramName, 0);
347 auto* permission_request_manager = SetupRequestManager(web_contents());
348 PermissionRequestWrapper* request_owner =
349 new PermissionRequestWrapper(RequestType::kGeolocation, kTopLevelUrl);
350 permission_request_manager->AddRequest(main_frame, request_owner->request());
351 PermissionUmaUtil::RecordPermissionsUsageSourceAndPolicyConfiguration(
352 ContentSettingsType::GEOLOCATION, main_frame);
353 EXPECT_THAT(histograms.GetAllSamples(kGeolocationUsageHistogramName),
354 testing::ElementsAre(base::Bucket(0, 1)));
355 PermissionUmaUtil::PermissionPromptResolved(
356 {request_owner->request()}, web_contents(), PermissionAction::GRANTED,
357 /*time_to_decision*/ base::TimeDelta(),
358 PermissionPromptDisposition::NOT_APPLICABLE,
359 /* ui_reason*/ absl::nullopt,
360 /*predicted_grant_likelihood*/ absl::nullopt,
361 /*prediction_decision_held_back*/ absl::nullopt,
362 /*ignored_reason*/ absl::nullopt, /*did_show_prompt*/ false,
363 /*did_click_managed*/ false,
364 /*did_click_learn_more*/ false);
365 histograms.ExpectTotalCount(kGeolocationPermissionsPolicyActionHistogramName,
369 TEST_F(PermissionUmaUtilTest, PageInfoPermissionReallowedTest) {
370 base::HistogramTester histograms;
372 PermissionUmaUtil::RecordPermissionRecoverySuccessRate(
373 ContentSettingsType::MEDIASTREAM_CAMERA, /*is_used=*/true,
374 /*show_infobar=*/true, /*page_reload=*/true);
375 histograms.ExpectBucketCount(
376 "Permissions.PageInfo.Changed.VideoCapture.Reallowed.Outcome",
377 permissions::PermissionChangeInfo::kInfobarShownPageReloadPermissionUsed,
380 PermissionUmaUtil::RecordPermissionRecoverySuccessRate(
381 ContentSettingsType::MEDIASTREAM_CAMERA, /*is_used=*/false,
382 /*show_infobar=*/true, /*page_reload=*/true);
383 histograms.ExpectBucketCount(
384 "Permissions.PageInfo.Changed.VideoCapture.Reallowed.Outcome",
385 permissions::PermissionChangeInfo::
386 kInfobarShownPageReloadPermissionNotUsed,
389 PermissionUmaUtil::RecordPermissionRecoverySuccessRate(
390 ContentSettingsType::MEDIASTREAM_CAMERA, /*is_used=*/true,
391 /*show_infobar=*/true, /*page_reload=*/false);
392 histograms.ExpectBucketCount(
393 "Permissions.PageInfo.Changed.VideoCapture.Reallowed.Outcome",
394 permissions::PermissionChangeInfo::
395 kInfobarShownNoPageReloadPermissionUsed,
398 PermissionUmaUtil::RecordPermissionRecoverySuccessRate(
399 ContentSettingsType::MEDIASTREAM_CAMERA, /*is_used=*/false,
400 /*show_infobar=*/true, /*page_reload=*/false);
401 histograms.ExpectBucketCount(
402 "Permissions.PageInfo.Changed.VideoCapture.Reallowed.Outcome",
403 permissions::PermissionChangeInfo::
404 kInfobarShownNoPageReloadPermissionNotUsed,
407 PermissionUmaUtil::RecordPermissionRecoverySuccessRate(
408 ContentSettingsType::MEDIASTREAM_CAMERA, /*is_used=*/true,
409 /*show_infobar=*/false, /*page_reload=*/true);
410 histograms.ExpectBucketCount(
411 "Permissions.PageInfo.Changed.VideoCapture.Reallowed.Outcome",
412 permissions::PermissionChangeInfo::
413 kInfobarNotShownPageReloadPermissionUsed,
416 PermissionUmaUtil::RecordPermissionRecoverySuccessRate(
417 ContentSettingsType::MEDIASTREAM_CAMERA, /*is_used=*/false,
418 /*show_infobar=*/false, /*page_reload=*/true);
419 histograms.ExpectBucketCount(
420 "Permissions.PageInfo.Changed.VideoCapture.Reallowed.Outcome",
421 permissions::PermissionChangeInfo::
422 kInfobarNotShownPageReloadPermissionNotUsed,
425 PermissionUmaUtil::RecordPermissionRecoverySuccessRate(
426 ContentSettingsType::MEDIASTREAM_CAMERA, /*is_used=*/true,
427 /*show_infobar=*/false, /*page_reload=*/false);
428 histograms.ExpectBucketCount(
429 "Permissions.PageInfo.Changed.VideoCapture.Reallowed.Outcome",
430 permissions::PermissionChangeInfo::
431 kInfobarNotShownNoPageReloadPermissionUsed,
434 PermissionUmaUtil::RecordPermissionRecoverySuccessRate(
435 ContentSettingsType::MEDIASTREAM_CAMERA, /*is_used=*/false,
436 /*show_infobar=*/false, /*page_reload=*/false);
437 histograms.ExpectBucketCount(
438 "Permissions.PageInfo.Changed.VideoCapture.Reallowed.Outcome",
439 permissions::PermissionChangeInfo::
440 kInfobarNotShownNoPageReloadPermissionNotUsed,
444 #if !BUILDFLAG(IS_ANDROID)
445 TEST_F(PermissionUmaUtilTest, RecordPermissionRegrantForUnusedSites) {
446 const GURL origin = GURL("https://example1.com:443");
447 content::TestBrowserContext browser_context;
448 base::HistogramTester histograms;
449 ContentSettingsType content_type = ContentSettingsType::GEOLOCATION;
450 std::string permission_string =
451 PermissionUtil::GetPermissionString(content_type);
452 base::SimpleTestClock clock;
453 base::Time now(base::Time::Now());
455 HostContentSettingsMap* hcsm =
456 PermissionsClient::Get()->GetSettingsMap(&browser_context);
457 hcsm->SetClockForTesting(&clock);
459 std::string prefix = "Settings.SafetyCheck.UnusedSitePermissionsRegrantDays";
461 // Record regrant before permission has been revoked.
462 PermissionUmaUtil::RecordPermissionRegrantForUnusedSites(
463 origin, content_type, PermissionSourceUI::PROMPT, &browser_context, now);
464 histograms.ExpectTotalCount(prefix + "Prompt." + permission_string, 0);
465 histograms.ExpectTotalCount(prefix + "Prompt.All", 0);
467 // Create a revoked permission.
468 auto dict = base::Value::Dict().Set(
469 permissions::kRevokedKey,
470 base::Value::List().Append(static_cast<int32_t>(content_type)));
471 // Set expiration to five days before the clean-up threshold to mimic that the
472 // permission was revoked five days ago.
473 base::Time past(now - base::Days(5));
474 content_settings::ContentSettingConstraints constraint(past);
475 constraint.set_lifetime(
476 content_settings::features::
477 kSafetyCheckUnusedSitePermissionsRevocationCleanUpThreshold.Get());
478 hcsm->SetWebsiteSettingDefaultScope(
479 origin, origin, ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS,
480 base::Value(dict.Clone()), constraint);
482 // Regrant another permission through the prompt.
483 PermissionUmaUtil::RecordPermissionRegrantForUnusedSites(
484 origin, ContentSettingsType::NOTIFICATIONS, PermissionSourceUI::PROMPT,
485 &browser_context, now);
486 histograms.ExpectTotalCount(prefix + "Prompt." +
487 PermissionUtil::GetPermissionString(
488 ContentSettingsType::NOTIFICATIONS),
490 histograms.ExpectTotalCount(prefix + "Prompt.All", 0);
492 // Regrant the geolocation permission through the prompt.
493 PermissionUmaUtil::RecordPermissionRegrantForUnusedSites(
494 origin, content_type, PermissionSourceUI::PROMPT, &browser_context, now);
495 histograms.ExpectBucketCount(prefix + "Prompt." + permission_string, 5, 1);
496 histograms.ExpectBucketCount(prefix + "Prompt.All", 5, 1);
498 // Regrant the geolocation permission through site settings.
499 PermissionUmaUtil::RecordPermissionRegrantForUnusedSites(
500 origin, content_type, PermissionSourceUI::SITE_SETTINGS, &browser_context,
502 histograms.ExpectBucketCount(prefix + "Settings." + permission_string, 5, 1);
503 histograms.ExpectBucketCount(prefix + "Settings.All", 5, 1);
506 TEST_F(PermissionUmaUtilTest, GetDaysSinceUnusedSitePermissionRevocation) {
507 base::test::ScopedFeatureList scoped_feature;
508 scoped_feature.InitAndEnableFeature(
509 content_settings::features::kSafetyCheckUnusedSitePermissions);
511 content::TestBrowserContext browser_context;
512 base::SimpleTestClock clock;
513 base::Time now(base::Time::Now());
515 HostContentSettingsMap* hcsm =
516 PermissionsClient::Get()->GetSettingsMap(&browser_context);
518 const GURL url = GURL("https://example1.com:443");
519 const ContentSettingsType type = ContentSettingsType::GEOLOCATION;
520 content_settings::ContentSettingConstraints constraint(clock.Now());
521 constraint.set_track_last_visit_for_autoexpiration(true);
523 absl::optional<uint32_t> days_since_revocation;
525 // Permission has not yet been revoked, so shouldn't return a number of days
527 days_since_revocation =
528 PermissionUmaUtil::GetDaysSinceUnusedSitePermissionRevocation(
529 url, ContentSettingsType::GEOLOCATION, now, hcsm);
530 ASSERT_FALSE(days_since_revocation.has_value());
532 hcsm->SetContentSettingDefaultScope(
533 url, url, type, ContentSetting::CONTENT_SETTING_ALLOW, constraint);
534 EXPECT_EQ(GetRevokedUnusedPermissions(hcsm).size(), 0u);
536 // Travel 70 days through time such that the granted permission would be
538 clock.Advance(base::Days(70));
539 // Revoke permission.
540 content_settings::ContentSettingConstraints expiration_constraint(
542 expiration_constraint.set_lifetime(base::Days(30));
543 auto dict = base::Value::Dict().Set(
544 permissions::kRevokedKey, base::Value::List().Append(static_cast<int32_t>(
545 ContentSettingsType::GEOLOCATION)));
546 hcsm->SetWebsiteSettingCustomScope(
547 ContentSettingsPattern::FromURLNoWildcard(url),
548 ContentSettingsPattern::Wildcard(),
549 ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS,
550 base::Value(std::move(dict)), expiration_constraint);
551 EXPECT_EQ(GetRevokedUnusedPermissions(hcsm).size(), 1u);
553 days_since_revocation =
554 PermissionUmaUtil::GetDaysSinceUnusedSitePermissionRevocation(
555 url, ContentSettingsType::GEOLOCATION, clock.Now(), hcsm);
556 ASSERT_TRUE(days_since_revocation.has_value());
557 EXPECT_EQ(days_since_revocation.value(), 0u);
559 // Forward the clock for five days, which would be the number of days since
561 clock.Advance(base::Days(5));
563 days_since_revocation =
564 PermissionUmaUtil::GetDaysSinceUnusedSitePermissionRevocation(
565 url, ContentSettingsType::GEOLOCATION, clock.Now(), hcsm);
566 ASSERT_TRUE(days_since_revocation.has_value());
567 EXPECT_EQ(days_since_revocation.value(), 5u);
571 TEST_F(PermissionsDelegationUmaUtilTest, SameOriginFrame) {
572 base::HistogramTester histograms;
573 auto* main_frame = GetMainFrameAndNavigate(kTopLevelUrl);
574 auto* child_frame = AddChildFrameWithPermissionsPolicy(
575 main_frame, kSameOriginFrameUrl,
576 CreatePermissionsPolicy(
577 blink::mojom::PermissionsPolicyFeature::kGeolocation,
578 {std::string(kTopLevelUrl), std::string(kSameOriginFrameUrl)},
579 /*matches_all_origins*/ true));
580 histograms.ExpectTotalCount(kGeolocationUsageHistogramName, 0);
582 auto* permission_request_manager = SetupRequestManager(web_contents());
583 PermissionRequestWrapper* request_owner = new PermissionRequestWrapper(
584 RequestType::kGeolocation, kSameOriginFrameUrl);
585 permission_request_manager->AddRequest(child_frame, request_owner->request());
586 PermissionUmaUtil::RecordPermissionsUsageSourceAndPolicyConfiguration(
587 ContentSettingsType::GEOLOCATION, child_frame);
588 EXPECT_THAT(histograms.GetAllSamples(kGeolocationUsageHistogramName),
589 testing::ElementsAre(base::Bucket(0, 1)));
590 histograms.ExpectTotalCount(kGeolocationPermissionsPolicyUsageHistogramName,
592 PermissionUmaUtil::PermissionPromptResolved(
593 {request_owner->request()}, web_contents(), PermissionAction::GRANTED,
594 /*time_to_decision*/ base::TimeDelta(),
595 PermissionPromptDisposition::NOT_APPLICABLE,
596 /* ui_reason*/ absl::nullopt,
597 /*predicted_grant_likelihood*/ absl::nullopt,
598 /*prediction_decision_held_back*/ absl::nullopt,
599 /*ignored_reason*/ absl::nullopt, /*did_show_prompt*/ false,
600 /*did_click_managed*/ false,
601 /*did_click_learn_more*/ false);
602 histograms.ExpectTotalCount(kGeolocationPermissionsPolicyActionHistogramName,
606 TEST_P(PermissionsDelegationUmaUtilTest, TopLevelFrame) {
607 auto type = GetParam().type;
608 std::string permission_string = PermissionUtil::GetPermissionString(type);
609 // The histogram values should match with the ones defined in
610 // |permission_uma_util.cc|
611 std::string kPermissionsPolicyHeaderHistogramName =
612 base::StrCat({"Permissions.Experimental.PrimaryMainNavigationFinished.",
613 permission_string, ".TopLevelHeaderPolicy"});
615 base::HistogramTester histograms;
616 auto* main_frame = GetMainFrameAndNavigate(kTopLevelUrl);
617 auto feature = PermissionUtil::GetPermissionsPolicyFeature(type);
618 blink::ParsedPermissionsPolicy top_policy;
619 if (feature.has_value() &&
620 (GetParam().matches_all_origins || !GetParam().origins.empty())) {
621 top_policy = CreatePermissionsPolicy(
622 GetParam().feature_overriden.has_value()
623 ? GetParam().feature_overriden.value()
625 GetParam().origins, GetParam().matches_all_origins);
628 if (!top_policy.empty()) {
629 RefreshAndSetPermissionsPolicy(&main_frame, top_policy);
632 histograms.ExpectTotalCount(kPermissionsPolicyHeaderHistogramName, 0);
634 PermissionUmaUtil::RecordTopLevelPermissionsHeaderPolicyOnNavigation(
637 histograms.GetAllSamples(kPermissionsPolicyHeaderHistogramName),
638 testing::ElementsAre(base::Bucket(
639 static_cast<int>(GetParam().expected_configuration.value()), 1)));
642 INSTANTIATE_TEST_SUITE_P(
644 PermissionsDelegationUmaUtilTest,
646 PermissionsDelegationTestConfig{
647 ContentSettingsType::GEOLOCATION, PermissionAction::GRANTED,
648 /*feature_overriden*/ absl::nullopt,
649 /*matches_all_origins*/ true,
651 PermissionHeaderPolicyForUMA::FEATURE_ALLOWLIST_IS_WILDCARD},
653 PermissionsDelegationTestConfig{
654 ContentSettingsType::GEOLOCATION,
655 PermissionAction::GRANTED,
656 /*feature_overriden*/ absl::nullopt,
657 /*matches_all_origins*/ false,
658 {std::string(kTopLevelUrl)},
659 PermissionHeaderPolicyForUMA::
660 FEATURE_ALLOWLIST_EXPLICITLY_MATCHES_ORIGIN},
662 PermissionsDelegationTestConfig{
663 ContentSettingsType::GEOLOCATION, PermissionAction::GRANTED,
664 /*feature_overriden*/ absl::nullopt,
665 /*matches_all_origins*/ false,
667 PermissionHeaderPolicyForUMA::HEADER_NOT_PRESENT_OR_INVALID},
669 PermissionsDelegationTestConfig{
670 ContentSettingsType::GEOLOCATION,
671 PermissionAction::GRANTED,
672 absl::make_optional<blink::mojom::PermissionsPolicyFeature>(
673 blink::mojom::PermissionsPolicyFeature::kCamera),
674 /*matches_all_origins*/ false,
675 {std::string(kTopLevelUrl)},
676 PermissionHeaderPolicyForUMA::FEATURE_NOT_PRESENT},
678 PermissionsDelegationTestConfig{
679 ContentSettingsType::GEOLOCATION,
680 PermissionAction::GRANTED,
681 /*feature_overriden*/ absl::nullopt,
682 /*matches_all_origins*/ false,
683 {std::string(kCrossOriginFrameUrl)},
684 PermissionHeaderPolicyForUMA::
685 FEATURE_ALLOWLIST_DOES_NOT_MATCH_ORIGIN}));
687 class CrossFramePermissionsDelegationUmaUtilTest
688 : public PermissionsDelegationUmaUtilTest {
690 CrossFramePermissionsDelegationUmaUtilTest() = default;
693 TEST_P(CrossFramePermissionsDelegationUmaUtilTest, CrossOriginFrame) {
694 auto type = GetParam().type;
695 std::string permission_string = PermissionUtil::GetPermissionString(type);
696 // The histogram values should match with the ones defined in
697 // |permission_uma_util.cc|
698 std::string kUsageHistogramName =
699 base::StrCat({PermissionUmaUtil::kPermissionsExperimentalUsagePrefix,
700 permission_string, ".IsCrossOriginFrame"});
701 std::string kCrossOriginFrameActionHistogramName =
702 base::StrCat({PermissionUmaUtil::kPermissionsActionPrefix,
703 permission_string, ".CrossOriginFrame"});
704 std::string kPermissionsPolicyUsageHistogramName = base::StrCat(
705 {PermissionUmaUtil::kPermissionsExperimentalUsagePrefix,
706 permission_string, ".CrossOriginFrame.TopLevelHeaderPolicy"});
707 std::string kPermissionsPolicyActionHistogramName = base::StrCat(
708 {PermissionUmaUtil::kPermissionsActionPrefix, permission_string,
709 ".CrossOriginFrame.TopLevelHeaderPolicy"});
711 base::HistogramTester histograms;
712 auto* main_frame = GetMainFrameAndNavigate(kTopLevelUrl);
713 auto feature = PermissionUtil::GetPermissionsPolicyFeature(type);
714 blink::ParsedPermissionsPolicy top_policy;
715 if (feature.has_value() &&
716 (GetParam().matches_all_origins || !GetParam().origins.empty())) {
717 top_policy = CreatePermissionsPolicy(
718 GetParam().feature_overriden.has_value()
719 ? GetParam().feature_overriden.value()
721 GetParam().origins, GetParam().matches_all_origins);
724 if (!top_policy.empty()) {
725 RefreshAndSetPermissionsPolicy(&main_frame, top_policy);
728 // Add nested subframes A(B(C))
729 blink::ParsedPermissionsPolicy empty_policy;
730 auto* child_frame = AddChildFrameWithPermissionsPolicy(
731 main_frame, kCrossOriginFrameUrl,
733 ? CreatePermissionsPolicy(feature.value(),
734 {std::string(kCrossOriginFrameUrl)},
735 /*matches_all_origins*/ false)
737 child_frame = AddChildFrameWithPermissionsPolicy(
738 child_frame, kCrossOriginFrameUrl2,
740 ? CreatePermissionsPolicy(feature.value(),
741 {std::string(kCrossOriginFrameUrl2)},
742 /*matches_all_origins*/ false)
744 histograms.ExpectTotalCount(kUsageHistogramName, 0);
745 histograms.ExpectTotalCount(kPermissionsPolicyUsageHistogramName, 0);
746 histograms.ExpectTotalCount(kPermissionsPolicyActionHistogramName, 0);
748 PermissionUmaUtil::RecordPermissionsUsageSourceAndPolicyConfiguration(
750 EXPECT_THAT(histograms.GetAllSamples(kUsageHistogramName),
751 testing::ElementsAre(base::Bucket(1, 1)));
752 if (feature.has_value()) {
754 histograms.GetAllSamples(kPermissionsPolicyUsageHistogramName),
755 testing::ElementsAre(base::Bucket(
756 static_cast<int>(GetParam().expected_configuration.value()), 1)));
758 histograms.ExpectTotalCount(kPermissionsPolicyUsageHistogramName, 0);
761 auto* permission_request_manager = SetupRequestManager(web_contents());
762 PermissionRequestWrapper* request_owner = new PermissionRequestWrapper(
763 permissions::ContentSettingsTypeToRequestType(type),
764 kCrossOriginFrameUrl2);
765 permission_request_manager->AddRequest(child_frame, request_owner->request());
766 PermissionUmaUtil::PermissionPromptResolved(
767 {request_owner->request()}, web_contents(), GetParam().action,
768 /*time_to_decision*/ base::TimeDelta(),
769 PermissionPromptDisposition::NOT_APPLICABLE,
770 /* ui_reason*/ absl::nullopt,
771 /*predicted_grant_likelihood*/ absl::nullopt,
772 /*prediction_decision_held_back*/ absl::nullopt,
773 /*ignored_reason*/ absl::nullopt, /*did_show_prompt*/ false,
774 /*did_click_managed*/ false,
775 /*did_click_learn_more*/ false);
776 if (feature.has_value()) {
778 histograms.GetAllSamples(kPermissionsPolicyActionHistogramName),
779 testing::ElementsAre(base::Bucket(
780 static_cast<int>(GetParam().expected_configuration.value()), 1)));
782 histograms.ExpectTotalCount(kPermissionsPolicyActionHistogramName, 0);
785 EXPECT_THAT(histograms.GetAllSamples(kCrossOriginFrameActionHistogramName),
786 testing::ElementsAre(
787 base::Bucket(static_cast<int>(GetParam().action), 1)));
790 INSTANTIATE_TEST_SUITE_P(
792 CrossFramePermissionsDelegationUmaUtilTest,
794 PermissionsDelegationTestConfig{
795 ContentSettingsType::GEOLOCATION, PermissionAction::GRANTED,
796 /*feature_overriden*/ absl::nullopt,
797 /*matches_all_origins*/ true,
799 PermissionHeaderPolicyForUMA::FEATURE_ALLOWLIST_IS_WILDCARD},
801 PermissionsDelegationTestConfig{
802 ContentSettingsType::GEOLOCATION,
803 PermissionAction::DENIED,
804 /*feature_overriden*/ absl::nullopt,
805 /*matches_all_origins*/ false,
806 {std::string(kTopLevelUrl), std::string(kCrossOriginFrameUrl),
807 std::string(kCrossOriginFrameUrl2)},
808 PermissionHeaderPolicyForUMA::
809 FEATURE_ALLOWLIST_EXPLICITLY_MATCHES_ORIGIN},
811 PermissionsDelegationTestConfig{
812 ContentSettingsType::GEOLOCATION, PermissionAction::GRANTED,
813 /*feature_overriden*/ absl::nullopt,
814 /*matches_all_origins*/ false,
816 PermissionHeaderPolicyForUMA::HEADER_NOT_PRESENT_OR_INVALID},
818 PermissionsDelegationTestConfig{
819 ContentSettingsType::GEOLOCATION,
820 PermissionAction::GRANTED,
821 absl::make_optional<blink::mojom::PermissionsPolicyFeature>(
822 blink::mojom::PermissionsPolicyFeature::kCamera),
823 /*matches_all_origins*/ false,
824 {std::string(kTopLevelUrl), std::string(kCrossOriginFrameUrl)},
825 PermissionHeaderPolicyForUMA::FEATURE_NOT_PRESENT},
827 PermissionsDelegationTestConfig{
828 ContentSettingsType::GEOLOCATION,
829 PermissionAction::DENIED,
830 /*feature_overriden*/ absl::nullopt,
831 /*matches_all_origins*/ false,
832 {std::string(kTopLevelUrl), std::string(kCrossOriginFrameUrl)},
833 PermissionHeaderPolicyForUMA::
834 FEATURE_ALLOWLIST_DOES_NOT_MATCH_ORIGIN},
836 PermissionsDelegationTestConfig{
837 ContentSettingsType::ACCESSIBILITY_EVENTS, PermissionAction::DENIED,
838 /*feature_overriden*/ absl::nullopt,
839 /*matches_all_origins*/ true,
841 /*expected_configuration*/ absl::nullopt}));
843 class UkmRecorderPermissionUmaUtilTest
844 : public content::RenderViewHostTestHarness {
846 void SetUp() override { content::RenderViewHostTestHarness::SetUp(); }
848 class UkmRecorderTestPermissionsClient : public TestPermissionsClient {
850 UkmRecorderTestPermissionsClient() = default;
852 void SetSimulatedHasSourceId(bool source_id) {
853 simulated_has_source_id_ = source_id;
856 void GetUkmSourceId(content::BrowserContext* browser_context,
857 content::WebContents* web_contents,
858 const GURL& requesting_origin,
859 GetUkmSourceIdCallback callback) override {
860 // Short circuit and return a null SourceId.
861 if (!simulated_has_source_id_) {
862 std::move(callback).Run(absl::nullopt);
864 ukm::SourceId fake_source_id =
865 ukm::ConvertToSourceId(1, ukm::SourceIdType::NAVIGATION_ID);
866 std::move(callback).Run(fake_source_id);
871 bool simulated_has_source_id_ = false;
874 UkmRecorderTestPermissionsClient permissions_client_;
877 TEST_F(UkmRecorderPermissionUmaUtilTest,
878 NotificationRevocationHistogramDidRecordUkmTest) {
879 base::HistogramTester histograms;
880 content::TestBrowserContext browser_context;
881 ukm::InitializeSourceUrlRecorderForWebContents(web_contents());
882 ukm::TestAutoSetUkmRecorder ukm_recorder;
884 permissions_client_.SetSimulatedHasSourceId(true);
885 const GURL origin(kTopLevelUrl);
886 PermissionUmaUtil::PermissionRevoked(
887 ContentSettingsType::NOTIFICATIONS,
888 permissions::PermissionSourceUI::ANDROID_SETTINGS, origin,
891 histograms.ExpectBucketCount("Permissions.Action.Notifications",
892 static_cast<int64_t>(PermissionAction::REVOKED),
894 histograms.ExpectBucketCount(
895 "Permissions.Revocation.Notifications.DidRecordUkm", 1, 1);
896 const auto entries = ukm_recorder.GetEntriesByName("Permission");
897 ASSERT_EQ(1u, entries.size());
898 const auto* entry = entries.back();
899 EXPECT_EQ(*ukm_recorder.GetEntryMetric(entry, "Action"),
900 static_cast<int64_t>(PermissionAction::REVOKED));
903 TEST_F(UkmRecorderPermissionUmaUtilTest,
904 NotificationRevocationHistogramDroppedUkmTest) {
905 base::HistogramTester histograms;
906 content::TestBrowserContext browser_context;
907 ukm::InitializeSourceUrlRecorderForWebContents(web_contents());
908 ukm::TestAutoSetUkmRecorder ukm_recorder;
910 permissions_client_.SetSimulatedHasSourceId(false);
911 const GURL origin(kTopLevelUrl);
912 PermissionUmaUtil::PermissionRevoked(
913 ContentSettingsType::NOTIFICATIONS,
914 permissions::PermissionSourceUI::ANDROID_SETTINGS, origin,
917 histograms.ExpectBucketCount("Permissions.Action.Notifications",
918 static_cast<int64_t>(PermissionAction::REVOKED),
921 histograms.ExpectBucketCount(
922 "Permissions.Revocation.Notifications.DidRecordUkm", 0, 1);
923 const auto entries = ukm_recorder.GetEntriesByName("Permission");
924 EXPECT_EQ(0u, entries.size());
927 TEST_F(UkmRecorderPermissionUmaUtilTest,
928 NotificationUsageHistogramDidRecordUkmTest) {
929 base::HistogramTester histograms;
930 content::TestBrowserContext browser_context;
931 ukm::InitializeSourceUrlRecorderForWebContents(web_contents());
932 ukm::TestAutoSetUkmRecorder ukm_recorder;
934 permissions_client_.SetSimulatedHasSourceId(true);
935 PermissionUmaUtil::RecordPermissionUsage(ContentSettingsType::NOTIFICATIONS,
936 &browser_context, web_contents(),
939 histograms.ExpectBucketCount("Permissions.Usage.Notifications.DidRecordUkm",
941 const auto entries = ukm_recorder.GetEntriesByName("PermissionUsage");
942 ASSERT_EQ(1u, entries.size());
943 const auto* entry = entries.back();
944 EXPECT_EQ(*ukm_recorder.GetEntryMetric(entry, "PermissionType"),
945 content_settings_uma_util::ContentSettingTypeToHistogramValue(
946 ContentSettingsType::NOTIFICATIONS));
949 TEST_F(UkmRecorderPermissionUmaUtilTest,
950 NotificationUsageHistogramDroppedUkmTest) {
951 base::HistogramTester histograms;
952 content::TestBrowserContext browser_context;
954 ukm::InitializeSourceUrlRecorderForWebContents(web_contents());
955 ukm::TestAutoSetUkmRecorder ukm_recorder;
957 permissions_client_.SetSimulatedHasSourceId(false);
958 PermissionUmaUtil::RecordPermissionUsage(ContentSettingsType::NOTIFICATIONS,
959 &browser_context, web_contents(),
962 histograms.ExpectBucketCount("Permissions.Usage.Notifications.DidRecordUkm",
964 const auto entries = ukm_recorder.GetEntriesByName("PermissionUsage");
965 ASSERT_EQ(0u, entries.size());
968 } // namespace permissions