Commit 4a88f427 authored by Andy Paicu's avatar Andy Paicu Committed by Commit Bot

Bare-bones Permission Predictions API client.

Implements a bare-bones version that mostly reuses code from
RealTimeUrlLookupServiceBase without doing the work to derive from a
base class. This will follow-up in a different CL. For this reason some
of the callbacks have unused parameters, to make implementing a base
common class less painful.

Design doc: go/permission-predictions-client-doc

Bug: 1138595
Change-Id: Idc26e836fba565cf42f796ee3a5b20bc8d61f53f
No-Try: true
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2513656
Commit-Queue: Andy Paicu <andypaicu@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarRyan Sleevi <rsleevi@chromium.org>
Reviewed-by: default avatarBalazs Engedy <engedy@chromium.org>
Reviewed-by: default avatarChristian Dullweber <dullweber@chromium.org>
Reviewed-by: default avatarRavjit Singh Uppal <ravjit@chromium.org>
Cr-Commit-Position: refs/heads/master@{#826868}
parent 97c1e462
......@@ -277,6 +277,7 @@ test("components_unittests") {
"//components/payments/content/utility:unit_tests",
"//components/performance_manager:unit_tests",
"//components/permissions:unit_tests",
"//components/permissions/prediction_service:unit_tests",
"//components/policy/content:unit_tests",
"//components/previews/content:unit_tests",
"//components/query_tiles:unit_tests",
......
......@@ -2,6 +2,10 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
source_set("permissions_common") {
sources = [ "permission_request_enums.h" ]
}
source_set("permissions") {
sources = [
"chooser_context_base.cc",
......@@ -52,6 +56,7 @@ source_set("permissions") {
"//components/content_settings/browser",
"//components/content_settings/core/browser",
"//components/keyed_service/content",
"//components/permissions/prediction_service",
"//components/strings:components_strings_grit",
"//components/ukm/content",
"//components/url_formatter",
......@@ -86,6 +91,7 @@ source_set("permissions") {
} else {
sources += [ "permission_prompt_impl.cc" ]
}
public_deps = [ ":permissions_common" ]
}
source_set("test_support") {
......
......@@ -10,6 +10,7 @@
#include "base/strings/string16.h"
#include "build/build_config.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/permissions/permission_request_enums.h"
#include "url/gurl.h"
namespace gfx {
......@@ -18,62 +19,6 @@ struct VectorIcon;
namespace permissions {
// Used for UMA to record the types of permission prompts shown.
// When updating, you also need to update:
// 1) The PermissionRequestType enum in tools/metrics/histograms/enums.xml.
// 2) The PermissionRequestTypes suffix list in
// tools/metrics/histograms/histograms.xml.
// 3) GetPermissionRequestString in
// chrome/browser/permissions/permission_uma_util.cc.
//
// The usual rules of updating UMA values applies to this enum:
// - don't remove values
// - only ever add values at the end
enum class PermissionRequestType {
UNKNOWN = 0,
MULTIPLE = 1,
// UNUSED_PERMISSION = 2,
QUOTA = 3,
DOWNLOAD = 4,
// MEDIA_STREAM = 5,
REGISTER_PROTOCOL_HANDLER = 6,
PERMISSION_GEOLOCATION = 7,
PERMISSION_MIDI_SYSEX = 8,
PERMISSION_NOTIFICATIONS = 9,
PERMISSION_PROTECTED_MEDIA_IDENTIFIER = 10,
// PERMISSION_PUSH_MESSAGING = 11,
PERMISSION_FLASH = 12,
PERMISSION_MEDIASTREAM_MIC = 13,
PERMISSION_MEDIASTREAM_CAMERA = 14,
PERMISSION_ACCESSIBILITY_EVENTS = 15,
// PERMISSION_CLIPBOARD_READ = 16, // Replaced by
// PERMISSION_CLIPBOARD_READ_WRITE in M81.
PERMISSION_SECURITY_KEY_ATTESTATION = 17,
PERMISSION_PAYMENT_HANDLER = 18,
PERMISSION_NFC = 19,
PERMISSION_CLIPBOARD_READ_WRITE = 20,
PERMISSION_VR = 21,
PERMISSION_AR = 22,
PERMISSION_STORAGE_ACCESS = 23,
PERMISSION_CAMERA_PAN_TILT_ZOOM = 24,
PERMISSION_WINDOW_PLACEMENT = 25,
PERMISSION_FONT_ACCESS = 26,
PERMISSION_IDLE_DETECTION = 27,
// NUM must be the last value in the enum.
NUM
};
// Used for UMA to record whether a gesture was associated with the request. For
// simplicity not all request types track whether a gesture is associated with
// it or not, for these types of requests metrics are not recorded.
enum class PermissionRequestGestureType {
UNKNOWN,
GESTURE,
NO_GESTURE,
// NUM must be the last value in the enum.
NUM
};
// Describes the interface a feature making permission requests should
// implement. A class of this type is registered with the permission request
// manager to receive updates about the result of the permissions request
......
// Copyright 2020 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.
#ifndef COMPONENTS_PERMISSIONS_PERMISSION_REQUEST_ENUMS_H_
#define COMPONENTS_PERMISSIONS_PERMISSION_REQUEST_ENUMS_H_
namespace permissions {
// Used for UMA to record the types of permission prompts shown.
// When updating, you also need to update:
// 1) The PermissionRequestType enum in tools/metrics/histograms/enums.xml.
// 2) The PermissionRequestTypes suffix list in
// tools/metrics/histograms/histograms.xml.
// 3) GetPermissionRequestString in
// chrome/browser/permissions/permission_uma_util.cc.
//
// The usual rules of updating UMA values applies to this enum:
// - don't remove values
// - only ever add values at the end
enum class PermissionRequestType {
UNKNOWN = 0,
MULTIPLE = 1,
// UNUSED_PERMISSION = 2,
QUOTA = 3,
DOWNLOAD = 4,
// MEDIA_STREAM = 5,
REGISTER_PROTOCOL_HANDLER = 6,
PERMISSION_GEOLOCATION = 7,
PERMISSION_MIDI_SYSEX = 8,
PERMISSION_NOTIFICATIONS = 9,
PERMISSION_PROTECTED_MEDIA_IDENTIFIER = 10,
// PERMISSION_PUSH_MESSAGING = 11,
PERMISSION_FLASH = 12,
PERMISSION_MEDIASTREAM_MIC = 13,
PERMISSION_MEDIASTREAM_CAMERA = 14,
PERMISSION_ACCESSIBILITY_EVENTS = 15,
// PERMISSION_CLIPBOARD_READ = 16, // Replaced by
// PERMISSION_CLIPBOARD_READ_WRITE in M81.
PERMISSION_SECURITY_KEY_ATTESTATION = 17,
PERMISSION_PAYMENT_HANDLER = 18,
PERMISSION_NFC = 19,
PERMISSION_CLIPBOARD_READ_WRITE = 20,
PERMISSION_VR = 21,
PERMISSION_AR = 22,
PERMISSION_STORAGE_ACCESS = 23,
PERMISSION_CAMERA_PAN_TILT_ZOOM = 24,
PERMISSION_WINDOW_PLACEMENT = 25,
PERMISSION_FONT_ACCESS = 26,
PERMISSION_IDLE_DETECTION = 27,
// NUM must be the last value in the enum.
NUM
};
// Used for UMA to record whether a gesture was associated with the request. For
// simplicity not all request types track whether a gesture is associated with
// it or not, for these types of requests metrics are not recorded.
enum class PermissionRequestGestureType {
UNKNOWN,
GESTURE,
NO_GESTURE,
// NUM must be the last value in the enum.
NUM
};
} // namespace permissions
#endif // COMPONENTS_PERMISSIONS_PERMISSION_REQUEST_ENUMS_H_
# Copyright 2020 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.
import("//third_party/protobuf/proto_library.gni")
proto_library("prediction_service_messages_proto") {
sources = [ "prediction_service_messages.proto" ]
}
source_set("prediction_service") {
sources = [
"prediction_request_features.h",
"prediction_service.cc",
"prediction_service.h",
"prediction_service_base.h",
"prediction_service_common.cc",
"prediction_service_common.h",
]
deps = [
":prediction_service_messages_proto",
"//components/keyed_service/content",
"//components/permissions:permissions_common",
"//services/network/public/cpp:cpp",
"//third_party/protobuf:protobuf_lite",
]
}
source_set("unit_tests") {
testonly = true
sources = [ "prediction_service_unittest.cc" ]
deps = [
":prediction_service",
":prediction_service_messages_proto",
"//base/test:test_support",
"//components/permissions:permissions_common",
"//services/network:test_support",
"//services/network/public/cpp:cpp",
"//testing/gtest",
"//third_party/protobuf:protobuf_lite",
"//ui/gfx",
]
}
include_rules = [
"+google/protobuf",
"+net",
"+services/network/public",
"+services/network/test",
"+third_party/googletest",
]
// Copyright 2020 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.
#ifndef COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_REQUEST_FEATURES_H_
#define COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_REQUEST_FEATURES_H_
#include "components/permissions/permission_request_enums.h"
namespace permissions {
struct PredictionRequestFeatures {
struct ActionCounts {
unsigned int grants;
unsigned int denies;
unsigned int dismissals;
unsigned int ignores;
};
// Whether a gesture is present or not.
PermissionRequestGestureType gesture;
// Which permissions request type this is for.
PermissionRequestType type;
// The permission action counts for this specific permission type.
ActionCounts requested_permission_counts;
// The permission action counts for all permissions type.
ActionCounts all_permission_counts;
};
} // namespace permissions
#endif // COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_REQUEST_FEATURES_H_
// Copyright 2020 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/prediction_service/prediction_service.h"
#include <cmath>
#include <memory>
#include <utility>
#include "base/command_line.h"
#include "base/memory/scoped_refptr.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "components/permissions/prediction_service/prediction_request_features.h"
#include "components/permissions/prediction_service/prediction_service_common.h"
#include "components/permissions/prediction_service/prediction_service_messages.pb.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/http/http_status_code.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/fetch_api.mojom-shared.h"
namespace {
constexpr base::TimeDelta kURLLookupTimeout = base::TimeDelta::FromSeconds(2);
permissions::ClientFeatures_Gesture ConvertToProtoGesture(
const permissions::PermissionRequestGestureType type) {
switch (type) {
case permissions::PermissionRequestGestureType::GESTURE:
return permissions::ClientFeatures_Gesture_GESTURE;
case permissions::PermissionRequestGestureType::NO_GESTURE:
return permissions::ClientFeatures_Gesture_NO_GESTURE;
case permissions::PermissionRequestGestureType::UNKNOWN:
return permissions::ClientFeatures_Gesture_UNKNOWN_GESTURE;
case permissions::PermissionRequestGestureType::NUM:
break;
}
NOTREACHED();
return permissions::ClientFeatures_Gesture_UNKNOWN_GESTURE;
}
inline float GetRatioRoundedToTwoDecimals(int numerator, int denominator) {
if (denominator == 0)
return 0;
return roundf(100.f * numerator / denominator) / 100.f;
}
void FillInStatsFeatures(
const permissions::PredictionRequestFeatures::ActionCounts& counts,
permissions::StatsFeatures* features) {
int total_counts =
counts.denies + counts.dismissals + counts.grants + counts.ignores;
// Round to only 2 decimal places to help prevent fingerprinting.
features->set_avg_deny_rate(
GetRatioRoundedToTwoDecimals(counts.denies, total_counts));
features->set_avg_dismiss_rate(
GetRatioRoundedToTwoDecimals(counts.dismissals, total_counts));
features->set_avg_grant_rate(
GetRatioRoundedToTwoDecimals(counts.grants, total_counts));
features->set_avg_ignore_rate(
GetRatioRoundedToTwoDecimals(counts.ignores, total_counts));
// Prevent hyperspecific large counts from becoming usable to fingerprint
// users that see an unexpectedly large prompt count.
features->set_prompts_count(std::min(total_counts, 100));
}
net::NetworkTrafficAnnotationTag GetTrafficAnnotationTag() {
return net::DefineNetworkTrafficAnnotation("permission_predictions", R"(
semantics {
sender: "Web Permission Perdictions"
description:
"A request to the Web Permission Predictions Service. The service will "
"attempt to predict the likelihood that the user would grant this "
"permission. Based on this prediction Chrome might decide to present "
"the user with a different UI; a less intrusive one."
trigger:
"A permission prompt is about to be shown to the user, and the user "
"has opted into Safe Browsing's Enhanced Protection."
data:
"User stats helpful for attempting to predict the user's likelihood "
"of granting the permission: the permission type, the presence of a "
"user gesture, the user's OS, average deny/grant/ignore/dismiss rates "
"and total prompts shown, both for the specific permission type and "
"overall for all permission types."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting:
"This can be disabled by disabling Enhanced Protection by going to "
"Settings and then to the Security sub-menu."
chrome_policy {
SafeBrowsingProtectionLevel {
SafeBrowsingProtectionLevel: 1
}
}
})");
}
} // namespace
namespace permissions {
PredictionService::PredictionService(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: url_loader_factory_(url_loader_factory) {}
PredictionService::~PredictionService() = default;
void PredictionService::StartLookup(const PredictionRequestFeatures& entity,
LookupRequestCallback request_callback,
LookupResponseCallback response_callback) {
auto request = GetResourceRequest();
auto proto_request = GetPredictionRequestProto(entity);
std::string request_data;
proto_request->SerializeToString(&request_data);
SendRequestInternal(std::move(request), request_data, entity,
std::move(response_callback));
if (request_callback)
std::move(request_callback).Run(std::move(proto_request), std::string());
}
// static
const GURL PredictionService::GetPredictionServiceUrl() {
static base::NoDestructor<GURL> default_prediction_service_url{
kDefaultPredictionServiceUrl};
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
kDefaultPredictionServiceUrlSwitchKey)) {
GURL command_line_url(
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
kDefaultPredictionServiceUrlSwitchKey));
if (command_line_url.is_valid())
return command_line_url;
}
return *default_prediction_service_url;
}
std::unique_ptr<network::ResourceRequest>
PredictionService::GetResourceRequest() {
auto request = std::make_unique<network::ResourceRequest>();
request->url = prediction_service_url_override_.is_empty()
? GetPredictionServiceUrl()
: prediction_service_url_override_;
request->load_flags = net::LOAD_DISABLE_CACHE;
request->method = "POST";
request->credentials_mode = network::mojom::CredentialsMode::kOmit;
return request;
}
std::unique_ptr<GetSuggestionsRequest>
PredictionService::GetPredictionRequestProto(
const PredictionRequestFeatures& entity) {
auto proto_request = std::make_unique<GetSuggestionsRequest>();
ClientFeatures* client_features = proto_request->mutable_client_features();
client_features->set_platform(GetCurrentPlatformProto());
client_features->set_gesture(ConvertToProtoGesture(entity.gesture));
FillInStatsFeatures(entity.all_permission_counts,
client_features->mutable_client_stats());
PermissionFeatures* permission_features =
proto_request->mutable_permission_features()->Add();
FillInStatsFeatures(entity.requested_permission_counts,
permission_features->mutable_permission_stats());
switch (entity.type) {
case PermissionRequestType::PERMISSION_NOTIFICATIONS:
permission_features->mutable_notification_permission()
->InitAsDefaultInstance();
break;
default:
NOTREACHED() << "CPSS only supports notifications at the moment.";
}
return proto_request;
}
void PredictionService::SendRequestInternal(
std::unique_ptr<network::ResourceRequest> request,
const std::string& request_data,
const PredictionRequestFeatures& entity,
LookupResponseCallback response_callback) {
std::unique_ptr<network::SimpleURLLoader> owned_loader =
network::SimpleURLLoader::Create(std::move(request),
GetTrafficAnnotationTag());
owned_loader->AttachStringForUpload(request_data, "application/x-protobuf");
owned_loader->SetTimeoutDuration(kURLLookupTimeout);
owned_loader->DownloadToString(
url_loader_factory_.get(),
base::BindOnce(&PredictionService::OnURLLoaderComplete,
weak_factory_.GetWeakPtr(), entity, owned_loader.get(),
base::TimeTicks::Now()),
network::SimpleURLLoader::kMaxBoundedStringDownloadSize);
pending_requests_[std::move(owned_loader)] = std::move(response_callback);
}
void PredictionService::OnURLLoaderComplete(
const PredictionRequestFeatures& entity,
network::SimpleURLLoader* loader,
base::TimeTicks request_start_time,
std::unique_ptr<std::string> response_body) {
for (auto& request : pending_requests_) {
if (request.first.get() == loader) {
auto prediction_response =
CreatePredictionsResponse(loader, response_body.get());
if (request.second) {
std::move(request.second)
.Run(prediction_response != nullptr /* Lookup successful */,
false /* Response from cache */,
std::move(prediction_response));
}
pending_requests_.erase(request.first);
return;
}
}
NOTREACHED() << "Unexpected loader callback.";
}
std::unique_ptr<GetSuggestionsResponse>
PredictionService::CreatePredictionsResponse(network::SimpleURLLoader* loader,
const std::string* response_body) {
if (!response_body || loader->NetError() != net::OK ||
loader->ResponseInfo()->headers->response_code() != net::HTTP_OK) {
return nullptr;
}
auto predictions_response = std::make_unique<GetSuggestionsResponse>();
if (predictions_response->ParseFromString(*response_body)) {
return predictions_response;
}
return nullptr;
}
} // namespace permissions
// Copyright 2020 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.
#ifndef COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_SERVICE_H_
#define COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_SERVICE_H_
#include <memory>
#include <string>
#include "base/callback.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/permissions/permission_request_enums.h"
#include "components/permissions/prediction_service/prediction_request_features.h"
#include "components/permissions/prediction_service/prediction_service_base.h"
#include "components/permissions/prediction_service/prediction_service_messages.pb.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
namespace permissions {
// TODO(crbug.com/1138595, andypaicu): Refactor this class and
// RealTimeUrlLookupServiceBase to derive from the same base class instead of
// doing a bunch of duplicate work. Design doc:
// go/permissions-predictions-client-doc
// Service used to makes calls to the Web Permission Suggestions Service to
// obtaing recomandations regarding permission prompts.
class PredictionService : public PredictionServiceBase {
public:
using PendingRequestsMap = std::map<std::unique_ptr<network::SimpleURLLoader>,
LookupResponseCallback>;
explicit PredictionService(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
~PredictionService() override;
void StartLookup(const PredictionRequestFeatures& entity,
LookupRequestCallback request_callback,
LookupResponseCallback response_callback) override;
void set_prediction_service_url_for_testing(const GURL& url) {
prediction_service_url_override_ = url;
}
const PendingRequestsMap& pending_requests_for_testing() {
return pending_requests_;
}
private:
static const GURL GetPredictionServiceUrl();
std::unique_ptr<network::ResourceRequest> GetResourceRequest();
std::unique_ptr<GetSuggestionsRequest> GetPredictionRequestProto(
const PredictionRequestFeatures& entity);
void SendRequestInternal(std::unique_ptr<network::ResourceRequest> request,
const std::string& request_data,
const PredictionRequestFeatures& entity,
LookupResponseCallback response_callback);
void OnURLLoaderComplete(const PredictionRequestFeatures& entity,
network::SimpleURLLoader* loader,
base::TimeTicks request_start_time,
std::unique_ptr<std::string> response_body);
std::unique_ptr<GetSuggestionsResponse> CreatePredictionsResponse(
network::SimpleURLLoader* loader,
const std::string* response_body);
PendingRequestsMap pending_requests_;
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
GURL prediction_service_url_override_;
base::WeakPtrFactory<PredictionService> weak_factory_{this};
};
} // namespace permissions
#endif // COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_SERVICE_H_
// Copyright 2020 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.
#ifndef COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_SERVICE_BASE_H_
#define COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_SERVICE_BASE_H_
#include <memory>
#include <string>
#include "base/callback.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/permissions/permission_request_enums.h"
#include "components/permissions/prediction_service/prediction_request_features.h"
#include "components/permissions/prediction_service/prediction_service_messages.pb.h"
namespace permissions {
// TODO(crbug.com/1138595, andypaicu): Refactor this class and
// RealTimeUrlLookupServiceBase to derive from the same base class instead of
// doing a bunch of duplicate work. Design doc:
// https://docs.google.com/document/d/11Gd4bMpuPiVOVNhgqkixZXfckFDzv921BHoZWTBIISc/edit#heading=h.lxxeltml3hwr
class PredictionServiceBase : public KeyedService {
public:
// TODO(crbug.com/1138595, andypaicu): once the above TODO is done, refactor
// to use a struct to make the call sites more readable (for both callbacks).
using LookupRequestCallback =
base::OnceCallback<void(std::unique_ptr<GetSuggestionsRequest>,
std::string)>; // Access token.
using LookupResponseCallback =
base::OnceCallback<void(bool, // Lookup successful.
bool, // Response from cache.
std::unique_ptr<GetSuggestionsResponse>)>;
virtual void StartLookup(const PredictionRequestFeatures& entity,
LookupRequestCallback request_callback,
LookupResponseCallback response_callback) = 0;
};
} // namespace permissions
#endif // COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_SERVICE_BASE_H_
// Copyright 2020 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/prediction_service/prediction_service_common.h"
#include "build/build_config.h"
namespace permissions {
ClientFeatures_Platform GetCurrentPlatformProto() {
#if defined(OS_WIN)
return permissions::ClientFeatures_Platform_PLATFORM_WINDOWS;
#elif defined(OS_LINUX)
return permissions::ClientFeatures_Platform_PLATFORM_LINUX;
#elif defined(OS_ANDROID)
return permissions::ClientFeatures_Platform_PLATFORM_ANDROID;
#elif defined(OS_MAC)
return permissions::ClientFeatures_Platform_PLATFORM_MAC_OS;
#else
return permissions::ClientFeatures_Platform_PLATFORM_UNKNOWN;
#endif
}
} // namespace permissions
// Copyright 2020 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.
#ifndef COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_SERVICE_COMMON_H_
#define COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_SERVICE_COMMON_H_
#include "components/permissions/prediction_service/prediction_service_messages.pb.h"
namespace permissions {
// TODO(andypaicu): when available, replace with actual URL.
constexpr char kDefaultPredictionServiceUrl[] =
"https://webpermissionpredictions.googleapis.com/v1/GetPredictions";
// A command line witch used to override the default service url.
constexpr char kDefaultPredictionServiceUrlSwitchKey[] =
"permission-predictions-service-url";
// Get the current platform for proto message purposes.
ClientFeatures_Platform GetCurrentPlatformProto();
} // namespace permissions
#endif // COMPONENTS_PERMISSIONS_PREDICTION_SERVICE_PREDICTION_SERVICE_COMMON_H_
// Copyright (c) 2020 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.
syntax = "proto2";
option optimize_for = LITE_RUNTIME;
package permissions;
// Features that depend on the site that triggered the permission request.
// Currently empty but added proactively for future versions.
message SiteFeatures {}
// Statistical features about client's interactions with permission prompts.
// These features are computed on all permission actions that happened before
// the current permission request.
message StatsFeatures {
// Average deny rate for the client.
optional float avg_deny_rate = 1;
// Average grant rate for the client.
optional float avg_grant_rate = 2;
// Average dismiss rate for the client.
optional float avg_dismiss_rate = 3;
// Average ignore rate for the client.
optional float avg_ignore_rate = 4;
// Number of permission prompts seen by the client.
optional int32 prompts_count = 5;
}
// Features representing the overall (not permission-specific) client state at
// the time the permission was requested.
message ClientFeatures {
// Statistical features about client's previous interactions with permission
// prompts, aggregated across all permission types.
optional StatsFeatures client_stats = 1;
// Enum defining the client platforms.
enum Platform {
PLATFORM_UNKNOWN = 0;
PLATFORM_ANDROID = 1;
PLATFORM_WINDOWS = 2;
PLATFORM_LINUX = 3;
PLATFORM_MAC_OS = 4;
}
// The platform run by the client that originated the suggestion request.
optional Platform platform = 2;
// Enum defining gesture types.
enum Gesture {
UNKNOWN_GESTURE = 0;
NO_GESTURE = 1;
GESTURE = 2;
}
// The type of gesture performed by the user on the page before the permission
// prompt was shown.
optional Gesture gesture = 3;
}
// Features related to a specific permission type.
message PermissionFeatures {
// Statistical features about client's previous interactions with permission
// prompts of the specific permission type.
optional StatsFeatures permission_stats = 1;
// Features related to the notification permission.
message NotificationPermission {}
// This field has two purposes:
// * it specifies the permission type
// * it contains the possible additional features for the specified type.
oneof permission_type { NotificationPermission notification_permission = 2; }
}
// Permission suggestion with the predicted likelihood that the user will grant
// the permission prompt (more details at go/hedgehog-backend).
message PermissionSuggestion {
// Additional information regarding the notification suggestion.
message NotificationSuggestion {}
// This field has two purposes:
// * it specifies the permission type for which we generated the suggestion
// * it contains the possible additional information for the specified type.
oneof suggestion_type { NotificationSuggestion notification_suggestion = 1; }
// Information about how likely a user is to perform a specific action.
message Likelihood {
// Discretized likelihood values (see go/hedgehog-provider-browser). The ML
// models generate predictions as floats in the range [0, 1]; the service
// maps these floats to the discretized likelihood values in this enum using
// thresholds that are defined in the implementation.
enum DiscretizedLikelihood {
UNKNOWN = 0;
VERY_UNLIKELY = 1;
UNLIKELY = 2;
NEUTRAL = 3;
LIKELY = 4;
VERY_LIKELY = 5;
}
// Discretized likelihood of the user performing the action.
optional DiscretizedLikelihood discretized_likelihood = 1;
}
// The ML predicts the likelihood of the user NOT granting the permission. We
// then convert it to how likely the user is to GRANT the permission request.
optional Likelihood grant_likelihood = 2;
}
// Message sent from the client to get suggestions for one or more permissions.
message GetSuggestionsRequest {
// Features representing the overall (not permission-specific) client state.
optional ClientFeatures client_features = 1;
// Features that depend on the site that the client was visiting when the
// permission request was triggered.
optional SiteFeatures site_features = 2;
// Each PermissionFeatures message details a specific permission for which the
// client wants to receive a suggestion.
repeated PermissionFeatures permission_features = 3;
}
// The response message returned by the ChromePermissionsSuggestionsService to
// the Chrome client.
message GetSuggestionsResponse {
// One PermissionSuggestion is generated for each PermissionFeatures in the
// request. The order is kept between the input and output lists.
repeated PermissionSuggestion suggestion = 1;
}
// Copyright 2020 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/prediction_service/prediction_service.h"
#include <memory>
#include <vector>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/optional.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "components/permissions/permission_request_enums.h"
#include "components/permissions/prediction_service/prediction_request_features.h"
#include "components/permissions/prediction_service/prediction_service_common.h"
#include "components/permissions/prediction_service/prediction_service_messages.pb.h"
#include "google/protobuf/message_lite.h"
#include "net/base/net_errors.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "services/network/test/test_url_loader_factory.h"
#include "third_party/googletest/src/googletest/include/gtest/gtest.h"
namespace {
// Helper common requests and responses. All of these are for the NOTIFICATION
// type.
// A request that has all counts 0. With user gesture.
const permissions::PredictionRequestFeatures kFeaturesAllCountsZero = {
permissions::PermissionRequestGestureType::GESTURE,
permissions::PermissionRequestType::PERMISSION_NOTIFICATIONS,
{0, 0, 0, 0},
{0, 0, 0, 0}};
// A request that has all counts 5 expect for "grants" which are 6. Without user
// gesture.
const permissions::PredictionRequestFeatures kFeaturesCountsNeedingRounding = {
permissions::PermissionRequestGestureType::NO_GESTURE,
permissions::PermissionRequestType::PERMISSION_NOTIFICATIONS,
{6, 5, 5, 5},
{6, 5, 5, 5}};
// A request that has all counts 50. With user gesture.
const permissions::PredictionRequestFeatures kFeaturesEvenCountsOver100 = {
permissions::PermissionRequestGestureType::GESTURE,
permissions::PermissionRequestType::PERMISSION_NOTIFICATIONS,
{50, 50, 50, 50},
{50, 50, 50, 50}};
// A request that has all counts 100. With user gesture.
const permissions::PredictionRequestFeatures kFeaturesEvenCountsOver100Alt = {
permissions::PermissionRequestGestureType::GESTURE,
permissions::PermissionRequestType::PERMISSION_NOTIFICATIONS,
{100, 100, 100, 100},
{100, 100, 100, 100}};
// A request that has generic counts 50, and notification counts 0. Without user
// gesture.
const permissions::PredictionRequestFeatures kFeaturesDifferentCounts = {
permissions::PermissionRequestGestureType::NO_GESTURE,
permissions::PermissionRequestType::PERMISSION_NOTIFICATIONS,
{0, 0, 0, 0},
{50, 50, 50, 50}};
// A proto request that has all ratios and total counts 0. With user gesture.
permissions::GetSuggestionsRequest kRequestAllCountsZero;
// A proto request that has all ratios 0.24 (~5/21) except for "grants" which
// are 0.29 (~6/21). Without user gesture.
permissions::GetSuggestionsRequest kRequestRoundedCounts;
// A proto request that has all ratios .25 and total count 100. With user
// gesture.
permissions::GetSuggestionsRequest kRequestEqualCountsTotal100;
// A proot request that has generic ratios .25 and total count 100 and
// notifications ratios and counts 0. Without user gesture.
permissions::GetSuggestionsRequest kRequestDifferentCounts;
// A response that has a likelihood of DiscretizedLikelihood::LIKELY.
permissions::GetSuggestionsResponse kResponseLikely;
// A response that has a likelihood of DiscretizedLikelihood::UNLIKELY.
permissions::GetSuggestionsResponse kResponseUnlikely;
void InitializeProtoHelperObjects() {
kRequestAllCountsZero.mutable_client_features()
->mutable_client_stats()
->set_avg_deny_rate(0);
kRequestAllCountsZero.mutable_client_features()
->mutable_client_stats()
->set_avg_dismiss_rate(0);
kRequestAllCountsZero.mutable_client_features()
->mutable_client_stats()
->set_avg_grant_rate(0);
kRequestAllCountsZero.mutable_client_features()
->mutable_client_stats()
->set_avg_ignore_rate(0);
kRequestAllCountsZero.mutable_client_features()
->mutable_client_stats()
->set_prompts_count(0);
kRequestAllCountsZero.mutable_client_features()->set_platform(
permissions::GetCurrentPlatformProto());
kRequestAllCountsZero.mutable_client_features()->set_gesture(
permissions::ClientFeatures_Gesture_GESTURE);
auto* permission_feature =
kRequestAllCountsZero.mutable_permission_features()->Add();
permission_feature->mutable_permission_stats()->set_avg_deny_rate(0);
permission_feature->mutable_permission_stats()->set_avg_dismiss_rate(0);
permission_feature->mutable_permission_stats()->set_avg_grant_rate(0);
permission_feature->mutable_permission_stats()->set_avg_ignore_rate(0);
permission_feature->mutable_permission_stats()->set_prompts_count(0);
permission_feature->mutable_notification_permission()
->InitAsDefaultInstance();
kRequestRoundedCounts.mutable_client_features()
->mutable_client_stats()
->set_avg_deny_rate(0.24);
kRequestRoundedCounts.mutable_client_features()
->mutable_client_stats()
->set_avg_dismiss_rate(0.24);
kRequestRoundedCounts.mutable_client_features()
->mutable_client_stats()
->set_avg_grant_rate(0.29);
kRequestRoundedCounts.mutable_client_features()
->mutable_client_stats()
->set_avg_ignore_rate(0.24);
kRequestRoundedCounts.mutable_client_features()
->mutable_client_stats()
->set_prompts_count(21);
kRequestRoundedCounts.mutable_client_features()->set_platform(
permissions::GetCurrentPlatformProto());
kRequestRoundedCounts.mutable_client_features()->set_gesture(
permissions::ClientFeatures_Gesture_NO_GESTURE);
permission_feature =
kRequestRoundedCounts.mutable_permission_features()->Add();
permission_feature->mutable_permission_stats()->set_avg_deny_rate(0.24);
permission_feature->mutable_permission_stats()->set_avg_dismiss_rate(0.24);
permission_feature->mutable_permission_stats()->set_avg_grant_rate(0.29);
permission_feature->mutable_permission_stats()->set_avg_ignore_rate(0.24);
permission_feature->mutable_permission_stats()->set_prompts_count(21);
permission_feature->mutable_notification_permission()
->InitAsDefaultInstance();
kRequestEqualCountsTotal100.mutable_client_features()
->mutable_client_stats()
->set_avg_deny_rate(.25);
kRequestEqualCountsTotal100.mutable_client_features()
->mutable_client_stats()
->set_avg_dismiss_rate(.25);
kRequestEqualCountsTotal100.mutable_client_features()
->mutable_client_stats()
->set_avg_grant_rate(.25);
kRequestEqualCountsTotal100.mutable_client_features()
->mutable_client_stats()
->set_avg_ignore_rate(.25);
kRequestEqualCountsTotal100.mutable_client_features()
->mutable_client_stats()
->set_prompts_count(100);
kRequestEqualCountsTotal100.mutable_client_features()->set_platform(
permissions::GetCurrentPlatformProto());
kRequestEqualCountsTotal100.mutable_client_features()->set_gesture(
permissions::ClientFeatures_Gesture_GESTURE);
permission_feature =
kRequestEqualCountsTotal100.mutable_permission_features()->Add();
permission_feature->mutable_permission_stats()->set_avg_deny_rate(.25);
permission_feature->mutable_permission_stats()->set_avg_dismiss_rate(.25);
permission_feature->mutable_permission_stats()->set_avg_grant_rate(.25);
permission_feature->mutable_permission_stats()->set_avg_ignore_rate(.25);
permission_feature->mutable_permission_stats()->set_prompts_count(100);
permission_feature->mutable_notification_permission()
->InitAsDefaultInstance();
kRequestDifferentCounts.mutable_client_features()
->mutable_client_stats()
->set_avg_deny_rate(.25);
kRequestDifferentCounts.mutable_client_features()
->mutable_client_stats()
->set_avg_dismiss_rate(.25);
kRequestDifferentCounts.mutable_client_features()
->mutable_client_stats()
->set_avg_grant_rate(.25);
kRequestDifferentCounts.mutable_client_features()
->mutable_client_stats()
->set_avg_ignore_rate(.25);
kRequestDifferentCounts.mutable_client_features()
->mutable_client_stats()
->set_prompts_count(100);
kRequestDifferentCounts.mutable_client_features()->set_platform(
permissions::GetCurrentPlatformProto());
kRequestDifferentCounts.mutable_client_features()->set_gesture(
permissions::ClientFeatures_Gesture_NO_GESTURE);
permission_feature =
kRequestDifferentCounts.mutable_permission_features()->Add();
permission_feature->mutable_permission_stats()->set_avg_deny_rate(0);
permission_feature->mutable_permission_stats()->set_avg_dismiss_rate(0);
permission_feature->mutable_permission_stats()->set_avg_grant_rate(0);
permission_feature->mutable_permission_stats()->set_avg_ignore_rate(0);
permission_feature->mutable_permission_stats()->set_prompts_count(0);
permission_feature->mutable_notification_permission()
->InitAsDefaultInstance();
auto* prediction = kResponseLikely.mutable_suggestion()->Add();
prediction->mutable_grant_likelihood()->set_discretized_likelihood(
permissions::
PermissionSuggestion_Likelihood_DiscretizedLikelihood_LIKELY);
prediction = kResponseUnlikely.mutable_suggestion()->Add();
prediction->mutable_grant_likelihood()->set_discretized_likelihood(
permissions::
PermissionSuggestion_Likelihood_DiscretizedLikelihood_UNLIKELY);
}
} // namespace
namespace permissions {
class PredictionServiceTest : public testing::Test {
public:
PredictionServiceTest()
: test_shared_loader_factory_(
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&test_url_loader_factory_)) {}
~PredictionServiceTest() override = default;
void SetUp() override {
prediction_service_ =
std::make_unique<PredictionService>(test_shared_loader_factory_);
InitializeProtoHelperObjects();
}
void Respond(const GURL& url,
double delay_in_seconds = 0,
int err_code = net::OK) {
if (delay_in_seconds > 0) {
// Post a task to rerun this after |delay_in_seconds| seconds
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&PredictionServiceTest::Respond,
base::Unretained(this), url, 0, err_code),
base::TimeDelta::FromSecondsD(delay_in_seconds));
return;
}
auto head = network::mojom::URLResponseHead::New();
head->headers = base::MakeRefCounted<net::HttpResponseHeaders>("");
head->headers->AddHeader("Content-Type", "application/octet-stream");
test_url_loader_factory_.SimulateResponseForPendingRequest(
url, network::URLLoaderCompletionStatus(err_code), std::move(head),
GetResponseForUrl(url));
}
void StartLookup(const PredictionRequestFeatures& entity,
base::RunLoop* request_loop,
base::RunLoop* response_loop) {
prediction_service_->StartLookup(
entity,
base::BindOnce(&PredictionServiceTest::RequestCallback,
base::Unretained(this), request_loop),
base::BindOnce(&PredictionServiceTest::ResponseCallback,
base::Unretained(this), response_loop));
}
void RequestCallback(base::RunLoop* request_loop,
std::unique_ptr<GetSuggestionsRequest> request,
std::string access_token) {
received_requests_.emplace_back(std::move(request));
if (request_loop)
request_loop->Quit();
// Access token should always be the empty string.
EXPECT_EQ(std::string(), access_token);
}
void ResponseCallback(base::RunLoop* response_loop,
bool lookup_successful,
bool response_from_cache,
std::unique_ptr<GetSuggestionsResponse> response) {
received_responses_.emplace_back(std::move(response));
if (response_loop)
response_loop->Quit();
// The response is never from the cache.
EXPECT_FALSE(response_from_cache);
}
protected:
std::vector<std::unique_ptr<GetSuggestionsRequest>> received_requests_;
std::vector<std::unique_ptr<GetSuggestionsResponse>> received_responses_;
std::unique_ptr<PredictionService> prediction_service_;
// Different paths to simulate different server behaviours.
const GURL kUrl_Unlikely{"http://predictionsevice.com/unlikely"};
const GURL kUrl_Likely{"http://predictionsevice.com/likely"};
const GURL kUrl_Invalid{"http://predictionsevice.com/invalid"};
private:
std::string GetResponseForUrl(const GURL& url) {
if (url == kUrl_Unlikely) {
return kResponseUnlikely.SerializeAsString();
} else if (url == kUrl_Likely) {
return kResponseLikely.SerializeAsString();
} else if (url == GURL(permissions::kDefaultPredictionServiceUrl)) {
return kResponseLikely.SerializeAsString();
} else if (url == kUrl_Invalid) {
return "This is not a valid response";
}
return "";
}
base::test::TaskEnvironment task_environment_;
network::TestURLLoaderFactory test_url_loader_factory_;
scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
};
TEST_F(PredictionServiceTest, BuiltProtoRequestIsCorrect) {
struct {
PredictionRequestFeatures entity;
GetSuggestionsRequest expected_request;
} kTests[] = {
{kFeaturesAllCountsZero, kRequestAllCountsZero},
{kFeaturesCountsNeedingRounding, kRequestRoundedCounts},
{kFeaturesEvenCountsOver100, kRequestEqualCountsTotal100},
{kFeaturesEvenCountsOver100Alt, kRequestEqualCountsTotal100},
{kFeaturesDifferentCounts, kRequestDifferentCounts},
};
prediction_service_->set_prediction_service_url_for_testing(
GURL(kUrl_Likely));
for (const auto& kTest : kTests) {
base::RunLoop run_loop;
StartLookup(kTest.entity, &run_loop, nullptr /* response_loop */);
run_loop.Run();
EXPECT_EQ(1u, received_requests_.size());
EXPECT_EQ(kTest.expected_request.SerializeAsString(),
received_requests_[0]->SerializeAsString());
received_requests_.clear();
}
}
TEST_F(PredictionServiceTest, ResponsesAreCorrect) {
struct {
GURL url;
base::Optional<GetSuggestionsResponse> expected_response;
double delay_in_seconds;
int err_code;
} kTests[] = {
// Test different responses.
{kUrl_Likely, base::Optional<GetSuggestionsResponse>(kResponseLikely)},
{kUrl_Unlikely,
base::Optional<GetSuggestionsResponse>(kResponseUnlikely)},
// Test the response's timeout.
{kUrl_Likely, base::Optional<GetSuggestionsResponse>(kResponseLikely),
0.5},
{kUrl_Likely, base::nullopt, 2},
// Test error code responses.
{kUrl_Likely, base::nullopt, 0, net::ERR_SSL_PROTOCOL_ERROR},
{kUrl_Likely, base::nullopt, 0, net::ERR_CONNECTION_FAILED},
};
for (const auto& kTest : kTests) {
prediction_service_->set_prediction_service_url_for_testing(kTest.url);
base::RunLoop response_loop;
StartLookup(kFeaturesAllCountsZero, nullptr, &response_loop);
Respond(kTest.url, kTest.delay_in_seconds, kTest.err_code);
response_loop.Run();
EXPECT_EQ(1u, received_responses_.size());
if (kTest.expected_response.has_value()) {
EXPECT_TRUE(received_responses_[0]);
EXPECT_EQ(kTest.expected_response->SerializeAsString(),
received_responses_[0]->SerializeAsString());
} else {
EXPECT_FALSE(received_responses_[0]);
}
received_responses_.clear();
}
}
// These two tests together prove the assertion that you can override the server
// url via command line, but they need to be separated since the URL is only
// computed once, lazily initialized.
TEST_F(PredictionServiceTest, WithoutCommandLineUseDefaultUrl) {
base::RunLoop response_loop;
StartLookup(kFeaturesAllCountsZero, nullptr, &response_loop);
Respond(GURL(kDefaultPredictionServiceUrl));
response_loop.Run();
EXPECT_TRUE(received_responses_[0]);
EXPECT_EQ(kResponseLikely.SerializeAsString(),
received_responses_[0]->SerializeAsString());
}
TEST_F(PredictionServiceTest, CommandLineCanOverrideServerUrl) {
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
kDefaultPredictionServiceUrlSwitchKey, kUrl_Unlikely.spec());
base::RunLoop response_loop;
StartLookup(kFeaturesAllCountsZero, nullptr, &response_loop);
Respond(GURL(kUrl_Unlikely));
response_loop.Run();
EXPECT_TRUE(received_responses_[0]);
EXPECT_EQ(kResponseUnlikely.SerializeAsString(),
received_responses_[0]->SerializeAsString());
}
TEST_F(PredictionServiceTest, InvalidCommandLineUrlFallsbackOnDefault) {
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
kDefaultPredictionServiceUrlSwitchKey, "this_is_not_an_url");
base::RunLoop response_loop;
StartLookup(kFeaturesAllCountsZero, nullptr, &response_loop);
Respond(GURL(kDefaultPredictionServiceUrl));
response_loop.Run();
EXPECT_TRUE(received_responses_[0]);
EXPECT_EQ(kResponseLikely.SerializeAsString(),
received_responses_[0]->SerializeAsString());
}
TEST_F(PredictionServiceTest, HandleSimultaneousRequests) {
prediction_service_->set_prediction_service_url_for_testing(kUrl_Likely);
base::RunLoop response_loop;
StartLookup(kFeaturesAllCountsZero, nullptr, &response_loop);
prediction_service_->set_prediction_service_url_for_testing(kUrl_Unlikely);
base::RunLoop response_loop2;
StartLookup(kFeaturesAllCountsZero, nullptr, &response_loop2);
EXPECT_EQ(2u, prediction_service_->pending_requests_for_testing().size());
Respond(kUrl_Unlikely);
response_loop2.Run();
EXPECT_EQ(1u, received_responses_.size());
EXPECT_TRUE(received_responses_[0]);
EXPECT_EQ(kResponseUnlikely.SerializeAsString(),
received_responses_[0]->SerializeAsString());
EXPECT_EQ(1u, prediction_service_->pending_requests_for_testing().size());
Respond(kUrl_Likely);
response_loop.Run();
EXPECT_EQ(2u, received_responses_.size());
EXPECT_TRUE(received_responses_[1].get() != nullptr);
EXPECT_EQ(kResponseLikely.SerializeAsString(),
received_responses_[1]->SerializeAsString());
EXPECT_EQ(0u, prediction_service_->pending_requests_for_testing().size());
}
TEST_F(PredictionServiceTest, InvalidResponse) {
base::RunLoop response_loop;
StartLookup(kFeaturesAllCountsZero, nullptr, &response_loop);
Respond(GURL(kUrl_Invalid));
response_loop.Run();
EXPECT_FALSE(received_responses_[0]);
}
} // namespace permissions
......@@ -227,6 +227,7 @@ Refer to README.md for content description and update process.
<item id="pepper_tcp_socket" added_in_milestone="65" hash_code="120623198" type="0" content_hash_code="27489892" os_list="linux,windows" file_path="content/browser/renderer_host/pepper/pepper_socket_utils.cc"/>
<item id="pepper_udp_socket" added_in_milestone="70" hash_code="53512439" type="0" content_hash_code="7268418" os_list="linux,windows" file_path="content/browser/renderer_host/pepper/pepper_socket_utils.cc"/>
<item id="per_user_topic_registration_request" added_in_milestone="68" hash_code="10498172" type="0" content_hash_code="57098847" os_list="linux,windows" file_path="components/invalidation/impl/per_user_topic_subscription_request.cc"/>
<item id="permission_predictions" added_in_milestone="88" hash_code="119808259" type="0" content_hash_code="806663" os_list="linux,windows" file_path="components/permissions/prediction_service/prediction_service.cc"/>
<item id="permission_reporting" added_in_milestone="62" hash_code="131741641" type="0" deprecated="2018-03-06" content_hash_code="7213535" file_path=""/>
<item id="permission_request_creator" added_in_milestone="62" hash_code="43206794" type="0" deprecated="2019-07-30" content_hash_code="73571699" file_path=""/>
<item id="persist_blob_to_indexed_db" added_in_milestone="62" hash_code="32030464" type="0" deprecated="2018-08-13" content_hash_code="35410079" file_path=""/>
......
......@@ -202,6 +202,7 @@ hidden="true" so that these annotations don't show up in the document.
<sender name="Safe Browsing">
<traffic_annotation unique_id="client_download_request"/>
<traffic_annotation unique_id="password_protection_request"/>
<traffic_annotation unique_id="permission_predictions"/>
<traffic_annotation unique_id="safe_browsing_certificate_error_reporting"/>
<traffic_annotation unique_id="safe_browsing_client_side_malware_detector"/>
<traffic_annotation unique_id="safe_browsing_client_side_phishing_detector"/>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment