| // 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 |