blob: abcc0926686c9510bbfb9f526e41bf43ae5c7133 [file] [log] [blame]
// Copyright 2019 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 "third_party/blink/renderer/platform/fonts/font_matching_metrics.h"
#include "base/metrics/histogram_macros.h"
#include "services/metrics/public/cpp/metrics_utils.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "third_party/blink/public/common/privacy_budget/identifiability_metric_builder.h"
#include "third_party/blink/public/common/privacy_budget/identifiability_study_settings.h"
#include "third_party/blink/public/common/privacy_budget/identifiable_surface.h"
#include "third_party/blink/public/common/privacy_budget/identifiable_token.h"
#include "third_party/blink/renderer/platform/fonts/font_global_context.h"
#include "third_party/blink/renderer/platform/privacy_budget/identifiability_digest_helpers.h"
namespace {
constexpr double kUkmFontLoadCountBucketSpacing = 1.3;
template <typename T>
HashSet<T> SetIntersection(const HashSet<T>& a, const HashSet<T>& b) {
HashSet<T> result;
for (const T& a_value : a) {
if (b.Contains(a_value))
result.insert(a_value);
}
return result;
}
} // namespace
namespace blink {
FontMatchingMetrics::FontMatchingMetrics(
bool top_level,
ukm::UkmRecorder* ukm_recorder,
ukm::SourceId source_id,
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: load_context_(top_level ? kTopLevelFrame : kSubframe),
ukm_recorder_(ukm_recorder),
source_id_(source_id),
identifiability_metrics_timer_(
task_runner,
this,
&FontMatchingMetrics::IdentifiabilityMetricsTimerFired) {
Initialize();
}
FontMatchingMetrics::FontMatchingMetrics(
ukm::UkmRecorder* ukm_recorder,
ukm::SourceId source_id,
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: load_context_(kWorker),
ukm_recorder_(ukm_recorder),
source_id_(source_id),
identifiability_metrics_timer_(
task_runner,
this,
&FontMatchingMetrics::IdentifiabilityMetricsTimerFired) {
Initialize();
}
void FontMatchingMetrics::Initialize() {
// Estimate of average page font use from anecdotal browsing session.
constexpr unsigned kEstimatedFontCount = 7;
local_fonts_succeeded_.ReserveCapacityForSize(kEstimatedFontCount);
local_fonts_failed_.ReserveCapacityForSize(kEstimatedFontCount);
}
void FontMatchingMetrics::ReportSuccessfulFontFamilyMatch(
const AtomicString& font_family_name) {
successful_font_families_.insert(font_family_name);
}
void FontMatchingMetrics::ReportFailedFontFamilyMatch(
const AtomicString& font_family_name) {
failed_font_families_.insert(font_family_name);
}
void FontMatchingMetrics::ReportSystemFontFamily(
const AtomicString& font_family_name) {
system_font_families_.insert(font_family_name);
}
void FontMatchingMetrics::ReportWebFontFamily(
const AtomicString& font_family_name) {
web_font_families_.insert(font_family_name);
}
void FontMatchingMetrics::ReportSuccessfulLocalFontMatch(
const AtomicString& font_name) {
local_fonts_succeeded_.insert(font_name);
ReportLocalFontExistenceByUniqueNameOnly(font_name, /*font_exists=*/true);
}
void FontMatchingMetrics::ReportFailedLocalFontMatch(
const AtomicString& font_name) {
local_fonts_failed_.insert(font_name);
ReportLocalFontExistenceByUniqueNameOnly(font_name, /*font_exists=*/false);
}
void FontMatchingMetrics::ReportLocalFontExistenceByUniqueNameOnly(
const AtomicString& font_name,
bool font_exists) {
if (!IdentifiabilityStudySettings::Get()->IsTypeAllowed(
IdentifiableSurface::Type::kLocalFontExistenceByUniqueNameOnly)) {
return;
}
IdentifiableTokenKey input_key(
IdentifiabilityBenignCaseFoldingStringToken(font_name));
local_font_existence_by_unique_name_only_.insert(input_key, font_exists);
}
void FontMatchingMetrics::InsertFontHashIntoMap(IdentifiableTokenKey input_key,
SimpleFontData* font_data,
TokenToTokenHashMap& hash_map) {
DCHECK(IdentifiabilityStudySettings::Get()->IsActive());
if (hash_map.Contains(input_key))
return;
IdentifiableToken output_token(GetHashForFontData(font_data));
hash_map.insert(input_key, output_token);
// We only record postscript name metrics if both the the broader lookup's
// type and kLocalFontLoadPostScriptName are allowed. (If the former is not,
// InsertFontHashIntoMap would not be called.)
if (!font_data ||
!IdentifiabilityStudySettings::Get()->IsTypeAllowed(
IdentifiableSurface::Type::kLocalFontLoadPostScriptName)) {
return;
}
IdentifiableTokenKey postscript_name_key(
GetPostScriptNameTokenForFontData(font_data));
font_load_postscript_name_.insert(postscript_name_key, output_token);
}
IdentifiableTokenBuilder
FontMatchingMetrics::GetTokenBuilderWithFontSelectionRequest(
const FontDescription& font_description) {
IdentifiableTokenBuilder builder;
builder.AddValue(font_description.GetFontSelectionRequest().GetHash());
return builder;
}
void FontMatchingMetrics::ReportFontLookupByUniqueOrFamilyName(
const AtomicString& name,
const FontDescription& font_description,
SimpleFontData* resulting_font_data) {
if (!IdentifiabilityStudySettings::Get()->IsTypeAllowed(
IdentifiableSurface::Type::kLocalFontLookupByUniqueOrFamilyName)) {
return;
}
OnFontLookup();
IdentifiableTokenBuilder builder =
GetTokenBuilderWithFontSelectionRequest(font_description);
// Font name lookups are case-insensitive.
builder.AddToken(IdentifiabilityBenignCaseFoldingStringToken(name));
IdentifiableTokenKey input_key(builder.GetToken());
InsertFontHashIntoMap(input_key, resulting_font_data,
font_lookups_by_unique_or_family_name_);
}
void FontMatchingMetrics::ReportFontLookupByUniqueNameOnly(
const AtomicString& name,
const FontDescription& font_description,
SimpleFontData* resulting_font_data,
bool is_loading_fallback) {
// We ignore lookups that result in loading fallbacks for now as they should
// only be temporary.
if (is_loading_fallback ||
!IdentifiabilityStudySettings::Get()->IsTypeAllowed(
IdentifiableSurface::Type::kLocalFontLookupByUniqueNameOnly)) {
return;
}
OnFontLookup();
IdentifiableTokenBuilder builder =
GetTokenBuilderWithFontSelectionRequest(font_description);
// Font name lookups are case-insensitive.
builder.AddToken(IdentifiabilityBenignCaseFoldingStringToken(name));
IdentifiableTokenKey input_key(builder.GetToken());
InsertFontHashIntoMap(input_key, resulting_font_data,
font_lookups_by_unique_name_only_);
}
void FontMatchingMetrics::ReportFontLookupByFallbackCharacter(
UChar32 fallback_character,
FontFallbackPriority fallback_priority,
const FontDescription& font_description,
SimpleFontData* resulting_font_data) {
if (!IdentifiabilityStudySettings::Get()->IsTypeAllowed(
IdentifiableSurface::Type::kLocalFontLookupByFallbackCharacter)) {
return;
}
OnFontLookup();
IdentifiableTokenBuilder builder =
GetTokenBuilderWithFontSelectionRequest(font_description);
builder.AddValue(fallback_character)
.AddToken(IdentifiableToken(fallback_priority));
IdentifiableTokenKey input_key(builder.GetToken());
InsertFontHashIntoMap(input_key, resulting_font_data,
font_lookups_by_fallback_character_);
}
void FontMatchingMetrics::ReportLastResortFallbackFontLookup(
const FontDescription& font_description,
SimpleFontData* resulting_font_data) {
if (!IdentifiabilityStudySettings::Get()->IsTypeAllowed(
IdentifiableSurface::Type::kLocalFontLookupAsLastResort)) {
return;
}
OnFontLookup();
IdentifiableTokenBuilder builder =
GetTokenBuilderWithFontSelectionRequest(font_description);
IdentifiableTokenKey input_key(builder.GetToken());
InsertFontHashIntoMap(input_key, resulting_font_data,
font_lookups_as_last_resort_);
}
void FontMatchingMetrics::ReportFontFamilyLookupByGenericFamily(
const AtomicString& generic_font_family_name,
UScriptCode script,
FontDescription::GenericFamilyType generic_family_type,
const AtomicString& resulting_font_name) {
if (!IdentifiabilityStudySettings::Get()->IsTypeAllowed(
IdentifiableSurface::Type::kGenericFontLookup)) {
return;
}
OnFontLookup();
// kStandardFamily lookups override the |generic_font_family_name|. See
// FontSelector::FamilyNameFromSettings. No need to be case-insensitive as
// generic names should already be lowercase.
DCHECK(generic_family_type == FontDescription::kStandardFamily ||
generic_font_family_name == generic_font_family_name.LowerASCII());
IdentifiableToken lookup_name_token = IdentifiabilityBenignStringToken(
generic_family_type == FontDescription::kStandardFamily
? font_family_names::kWebkitStandard
: generic_font_family_name);
IdentifiableTokenBuilder builder;
builder.AddToken(lookup_name_token).AddToken(IdentifiableToken(script));
IdentifiableTokenKey input_key(builder.GetToken());
// Font name lookups are case-insensitive.
generic_font_lookups_.insert(
input_key,
IdentifiabilityBenignCaseFoldingStringToken(resulting_font_name));
}
void FontMatchingMetrics::PublishIdentifiabilityMetrics() {
if (!IdentifiabilityStudySettings::Get()->IsActive())
return;
IdentifiabilityMetricBuilder builder(source_id_);
std::pair<TokenToTokenHashMap*, IdentifiableSurface::Type>
hash_maps_with_corresponding_surface_types[] = {
{&font_lookups_by_unique_or_family_name_,
IdentifiableSurface::Type::kLocalFontLookupByUniqueOrFamilyName},
{&font_lookups_by_unique_name_only_,
IdentifiableSurface::Type::kLocalFontLookupByUniqueNameOnly},
{&font_lookups_by_fallback_character_,
IdentifiableSurface::Type::kLocalFontLookupByFallbackCharacter},
{&font_lookups_as_last_resort_,
IdentifiableSurface::Type::kLocalFontLookupAsLastResort},
{&generic_font_lookups_,
IdentifiableSurface::Type::kGenericFontLookup},
{&font_load_postscript_name_,
IdentifiableSurface::Type::kLocalFontLoadPostScriptName},
{&local_font_existence_by_unique_name_only_,
IdentifiableSurface::Type::kLocalFontExistenceByUniqueNameOnly},
};
for (const auto& surface_entry : hash_maps_with_corresponding_surface_types) {
TokenToTokenHashMap* hash_map = surface_entry.first;
const IdentifiableSurface::Type& surface_type = surface_entry.second;
for (const auto& individual_lookup : *hash_map) {
if (IdentifiabilityStudySettings::Get()->ShouldSample(surface_type)) {
builder.Set(IdentifiableSurface::FromTypeAndToken(
surface_type, individual_lookup.key.token),
individual_lookup.value);
}
}
hash_map->clear();
}
builder.Record(ukm_recorder_);
}
void FontMatchingMetrics::PublishUkmMetrics() {
ukm::builders::FontMatchAttempts(source_id_)
.SetLoadContext(load_context_)
.SetSystemFontFamilySuccesses(ukm::GetExponentialBucketMin(
SetIntersection(successful_font_families_, system_font_families_)
.size(),
kUkmFontLoadCountBucketSpacing))
.SetSystemFontFamilyFailures(ukm::GetExponentialBucketMin(
SetIntersection(failed_font_families_, system_font_families_).size(),
kUkmFontLoadCountBucketSpacing))
.SetSystemFontFamilyTotal(ukm::GetExponentialBucketMin(
system_font_families_.size(), kUkmFontLoadCountBucketSpacing))
.SetWebFontFamilySuccesses(ukm::GetExponentialBucketMin(
SetIntersection(successful_font_families_, web_font_families_).size(),
kUkmFontLoadCountBucketSpacing))
.SetWebFontFamilyFailures(ukm::GetExponentialBucketMin(
SetIntersection(failed_font_families_, web_font_families_).size(),
kUkmFontLoadCountBucketSpacing))
.SetWebFontFamilyTotal(ukm::GetExponentialBucketMin(
web_font_families_.size(), kUkmFontLoadCountBucketSpacing))
.SetLocalFontFailures(ukm::GetExponentialBucketMin(
local_fonts_failed_.size(), kUkmFontLoadCountBucketSpacing))
.SetLocalFontSuccesses(ukm::GetExponentialBucketMin(
local_fonts_succeeded_.size(), kUkmFontLoadCountBucketSpacing))
.SetLocalFontTotal(ukm::GetExponentialBucketMin(
local_fonts_succeeded_.size() + local_fonts_failed_.size(),
kUkmFontLoadCountBucketSpacing))
.Record(ukm_recorder_);
UMA_HISTOGRAM_COUNTS_10000("Blink.Fonts.FontFamilyMatchAttempts.System",
system_font_families_.size());
UMA_HISTOGRAM_COUNTS_10000(
"Blink.Fonts.FontMatchAttempts.System",
local_fonts_failed_.size() + local_fonts_succeeded_.size());
}
void FontMatchingMetrics::OnFontLookup() {
DCHECK(IdentifiabilityStudySettings::Get()->IsActive());
if (!identifiability_metrics_timer_.IsActive()) {
identifiability_metrics_timer_.StartOneShot(base::TimeDelta::FromMinutes(1),
FROM_HERE);
}
}
void FontMatchingMetrics::IdentifiabilityMetricsTimerFired(TimerBase*) {
PublishIdentifiabilityMetrics();
}
void FontMatchingMetrics::PublishAllMetrics() {
PublishIdentifiabilityMetrics();
PublishUkmMetrics();
}
int64_t FontMatchingMetrics::GetHashForFontData(SimpleFontData* font_data) {
return font_data ? FontGlobalContext::Get()
->GetOrComputeTypefaceDigest(font_data->PlatformData())
.ToUkmMetricValue()
: 0;
}
IdentifiableToken FontMatchingMetrics::GetPostScriptNameTokenForFontData(
SimpleFontData* font_data) {
DCHECK(font_data);
return FontGlobalContext::Get()->GetOrComputePostScriptNameDigest(
font_data->PlatformData());
}
} // namespace blink