1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "third_party/blink/renderer/platform/fonts/font_matching_metrics.h"
6 
7 #include "services/metrics/public/cpp/metrics_utils.h"
8 #include "services/metrics/public/cpp/ukm_builders.h"
9 #include "services/metrics/public/cpp/ukm_recorder.h"
10 #include "third_party/blink/public/common/privacy_budget/identifiability_metric_builder.h"
11 #include "third_party/blink/public/common/privacy_budget/identifiability_study_settings.h"
12 #include "third_party/blink/public/common/privacy_budget/identifiable_surface.h"
13 #include "third_party/blink/public/common/privacy_budget/identifiable_token.h"
14 #include "third_party/blink/renderer/platform/fonts/font_global_context.h"
15 #include "third_party/blink/renderer/platform/privacy_budget/identifiability_digest_helpers.h"
16 
17 namespace {
18 
19 constexpr double kUkmFontLoadCountBucketSpacing = 1.3;
20 
21 template <typename T>
SetIntersection(const HashSet<T> & a,const HashSet<T> & b)22 HashSet<T> SetIntersection(const HashSet<T>& a, const HashSet<T>& b) {
23   HashSet<T> result;
24   for (const T& a_value : a) {
25     if (b.Contains(a_value))
26       result.insert(a_value);
27   }
28   return result;
29 }
30 
31 }  // namespace
32 
33 namespace blink {
34 
FontMatchingMetrics(bool top_level,ukm::UkmRecorder * ukm_recorder,ukm::SourceId source_id,scoped_refptr<base::SingleThreadTaskRunner> task_runner)35 FontMatchingMetrics::FontMatchingMetrics(
36     bool top_level,
37     ukm::UkmRecorder* ukm_recorder,
38     ukm::SourceId source_id,
39     scoped_refptr<base::SingleThreadTaskRunner> task_runner)
40     : load_context_(top_level ? kTopLevelFrame : kSubframe),
41       ukm_recorder_(ukm_recorder),
42       source_id_(source_id),
43       identifiability_metrics_timer_(
44           task_runner,
45           this,
46           &FontMatchingMetrics::IdentifiabilityMetricsTimerFired) {
47   Initialize();
48 }
49 
FontMatchingMetrics(ukm::UkmRecorder * ukm_recorder,ukm::SourceId source_id,scoped_refptr<base::SingleThreadTaskRunner> task_runner)50 FontMatchingMetrics::FontMatchingMetrics(
51     ukm::UkmRecorder* ukm_recorder,
52     ukm::SourceId source_id,
53     scoped_refptr<base::SingleThreadTaskRunner> task_runner)
54     : load_context_(kWorker),
55       ukm_recorder_(ukm_recorder),
56       source_id_(source_id),
57       identifiability_metrics_timer_(
58           task_runner,
59           this,
60           &FontMatchingMetrics::IdentifiabilityMetricsTimerFired) {
61   Initialize();
62 }
63 
Initialize()64 void FontMatchingMetrics::Initialize() {
65   // Estimate of average page font use from anecdotal browsing session.
66   constexpr unsigned kEstimatedFontCount = 7;
67   local_fonts_succeeded_.ReserveCapacityForSize(kEstimatedFontCount);
68   local_fonts_failed_.ReserveCapacityForSize(kEstimatedFontCount);
69 }
70 
ReportSuccessfulFontFamilyMatch(const AtomicString & font_family_name)71 void FontMatchingMetrics::ReportSuccessfulFontFamilyMatch(
72     const AtomicString& font_family_name) {
73   successful_font_families_.insert(font_family_name);
74 }
75 
ReportFailedFontFamilyMatch(const AtomicString & font_family_name)76 void FontMatchingMetrics::ReportFailedFontFamilyMatch(
77     const AtomicString& font_family_name) {
78   failed_font_families_.insert(font_family_name);
79 }
80 
ReportSystemFontFamily(const AtomicString & font_family_name)81 void FontMatchingMetrics::ReportSystemFontFamily(
82     const AtomicString& font_family_name) {
83   system_font_families_.insert(font_family_name);
84 }
85 
ReportWebFontFamily(const AtomicString & font_family_name)86 void FontMatchingMetrics::ReportWebFontFamily(
87     const AtomicString& font_family_name) {
88   web_font_families_.insert(font_family_name);
89 }
90 
ReportSuccessfulLocalFontMatch(const AtomicString & font_name)91 void FontMatchingMetrics::ReportSuccessfulLocalFontMatch(
92     const AtomicString& font_name) {
93   local_fonts_succeeded_.insert(font_name);
94   ReportLocalFontExistenceByUniqueNameOnly(font_name, /*font_exists=*/true);
95 }
96 
ReportFailedLocalFontMatch(const AtomicString & font_name)97 void FontMatchingMetrics::ReportFailedLocalFontMatch(
98     const AtomicString& font_name) {
99   local_fonts_failed_.insert(font_name);
100   ReportLocalFontExistenceByUniqueNameOnly(font_name, /*font_exists=*/false);
101 }
102 
ReportLocalFontExistenceByUniqueNameOnly(const AtomicString & font_name,bool font_exists)103 void FontMatchingMetrics::ReportLocalFontExistenceByUniqueNameOnly(
104     const AtomicString& font_name,
105     bool font_exists) {
106   if (!IdentifiabilityStudySettings::Get()->IsTypeAllowed(
107           IdentifiableSurface::Type::kLocalFontExistenceByUniqueNameOnly)) {
108     return;
109   }
110   IdentifiableTokenKey input_key(
111       IdentifiabilityBenignCaseFoldingStringToken(font_name));
112   local_font_existence_by_unique_name_only_.insert(input_key, font_exists);
113 }
114 
InsertFontHashIntoMap(IdentifiableTokenKey input_key,SimpleFontData * font_data,TokenToTokenHashMap & hash_map)115 void FontMatchingMetrics::InsertFontHashIntoMap(IdentifiableTokenKey input_key,
116                                                 SimpleFontData* font_data,
117                                                 TokenToTokenHashMap& hash_map) {
118   DCHECK(IdentifiabilityStudySettings::Get()->IsActive());
119   if (hash_map.Contains(input_key))
120     return;
121   IdentifiableToken output_token(GetHashForFontData(font_data));
122   hash_map.insert(input_key, output_token);
123 
124   // We only record postscript name metrics if both the the broader lookup's
125   // type and kLocalFontLoadPostScriptName are allowed. (If the former is not,
126   // InsertFontHashIntoMap would not be called.)
127   if (!font_data ||
128       !IdentifiabilityStudySettings::Get()->IsTypeAllowed(
129           IdentifiableSurface::Type::kLocalFontLoadPostScriptName)) {
130     return;
131   }
132   IdentifiableTokenKey postscript_name_key(
133       GetPostScriptNameTokenForFontData(font_data));
134   font_load_postscript_name_.insert(postscript_name_key, output_token);
135 }
136 
137 IdentifiableTokenBuilder
GetTokenBuilderWithFontSelectionRequest(const FontDescription & font_description)138 FontMatchingMetrics::GetTokenBuilderWithFontSelectionRequest(
139     const FontDescription& font_description) {
140   IdentifiableTokenBuilder builder;
141   builder.AddValue(font_description.GetFontSelectionRequest().GetHash());
142   return builder;
143 }
144 
ReportFontLookupByUniqueOrFamilyName(const AtomicString & name,const FontDescription & font_description,SimpleFontData * resulting_font_data)145 void FontMatchingMetrics::ReportFontLookupByUniqueOrFamilyName(
146     const AtomicString& name,
147     const FontDescription& font_description,
148     SimpleFontData* resulting_font_data) {
149   if (!IdentifiabilityStudySettings::Get()->IsTypeAllowed(
150           IdentifiableSurface::Type::kLocalFontLookupByUniqueOrFamilyName)) {
151     return;
152   }
153   OnFontLookup();
154 
155   IdentifiableTokenBuilder builder =
156       GetTokenBuilderWithFontSelectionRequest(font_description);
157 
158   // Font name lookups are case-insensitive.
159   builder.AddToken(IdentifiabilityBenignCaseFoldingStringToken(name));
160 
161   IdentifiableTokenKey input_key(builder.GetToken());
162   InsertFontHashIntoMap(input_key, resulting_font_data,
163                         font_lookups_by_unique_or_family_name_);
164 }
165 
ReportFontLookupByUniqueNameOnly(const AtomicString & name,const FontDescription & font_description,SimpleFontData * resulting_font_data,bool is_loading_fallback)166 void FontMatchingMetrics::ReportFontLookupByUniqueNameOnly(
167     const AtomicString& name,
168     const FontDescription& font_description,
169     SimpleFontData* resulting_font_data,
170     bool is_loading_fallback) {
171   // We ignore lookups that result in loading fallbacks for now as they should
172   // only be temporary.
173   if (is_loading_fallback ||
174       !IdentifiabilityStudySettings::Get()->IsTypeAllowed(
175           IdentifiableSurface::Type::kLocalFontLookupByUniqueNameOnly)) {
176     return;
177   }
178   OnFontLookup();
179 
180   IdentifiableTokenBuilder builder =
181       GetTokenBuilderWithFontSelectionRequest(font_description);
182 
183   // Font name lookups are case-insensitive.
184   builder.AddToken(IdentifiabilityBenignCaseFoldingStringToken(name));
185 
186   IdentifiableTokenKey input_key(builder.GetToken());
187   InsertFontHashIntoMap(input_key, resulting_font_data,
188                         font_lookups_by_unique_name_only_);
189 }
190 
ReportFontLookupByFallbackCharacter(UChar32 fallback_character,FontFallbackPriority fallback_priority,const FontDescription & font_description,SimpleFontData * resulting_font_data)191 void FontMatchingMetrics::ReportFontLookupByFallbackCharacter(
192     UChar32 fallback_character,
193     FontFallbackPriority fallback_priority,
194     const FontDescription& font_description,
195     SimpleFontData* resulting_font_data) {
196   if (!IdentifiabilityStudySettings::Get()->IsTypeAllowed(
197           IdentifiableSurface::Type::kLocalFontLookupByFallbackCharacter)) {
198     return;
199   }
200   OnFontLookup();
201 
202   IdentifiableTokenBuilder builder =
203       GetTokenBuilderWithFontSelectionRequest(font_description);
204   builder.AddValue(fallback_character)
205       .AddToken(IdentifiableToken(fallback_priority));
206 
207   IdentifiableTokenKey input_key(builder.GetToken());
208   InsertFontHashIntoMap(input_key, resulting_font_data,
209                         font_lookups_by_fallback_character_);
210 }
211 
ReportLastResortFallbackFontLookup(const FontDescription & font_description,SimpleFontData * resulting_font_data)212 void FontMatchingMetrics::ReportLastResortFallbackFontLookup(
213     const FontDescription& font_description,
214     SimpleFontData* resulting_font_data) {
215   if (!IdentifiabilityStudySettings::Get()->IsTypeAllowed(
216           IdentifiableSurface::Type::kLocalFontLookupAsLastResort)) {
217     return;
218   }
219   OnFontLookup();
220 
221   IdentifiableTokenBuilder builder =
222       GetTokenBuilderWithFontSelectionRequest(font_description);
223 
224   IdentifiableTokenKey input_key(builder.GetToken());
225   InsertFontHashIntoMap(input_key, resulting_font_data,
226                         font_lookups_as_last_resort_);
227 }
228 
ReportFontFamilyLookupByGenericFamily(const AtomicString & generic_font_family_name,UScriptCode script,FontDescription::GenericFamilyType generic_family_type,const AtomicString & resulting_font_name)229 void FontMatchingMetrics::ReportFontFamilyLookupByGenericFamily(
230     const AtomicString& generic_font_family_name,
231     UScriptCode script,
232     FontDescription::GenericFamilyType generic_family_type,
233     const AtomicString& resulting_font_name) {
234   if (!IdentifiabilityStudySettings::Get()->IsTypeAllowed(
235           IdentifiableSurface::Type::kGenericFontLookup)) {
236     return;
237   }
238   OnFontLookup();
239 
240   // kStandardFamily lookups override the |generic_font_family_name|. See
241   // FontSelector::FamilyNameFromSettings. No need to be case-insensitive as
242   // generic names should already be lowercase.
243   DCHECK(generic_family_type == FontDescription::kStandardFamily ||
244          generic_font_family_name == generic_font_family_name.LowerASCII());
245   IdentifiableToken lookup_name_token = IdentifiabilityBenignStringToken(
246       generic_family_type == FontDescription::kStandardFamily
247           ? font_family_names::kWebkitStandard
248           : generic_font_family_name);
249 
250   IdentifiableTokenBuilder builder;
251   builder.AddToken(lookup_name_token).AddToken(IdentifiableToken(script));
252   IdentifiableTokenKey input_key(builder.GetToken());
253 
254   // Font name lookups are case-insensitive.
255   generic_font_lookups_.insert(
256       input_key,
257       IdentifiabilityBenignCaseFoldingStringToken(resulting_font_name));
258 }
259 
PublishIdentifiabilityMetrics()260 void FontMatchingMetrics::PublishIdentifiabilityMetrics() {
261   if (!IdentifiabilityStudySettings::Get()->IsActive())
262     return;
263 
264   IdentifiabilityMetricBuilder builder(source_id_);
265 
266   std::pair<TokenToTokenHashMap*, IdentifiableSurface::Type>
267       hash_maps_with_corresponding_surface_types[] = {
268           {&font_lookups_by_unique_or_family_name_,
269            IdentifiableSurface::Type::kLocalFontLookupByUniqueOrFamilyName},
270           {&font_lookups_by_unique_name_only_,
271            IdentifiableSurface::Type::kLocalFontLookupByUniqueNameOnly},
272           {&font_lookups_by_fallback_character_,
273            IdentifiableSurface::Type::kLocalFontLookupByFallbackCharacter},
274           {&font_lookups_as_last_resort_,
275            IdentifiableSurface::Type::kLocalFontLookupAsLastResort},
276           {&generic_font_lookups_,
277            IdentifiableSurface::Type::kGenericFontLookup},
278           {&font_load_postscript_name_,
279            IdentifiableSurface::Type::kLocalFontLoadPostScriptName},
280           {&local_font_existence_by_unique_name_only_,
281            IdentifiableSurface::Type::kLocalFontExistenceByUniqueNameOnly},
282       };
283 
284   for (const auto& surface_entry : hash_maps_with_corresponding_surface_types) {
285     TokenToTokenHashMap* hash_map = surface_entry.first;
286     const IdentifiableSurface::Type& surface_type = surface_entry.second;
287     for (const auto& individual_lookup : *hash_map) {
288       if (IdentifiabilityStudySettings::Get()->ShouldSample(surface_type)) {
289         builder.Set(IdentifiableSurface::FromTypeAndToken(
290                         surface_type, individual_lookup.key.token),
291                     individual_lookup.value);
292       }
293     }
294     hash_map->clear();
295   }
296 
297   builder.Record(ukm_recorder_);
298 }
299 
PublishUkmMetrics()300 void FontMatchingMetrics::PublishUkmMetrics() {
301   ukm::builders::FontMatchAttempts(source_id_)
302       .SetLoadContext(load_context_)
303       .SetSystemFontFamilySuccesses(ukm::GetExponentialBucketMin(
304           SetIntersection(successful_font_families_, system_font_families_)
305               .size(),
306           kUkmFontLoadCountBucketSpacing))
307       .SetSystemFontFamilyFailures(ukm::GetExponentialBucketMin(
308           SetIntersection(failed_font_families_, system_font_families_).size(),
309           kUkmFontLoadCountBucketSpacing))
310       .SetWebFontFamilySuccesses(ukm::GetExponentialBucketMin(
311           SetIntersection(successful_font_families_, web_font_families_).size(),
312           kUkmFontLoadCountBucketSpacing))
313       .SetWebFontFamilyFailures(ukm::GetExponentialBucketMin(
314           SetIntersection(failed_font_families_, web_font_families_).size(),
315           kUkmFontLoadCountBucketSpacing))
316       .SetLocalFontFailures(ukm::GetExponentialBucketMin(
317           local_fonts_failed_.size(), kUkmFontLoadCountBucketSpacing))
318       .SetLocalFontSuccesses(ukm::GetExponentialBucketMin(
319           local_fonts_succeeded_.size(), kUkmFontLoadCountBucketSpacing))
320       .Record(ukm_recorder_);
321 }
322 
OnFontLookup()323 void FontMatchingMetrics::OnFontLookup() {
324   DCHECK(IdentifiabilityStudySettings::Get()->IsActive());
325   if (!identifiability_metrics_timer_.IsActive()) {
326     identifiability_metrics_timer_.StartOneShot(base::TimeDelta::FromMinutes(1),
327                                                 FROM_HERE);
328   }
329 }
330 
IdentifiabilityMetricsTimerFired(TimerBase *)331 void FontMatchingMetrics::IdentifiabilityMetricsTimerFired(TimerBase*) {
332   PublishIdentifiabilityMetrics();
333 }
334 
PublishAllMetrics()335 void FontMatchingMetrics::PublishAllMetrics() {
336   PublishIdentifiabilityMetrics();
337   PublishUkmMetrics();
338 }
339 
GetHashForFontData(SimpleFontData * font_data)340 int64_t FontMatchingMetrics::GetHashForFontData(SimpleFontData* font_data) {
341   return font_data ? FontGlobalContext::Get()
342                          ->GetOrComputeTypefaceDigest(font_data->PlatformData())
343                          .ToUkmMetricValue()
344                    : 0;
345 }
346 
GetPostScriptNameTokenForFontData(SimpleFontData * font_data)347 IdentifiableToken FontMatchingMetrics::GetPostScriptNameTokenForFontData(
348     SimpleFontData* font_data) {
349   DCHECK(font_data);
350   return FontGlobalContext::Get()->GetOrComputePostScriptNameDigest(
351       font_data->PlatformData());
352 }
353 
354 }  // namespace blink
355