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