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