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 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_FONT_MATCHING_METRICS_H_
6 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_FONT_MATCHING_METRICS_H_
7 
8 #include "services/metrics/public/cpp/ukm_source_id.h"
9 #include "third_party/blink/public/common/privacy_budget/identifiable_token.h"
10 #include "third_party/blink/public/common/privacy_budget/identifiable_token_builder.h"
11 #include "third_party/blink/renderer/platform/fonts/font_description.h"
12 #include "third_party/blink/renderer/platform/fonts/font_fallback_priority.h"
13 #include "third_party/blink/renderer/platform/fonts/simple_font_data.h"
14 #include "third_party/blink/renderer/platform/platform_export.h"
15 #include "third_party/blink/renderer/platform/timer.h"
16 #include "third_party/blink/renderer/platform/wtf/hash_functions.h"
17 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
18 #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
19 
20 namespace ukm {
21 class UkmRecorder;
22 }  // namespace ukm
23 
24 namespace blink {
25 
26 // A (generic) wrapper around IdentifiableToken to enable its use as a HashMap
27 // key. The |token| represents the parameters by which a font was looked up.
28 // However, if |is_deleted_value| or |is_empty_value|, this key represents an
29 // object for HashMap's internal use only. In that case, |token| is left as a
30 // default value.
31 struct IdentifiableTokenKey {
32   IdentifiableToken token;
33   bool is_deleted_value = false;
34   bool is_empty_value = false;
35 
IdentifiableTokenKeyIdentifiableTokenKey36   IdentifiableTokenKey() : is_empty_value(true) {}
IdentifiableTokenKeyIdentifiableTokenKey37   explicit IdentifiableTokenKey(const IdentifiableToken& token)
38       : token(token) {}
IdentifiableTokenKeyIdentifiableTokenKey39   explicit IdentifiableTokenKey(WTF::HashTableDeletedValueType)
40       : is_deleted_value(true) {}
41 
IsHashTableDeletedValueIdentifiableTokenKey42   bool IsHashTableDeletedValue() const { return is_deleted_value; }
43 
44   bool operator==(const IdentifiableTokenKey& other) const {
45     return token == other.token && is_deleted_value == other.is_deleted_value &&
46            is_empty_value == other.is_empty_value;
47   }
48   bool operator!=(const IdentifiableTokenKey& other) const {
49     return !(*this == other);
50   }
51 };
52 
53 // A helper that defines the hash and equality functions that HashMap should use
54 // internally for comparing IdentifiableTokenKeys.
55 struct IdentifiableTokenKeyHash {
56   STATIC_ONLY(IdentifiableTokenKeyHash);
GetHashIdentifiableTokenKeyHash57   static unsigned GetHash(const IdentifiableTokenKey& key) {
58     IntHash<int64_t> hasher;
59     return hasher.GetHash(key.token.ToUkmMetricValue()) ^
60            hasher.GetHash((key.is_deleted_value << 1) + key.is_empty_value);
61   }
EqualIdentifiableTokenKeyHash62   static bool Equal(const IdentifiableTokenKey& a,
63                     const IdentifiableTokenKey& b) {
64     return a == b;
65   }
66   static const bool safe_to_compare_to_empty_or_deleted = true;
67 };
68 
69 // A helper that defines the invalid 'empty value' that HashMap should use
70 // internally.
71 struct IdentifiableTokenKeyHashTraits
72     : WTF::SimpleClassHashTraits<IdentifiableTokenKey> {
73   STATIC_ONLY(IdentifiableTokenKeyHashTraits);
74   static const bool kEmptyValueIsZero = false;
EmptyValueIdentifiableTokenKeyHashTraits75   static IdentifiableTokenKey EmptyValue() { return IdentifiableTokenKey(); }
76 };
77 
78 // Tracks and reports UKM metrics of attempted font family match attempts (both
79 // successful and not successful) by the current frame.
80 //
81 // The number of successful / not successful font family match attempts are
82 // reported to UKM. The class de-dupes attempts to match the same font family
83 // name such that they are counted as one attempt.
84 //
85 // Each local font lookup is also reported as is each mapping of generic font
86 // family name to its corresponding actual font family names. Local font lookups
87 // are deduped according to the family name looked up in the FontCache and the
88 // FontSelectionRequest parameters (i.e. weight, width and slope). Generic font
89 // family lookups are de-duped according to the generic name, the
90 // GenericFamilyType and the script. Both types of lookup events are reported
91 // regularly.
92 class PLATFORM_EXPORT FontMatchingMetrics {
93  public:
94   enum FontLoadContext { kTopLevelFrame = 0, kSubframe, kWorker };
95 
96   // Create a FontMatchingMetrics objects for a frame, with |top_level|
97   // indicating whether it is a mainframe.
98   FontMatchingMetrics(bool top_level,
99                       ukm::UkmRecorder* ukm_recorder,
100                       ukm::SourceId source_id,
101                       scoped_refptr<base::SingleThreadTaskRunner> task_runner);
102 
103   // Create a FontMatchingMetrics objects for a worker.
104   FontMatchingMetrics(ukm::UkmRecorder* ukm_recorder,
105                       ukm::SourceId source_id,
106                       scoped_refptr<base::SingleThreadTaskRunner> task_runner);
107 
108   // Called when a page attempts to match a font family, and the font family is
109   // available.
110   void ReportSuccessfulFontFamilyMatch(const AtomicString& font_family_name);
111 
112   // Called when a page attempts to match a font family, and the font family is
113   // not available.
114   void ReportFailedFontFamilyMatch(const AtomicString& font_family_name);
115 
116   // Called when a page attempts to match a system font family.
117   void ReportSystemFontFamily(const AtomicString& font_family_name);
118 
119   // Called when a page attempts to match a web font family.
120   void ReportWebFontFamily(const AtomicString& font_family_name);
121 
122   // Reports a font listed in a @font-face src:local rule that successfully
123   // matched.
124   void ReportSuccessfulLocalFontMatch(const AtomicString& font_name);
125 
126   // Reports a font listed in a @font-face src:local rule that didn't
127   // successfully match.
128   void ReportFailedLocalFontMatch(const AtomicString& font_name);
129 
130   // Reports a local font was looked up by a name and font description. This
131   // only includes lookups where the name is allowed to match family names,
132   // PostScript names and full font names.
133   void ReportFontLookupByUniqueOrFamilyName(
134       const AtomicString& name,
135       const FontDescription& font_description,
136       SimpleFontData* resulting_font_data);
137 
138   // Reports a local font was looked up by a name and font description. This
139   // only includes lookups where the name is allowed to match PostScript names
140   // and full font names, but not family names.
141   void ReportFontLookupByUniqueNameOnly(const AtomicString& name,
142                                         const FontDescription& font_description,
143                                         SimpleFontData* resulting_font_data,
144                                         bool is_loading_fallback = false);
145 
146   // Reports a font was looked up by a fallback character, fallback priority,
147   // and a font description.
148   void ReportFontLookupByFallbackCharacter(
149       UChar32 fallback_character,
150       FontFallbackPriority fallback_priority,
151       const FontDescription& font_description,
152       SimpleFontData* resulting_font_data);
153 
154   // Reports a last-resort fallback font was looked up by a font description.
155   void ReportLastResortFallbackFontLookup(
156       const FontDescription& font_description,
157       SimpleFontData* resulting_font_data);
158 
159   // Reports a generic font family name was matched according to the script and
160   // the user's preferences to a font family name.
161   void ReportFontFamilyLookupByGenericFamily(
162       const AtomicString& generic_font_family_name,
163       UScriptCode script,
164       FontDescription::GenericFamilyType generic_family_type,
165       const AtomicString& resulting_font_name);
166 
167   // Called on page unload and forces metrics to be flushed.
168   void PublishAllMetrics();
169 
170   // Called whenever a font lookup event that will be saved in |font_tracker| or
171   // |user_font_preference_mapping| occurs.
172   void OnFontLookup();
173 
174   // Publishes the font lookup events. Recorded on document shutdown/worker
175   // destruction and every minute, as long as additional lookups are occurring.
176   void PublishIdentifiabilityMetrics();
177 
178   // Publishes the number of font family matches attempted (both successful
179   // and otherwise) to UKM. Recorded on page unload.
180   void PublishUkmMetrics();
181 
182  private:
183   void IdentifiabilityMetricsTimerFired(TimerBase*);
184 
185   // This HashMap generically stores details of font lookups, i.e. what was used
186   // to search for the font, and what the resulting font was. The key is an
187   // IdentifiableTokenKey representing a wrapper around a digest of the lookup
188   // parameters. The value is an IdentifiableToken representing either a digest
189   // of the returned typeface or 0, if no valid typeface was found.
190   using TokenToTokenHashMap = HashMap<IdentifiableTokenKey,
191                                       IdentifiableToken,
192                                       IdentifiableTokenKeyHash,
193                                       IdentifiableTokenKeyHashTraits>;
194 
195   // Adds a digest of the |font_data|'s typeface to |hash_map| using the key
196   // |input_key|, unless that key is already present. If |font_data| is not
197   // nullptr, then the typeface digest will also be saved with its PostScript
198   // name in |font_load_postscript_name_|.
199   void InsertFontHashIntoMap(IdentifiableTokenKey input_key,
200                              SimpleFontData* font_data,
201                              TokenToTokenHashMap& hash_map);
202 
203   // Reports a local font's existence was looked up by a name, but its actual
204   // font data may or may not have been loaded. This only includes lookups where
205   // the name is allowed to match PostScript names and full font names, but not
206   // family names.
207   void ReportLocalFontExistenceByUniqueNameOnly(const AtomicString& font_name,
208                                                 bool font_exists);
209 
210   // Constructs a builder with a hash of the FontSelectionRequest already added.
211   IdentifiableTokenBuilder GetTokenBuilderWithFontSelectionRequest(
212       const FontDescription& font_description);
213 
214   // Get a hash that uniquely represents the font data. Returns 0 if |font_data|
215   // is nullptr.
216   int64_t GetHashForFontData(SimpleFontData* font_data);
217 
218   void Initialize();
219 
220   // Get a token that uniquely represents the typeface's PostScript name. May
221   // represent the empty string if no PostScript name was found.
222   IdentifiableToken GetPostScriptNameTokenForFontData(
223       SimpleFontData* font_data);
224 
225   // Font family names successfully matched.
226   HashSet<AtomicString> successful_font_families_;
227 
228   // Font family names that weren't successfully matched.
229   HashSet<AtomicString> failed_font_families_;
230 
231   // System font families the page attempted to match.
232   HashSet<AtomicString> system_font_families_;
233 
234   // Web font families the page attempted to match.
235   HashSet<AtomicString> web_font_families_;
236 
237   // @font-face src:local fonts that successfully matched.
238   HashSet<AtomicString> local_fonts_succeeded_;
239 
240   // @font-face src:local fonts that didn't successfully match.
241   HashSet<AtomicString> local_fonts_failed_;
242 
243   // Indicates whether this FontMatchingMetrics instance is for a top-level
244   // frame, a subframe or a worker.
245   const FontLoadContext load_context_;
246 
247   TokenToTokenHashMap font_lookups_by_unique_or_family_name_;
248   TokenToTokenHashMap font_lookups_by_unique_name_only_;
249   TokenToTokenHashMap font_lookups_by_fallback_character_;
250   TokenToTokenHashMap font_lookups_as_last_resort_;
251   TokenToTokenHashMap generic_font_lookups_;
252   TokenToTokenHashMap font_load_postscript_name_;
253   TokenToTokenHashMap local_font_existence_by_unique_name_only_;
254 
255   ukm::UkmRecorder* const ukm_recorder_;
256   const ukm::SourceId source_id_;
257 
258   TaskRunnerTimer<FontMatchingMetrics> identifiability_metrics_timer_;
259 };
260 
261 }  // namespace blink
262 
263 #endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_FONT_MATCHING_METRICS_H_
264