blob: e4096511164296749fa9be0eccf4fdd8ab5213f5 [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_uma_util.h"
#include <utility>
#include "base/command_line.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/engagement/site_engagement_service.h"
#include "chrome/browser/permissions/permission_decision_auto_blocker.h"
#include "chrome/browser/permissions/permission_request.h"
#include "chrome/browser/permissions/permission_util.h"
#include "chrome/browser/profiles/profile.h"
#include "components/ukm/content/source_url_recorder.h"
#include "content/public/browser/permission_type.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/origin_util.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "url/gurl.h"
#if defined(OS_ANDROID)
#include "base/android/jni_string.h"
#include "jni/PermissionUmaUtil_jni.h"
#endif
// UMA keys need to be statically initialized so plain function would not
// work. Use macros instead.
#define PERMISSION_ACTION_UMA(secure_origin, permission, permission_secure, \
permission_insecure, action) \
UMA_HISTOGRAM_ENUMERATION(permission, action, PermissionAction::NUM); \
if (secure_origin) { \
UMA_HISTOGRAM_ENUMERATION(permission_secure, action, \
PermissionAction::NUM); \
} else { \
UMA_HISTOGRAM_ENUMERATION(permission_insecure, action, \
PermissionAction::NUM); \
}
#define PERMISSION_BUBBLE_TYPE_UMA(metric_name, permission_bubble_type) \
UMA_HISTOGRAM_ENUMERATION(metric_name, permission_bubble_type, \
PermissionRequestType::NUM)
#define PERMISSION_BUBBLE_GESTURE_TYPE_UMA(gesture_metric_name, \
no_gesture_metric_name, \
gesture_type, \
permission_bubble_type) \
if (gesture_type == PermissionRequestGestureType::GESTURE) { \
PERMISSION_BUBBLE_TYPE_UMA(gesture_metric_name, permission_bubble_type); \
} else if (gesture_type == PermissionRequestGestureType::NO_GESTURE) { \
PERMISSION_BUBBLE_TYPE_UMA(no_gesture_metric_name, \
permission_bubble_type); \
}
using content::PermissionType;
namespace {
const int kPriorCountCap = 10;
std::string GetPermissionRequestString(PermissionRequestType type) {
switch (type) {
case PermissionRequestType::MULTIPLE:
return "AudioAndVideoCapture";
case PermissionRequestType::QUOTA:
return "Quota";
case PermissionRequestType::DOWNLOAD:
return "MultipleDownload";
case PermissionRequestType::REGISTER_PROTOCOL_HANDLER:
return "RegisterProtocolHandler";
case PermissionRequestType::PERMISSION_GEOLOCATION:
return "Geolocation";
case PermissionRequestType::PERMISSION_MIDI_SYSEX:
return "MidiSysEx";
case PermissionRequestType::PERMISSION_NOTIFICATIONS:
return "Notifications";
case PermissionRequestType::PERMISSION_PROTECTED_MEDIA_IDENTIFIER:
return "ProtectedMedia";
case PermissionRequestType::PERMISSION_FLASH:
return "Flash";
case PermissionRequestType::PERMISSION_MEDIASTREAM_MIC:
return "AudioCapture";
case PermissionRequestType::PERMISSION_MEDIASTREAM_CAMERA:
return "VideoCapture";
case PermissionRequestType::PERMISSION_CLIPBOARD_READ:
return "ClipboardRead";
case PermissionRequestType::PERMISSION_SECURITY_KEY_ATTESTATION:
return "SecurityKeyAttestation";
case PermissionRequestType::PERMISSION_PAYMENT_HANDLER:
return "PaymentHandler";
default:
NOTREACHED();
return "";
}
}
void RecordEngagementMetric(const std::vector<PermissionRequest*>& requests,
content::WebContents* web_contents,
const std::string& action) {
PermissionRequestType type = requests[0]->GetPermissionRequestType();
if (requests.size() > 1)
type = PermissionRequestType::MULTIPLE;
DCHECK(action == "Accepted" || action == "Denied" || action == "Dismissed" ||
action == "Ignored");
std::string name = "Permissions.Engagement." + action + '.' +
GetPermissionRequestString(type);
SiteEngagementService* site_engagement_service = SiteEngagementService::Get(
Profile::FromBrowserContext(web_contents->GetBrowserContext()));
double engagement_score =
site_engagement_service->GetScore(requests[0]->GetOrigin());
base::UmaHistogramPercentage(name, engagement_score);
}
} // anonymous namespace
// PermissionUmaUtil ----------------------------------------------------------
const char PermissionUmaUtil::kPermissionsPromptShown[] =
"Permissions.Prompt.Shown";
const char PermissionUmaUtil::kPermissionsPromptShownGesture[] =
"Permissions.Prompt.Shown.Gesture";
const char PermissionUmaUtil::kPermissionsPromptShownNoGesture[] =
"Permissions.Prompt.Shown.NoGesture";
const char PermissionUmaUtil::kPermissionsPromptAccepted[] =
"Permissions.Prompt.Accepted";
const char PermissionUmaUtil::kPermissionsPromptAcceptedGesture[] =
"Permissions.Prompt.Accepted.Gesture";
const char PermissionUmaUtil::kPermissionsPromptAcceptedNoGesture[] =
"Permissions.Prompt.Accepted.NoGesture";
const char PermissionUmaUtil::kPermissionsPromptDenied[] =
"Permissions.Prompt.Denied";
const char PermissionUmaUtil::kPermissionsPromptDeniedGesture[] =
"Permissions.Prompt.Denied.Gesture";
const char PermissionUmaUtil::kPermissionsPromptDeniedNoGesture[] =
"Permissions.Prompt.Denied.NoGesture";
// Make sure you update histograms.xml permission histogram_suffix if you
// add new permission
void PermissionUmaUtil::PermissionRequested(ContentSettingsType content_type,
const GURL& requesting_origin) {
PermissionType permission;
bool success = PermissionUtil::GetPermissionType(content_type, &permission);
DCHECK(success);
bool secure_origin = content::IsOriginSecure(requesting_origin);
UMA_HISTOGRAM_ENUMERATION("ContentSettings.PermissionRequested", permission,
PermissionType::NUM);
if (secure_origin) {
UMA_HISTOGRAM_ENUMERATION(
"ContentSettings.PermissionRequested_SecureOrigin", permission,
PermissionType::NUM);
} else {
UMA_HISTOGRAM_ENUMERATION(
"ContentSettings.PermissionRequested_InsecureOrigin", permission,
PermissionType::NUM);
}
}
void PermissionUmaUtil::PermissionRevoked(ContentSettingsType permission,
PermissionSourceUI source_ui,
const GURL& revoked_origin,
Profile* profile) {
// TODO(tsergeant): Expand metrics definitions for revocation to include all
// permissions.
if (permission == CONTENT_SETTINGS_TYPE_NOTIFICATIONS ||
permission == CONTENT_SETTINGS_TYPE_GEOLOCATION ||
permission == CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC ||
permission == CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA) {
// An unknown gesture type is passed in since gesture type is only
// applicable in prompt UIs where revocations are not possible.
RecordPermissionAction(permission, PermissionAction::REVOKED, source_ui,
PermissionRequestGestureType::UNKNOWN,
revoked_origin, /*web_contents=*/nullptr, profile);
}
}
void PermissionUmaUtil::RecordEmbargoPromptSuppression(
PermissionEmbargoStatus embargo_status) {
UMA_HISTOGRAM_ENUMERATION("Permissions.AutoBlocker.EmbargoPromptSuppression",
embargo_status, PermissionEmbargoStatus::NUM);
}
void PermissionUmaUtil::RecordEmbargoPromptSuppressionFromSource(
PermissionStatusSource source) {
// Explicitly switch to ensure that any new PermissionStatusSource values are
// dealt with appropriately.
switch (source) {
case PermissionStatusSource::MULTIPLE_DISMISSALS:
PermissionUmaUtil::RecordEmbargoPromptSuppression(
PermissionEmbargoStatus::REPEATED_DISMISSALS);
break;
case PermissionStatusSource::MULTIPLE_IGNORES:
PermissionUmaUtil::RecordEmbargoPromptSuppression(
PermissionEmbargoStatus::REPEATED_IGNORES);
break;
case PermissionStatusSource::UNSPECIFIED:
case PermissionStatusSource::KILL_SWITCH:
case PermissionStatusSource::INSECURE_ORIGIN:
case PermissionStatusSource::FEATURE_POLICY:
case PermissionStatusSource::VIRTUAL_URL_DIFFERENT_ORIGIN:
// The permission wasn't under embargo, so don't record anything. We may
// embargo it later.
break;
}
}
void PermissionUmaUtil::RecordEmbargoStatus(
PermissionEmbargoStatus embargo_status) {
UMA_HISTOGRAM_ENUMERATION("Permissions.AutoBlocker.EmbargoStatus",
embargo_status, PermissionEmbargoStatus::NUM);
}
void PermissionUmaUtil::PermissionPromptShown(
const std::vector<PermissionRequest*>& requests) {
DCHECK(!requests.empty());
PermissionRequestType request_type = PermissionRequestType::MULTIPLE;
PermissionRequestGestureType gesture_type =
PermissionRequestGestureType::UNKNOWN;
if (requests.size() == 1) {
request_type = requests[0]->GetPermissionRequestType();
gesture_type = requests[0]->GetGestureType();
}
PERMISSION_BUBBLE_TYPE_UMA(kPermissionsPromptShown, request_type);
PERMISSION_BUBBLE_GESTURE_TYPE_UMA(kPermissionsPromptShownGesture,
kPermissionsPromptShownNoGesture,
gesture_type, request_type);
}
void PermissionUmaUtil::PermissionPromptResolved(
const std::vector<PermissionRequest*>& requests,
content::WebContents* web_contents,
PermissionAction permission_action) {
std::string action_string;
switch (permission_action) {
case PermissionAction::GRANTED:
RecordPromptDecided(requests, /*accepted=*/true);
action_string = "Accepted";
break;
case PermissionAction::DENIED:
RecordPromptDecided(requests, /*accepted=*/false);
action_string = "Denied";
break;
case PermissionAction::DISMISSED:
action_string = "Dismissed";
break;
case PermissionAction::IGNORED:
action_string = "Ignored";
break;
default:
NOTREACHED();
break;
}
RecordEngagementMetric(requests, web_contents, action_string);
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
PermissionDecisionAutoBlocker* autoblocker =
PermissionDecisionAutoBlocker::GetForProfile(profile);
for (PermissionRequest* request : requests) {
ContentSettingsType permission = request->GetContentSettingsType();
// TODO(timloh): We only record these metrics for permissions which use
// PermissionRequestImpl as the other subclasses don't support
// GetGestureType and GetContentSettingsType.
if (permission == CONTENT_SETTINGS_TYPE_DEFAULT)
continue;
PermissionRequestGestureType gesture_type = request->GetGestureType();
const GURL& requesting_origin = request->GetOrigin();
RecordPermissionAction(permission, permission_action,
PermissionSourceUI::PROMPT, gesture_type,
requesting_origin, web_contents, profile);
std::string priorDismissPrefix =
"Permissions.Prompt." + action_string + ".PriorDismissCount.";
std::string priorIgnorePrefix =
"Permissions.Prompt." + action_string + ".PriorIgnoreCount.";
RecordPermissionPromptPriorCount(
permission, priorDismissPrefix,
autoblocker->GetDismissCount(requesting_origin, permission));
RecordPermissionPromptPriorCount(
permission, priorIgnorePrefix,
autoblocker->GetIgnoreCount(requesting_origin, permission));
#if defined(OS_ANDROID)
if (permission == CONTENT_SETTINGS_TYPE_GEOLOCATION &&
permission_action != PermissionAction::IGNORED) {
RecordWithBatteryBucket("Permissions.BatteryLevel." + action_string +
".Geolocation");
}
#endif
}
}
void PermissionUmaUtil::RecordPermissionPromptPriorCount(
ContentSettingsType permission,
const std::string& prefix,
int count) {
// The user is not prompted for this permissions, thus there is no prompt
// event to record a prior count for.
DCHECK_NE(CONTENT_SETTINGS_TYPE_BACKGROUND_SYNC, permission);
// Expand UMA_HISTOGRAM_COUNTS_100 so that we can use a dynamically suffixed
// histogram name.
base::Histogram::FactoryGet(
prefix + PermissionUtil::GetPermissionString(permission), 1, 100, 50,
base::HistogramBase::kUmaTargetedHistogramFlag)
->Add(count);
}
#if defined(OS_ANDROID)
void PermissionUmaUtil::RecordWithBatteryBucket(const std::string& histogram) {
JNIEnv* env = base::android::AttachCurrentThread();
Java_PermissionUmaUtil_recordWithBatteryBucket(
env, base::android::ConvertUTF8ToJavaString(env, histogram));
}
#endif
void PermissionUmaUtil::RecordPermissionAction(
ContentSettingsType permission,
PermissionAction action,
PermissionSourceUI source_ui,
PermissionRequestGestureType gesture_type,
const GURL& requesting_origin,
const content::WebContents* web_contents,
Profile* profile) {
PermissionDecisionAutoBlocker* autoblocker =
PermissionDecisionAutoBlocker::GetForProfile(profile);
int dismiss_count =
autoblocker->GetDismissCount(requesting_origin, permission);
int ignore_count = autoblocker->GetIgnoreCount(requesting_origin, permission);
if (web_contents) {
ukm::SourceId source_id =
ukm::GetSourceIdForWebContentsDocument(web_contents);
ukm::builders::Permission(source_id)
.SetAction(static_cast<int64_t>(action))
.SetGesture(static_cast<int64_t>(gesture_type))
.SetPermissionType(permission)
.SetPriorDismissals(std::min(kPriorCountCap, dismiss_count))
.SetPriorIgnores(std::min(kPriorCountCap, ignore_count))
.SetSource(static_cast<int64_t>(source_ui))
.Record(ukm::UkmRecorder::Get());
}
bool secure_origin = content::IsOriginSecure(requesting_origin);
switch (permission) {
// Geolocation, MidiSysEx, Push, Media and Clipboard permissions are
// disabled on insecure origins, so there's no need to record separate
// metrics for secure/insecure.
case CONTENT_SETTINGS_TYPE_GEOLOCATION:
UMA_HISTOGRAM_ENUMERATION("Permissions.Action.Geolocation", action,
PermissionAction::NUM);
break;
case CONTENT_SETTINGS_TYPE_NOTIFICATIONS:
PERMISSION_ACTION_UMA(secure_origin, "Permissions.Action.Notifications",
"Permissions.Action.SecureOrigin.Notifications",
"Permissions.Action.InsecureOrigin.Notifications",
action);
break;
case CONTENT_SETTINGS_TYPE_MIDI_SYSEX:
UMA_HISTOGRAM_ENUMERATION("Permissions.Action.MidiSysEx", action,
PermissionAction::NUM);
break;
case CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER:
PERMISSION_ACTION_UMA(secure_origin, "Permissions.Action.ProtectedMedia",
"Permissions.Action.SecureOrigin.ProtectedMedia",
"Permissions.Action.InsecureOrigin.ProtectedMedia",
action);
break;
case CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC:
UMA_HISTOGRAM_ENUMERATION("Permissions.Action.AudioCapture", action,
PermissionAction::NUM);
break;
case CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA:
UMA_HISTOGRAM_ENUMERATION("Permissions.Action.VideoCapture", action,
PermissionAction::NUM);
break;
case CONTENT_SETTINGS_TYPE_PLUGINS:
PERMISSION_ACTION_UMA(secure_origin, "Permissions.Action.Flash",
"Permissions.Action.SecureOrigin.Flash",
"Permissions.Action.InsecureOrigin.Flash", action);
break;
case CONTENT_SETTINGS_TYPE_CLIPBOARD_READ:
UMA_HISTOGRAM_ENUMERATION("Permissions.Action.ClipboardRead", action,
PermissionAction::NUM);
break;
case CONTENT_SETTINGS_TYPE_PAYMENT_HANDLER:
UMA_HISTOGRAM_ENUMERATION("Permissions.Action.PaymentHandler", action,
PermissionAction::NUM);
break;
// The user is not prompted for these permissions, thus there is no
// permission action recorded for them.
default:
NOTREACHED() << "PERMISSION "
<< PermissionUtil::GetPermissionString(permission)
<< " not accounted for";
}
}
// static
void PermissionUmaUtil::RecordPromptDecided(
const std::vector<PermissionRequest*>& requests,
bool accepted) {
DCHECK(!requests.empty());
PermissionRequestType request_type = PermissionRequestType::MULTIPLE;
PermissionRequestGestureType gesture_type =
PermissionRequestGestureType::UNKNOWN;
if (requests.size() == 1) {
request_type = requests[0]->GetPermissionRequestType();
gesture_type = requests[0]->GetGestureType();
}
if (accepted) {
PERMISSION_BUBBLE_TYPE_UMA(kPermissionsPromptAccepted, request_type);
PERMISSION_BUBBLE_GESTURE_TYPE_UMA(kPermissionsPromptAcceptedGesture,
kPermissionsPromptAcceptedNoGesture,
gesture_type, request_type);
} else {
PERMISSION_BUBBLE_TYPE_UMA(kPermissionsPromptDenied, request_type);
PERMISSION_BUBBLE_GESTURE_TYPE_UMA(kPermissionsPromptDeniedGesture,
kPermissionsPromptDeniedNoGesture,
gesture_type, request_type);
}
}