blob: 700bf7c23a46082805437ad6b12d5b24a9590e05 [file] [log] [blame]
// Copyright 2020 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 "content/browser/font_access/font_enumeration_cache_win.h"
#include "base/feature_list.h"
#include "base/i18n/rtl.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/timer/elapsed_timer.h"
#include "third_party/blink/public/common/features.h"
#include "ui/gfx/win/direct_write.h"
namespace content {
namespace {
// The following HRESULT code is also used in local font src matching.
constexpr HRESULT kErrorNoFullNameOrPostScriptName =
MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, 0xD103);
// Additional local custom interface specific HRESULT code to log font
// enumeration errors when reporting them in a UMA metric.
constexpr HRESULT kErrorNoFamilyName =
MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, 0xD104);
base::Optional<std::string> GetNativeString(
Microsoft::WRL::ComPtr<IDWriteLocalizedStrings> names) {
// Retrieve the native name. Try the "en-us" locale and if it's
// not present, used the first available localized name.
base::Optional<std::string> native =
gfx::win::RetrieveLocalizedString(names.Get(), "en-us");
if (!native) {
native = gfx::win::RetrieveLocalizedString(names.Get(), "");
}
return native;
}
base::Optional<std::string> GetLocalizedString(
Microsoft::WRL::ComPtr<IDWriteLocalizedStrings> names,
const std::string& locale) {
base::Optional<std::string> localized_name =
gfx::win::RetrieveLocalizedString(names.Get(), locale);
return localized_name;
}
std::unique_ptr<content::FontEnumerationCacheWin::FamilyDataResult>
ExtractNamesFromFamily(Microsoft::WRL::ComPtr<IDWriteFontCollection> collection,
uint32_t family_index,
const std::string& locale) {
auto family_result =
std::make_unique<content::FontEnumerationCacheWin::FamilyDataResult>();
family_result->fonts =
std::vector<blink::FontEnumerationTable_FontMetadata>();
family_result->exit_hresult = S_OK;
Microsoft::WRL::ComPtr<IDWriteFontFamily> family;
Microsoft::WRL::ComPtr<IDWriteLocalizedStrings> family_names;
HRESULT hr = collection->GetFontFamily(family_index, &family);
if (FAILED(hr)) {
family_result->exit_hresult = hr;
return family_result;
}
hr = family->GetFamilyNames(&family_names);
if (FAILED(hr)) {
family_result->exit_hresult = hr;
return family_result;
}
base::Optional<std::string> native_family_name =
GetNativeString(family_names);
if (!native_family_name) {
family_result->exit_hresult = kErrorNoFamilyName;
return family_result;
}
UINT32 font_count = family->GetFontCount();
for (UINT32 font_index = 0; font_index < font_count; ++font_index) {
BOOL exists = false;
Microsoft::WRL::ComPtr<IDWriteFont> font;
{
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::MAY_BLOCK);
hr = family->GetFont(font_index, &font);
}
if (FAILED(hr)) {
family_result->exit_hresult = hr;
return family_result;
}
// Skip this font if it's a simulation.
if (font->GetSimulations() != DWRITE_FONT_SIMULATIONS_NONE)
continue;
Microsoft::WRL::ComPtr<IDWriteLocalizedStrings> postscript_name;
Microsoft::WRL::ComPtr<IDWriteLocalizedStrings> full_name;
Microsoft::WRL::ComPtr<IDWriteLocalizedStrings> style;
// DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_NAME and
// DWRITE_INFORMATIONAL_STRING_FULL_NAME are only supported on Windows 7
// with KB2670838 (https://support.microsoft.com/en-us/kb/2670838)
// installed. It is possible to use a fallback as can be observed
// in Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=947812 However,
// this might not be worth the effort.
// Extracting the postscript name.
{
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::MAY_BLOCK);
hr = font->GetInformationalStrings(
DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_NAME, &postscript_name,
&exists);
}
if (FAILED(hr)) {
family_result->exit_hresult = hr;
return family_result;
}
if (!exists) {
family_result->exit_hresult = kErrorNoFullNameOrPostScriptName;
return family_result;
}
base::Optional<std::string> native_postscript_name =
GetNativeString(postscript_name);
if (!native_postscript_name) {
family_result->exit_hresult = kErrorNoFullNameOrPostScriptName;
return family_result;
}
// Extracting the full name.
{
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::MAY_BLOCK);
hr = font->GetInformationalStrings(DWRITE_INFORMATIONAL_STRING_FULL_NAME,
&full_name, &exists);
}
if (FAILED(hr)) {
family_result->exit_hresult = hr;
return family_result;
}
if (!exists) {
family_result->exit_hresult = kErrorNoFullNameOrPostScriptName;
return family_result;
}
base::Optional<std::string> localized_full_name =
GetLocalizedString(full_name, locale);
if (!localized_full_name)
localized_full_name = GetLocalizedString(full_name, "");
if (!localized_full_name)
localized_full_name = native_postscript_name;
// Extracting Style.
{
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::MAY_BLOCK);
hr = font->GetInformationalStrings(
DWRITE_INFORMATIONAL_STRING_PREFERRED_SUBFAMILY_NAMES, &style,
&exists);
}
if (FAILED(hr)) {
family_result->exit_hresult = hr;
return family_result;
}
if (!exists) {
{
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::MAY_BLOCK);
hr = font->GetInformationalStrings(
DWRITE_INFORMATIONAL_STRING_WIN32_SUBFAMILY_NAMES, &style, &exists);
}
if (FAILED(hr)) {
family_result->exit_hresult = hr;
return family_result;
}
}
base::Optional<std::string> native_style_name;
if (exists) {
native_style_name = GetNativeString(style);
}
blink::FontEnumerationTable_FontMetadata metadata;
metadata.set_postscript_name(native_postscript_name.value());
metadata.set_full_name(localized_full_name.value());
metadata.set_family(native_family_name.value());
metadata.set_style(native_style_name ? native_style_name.value() : "");
family_result->fonts.push_back(std::move(metadata));
}
return family_result;
}
} // namespace
FontEnumerationCacheWin::FontEnumerationCacheWin() = default;
FontEnumerationCacheWin::~FontEnumerationCacheWin() = default;
FontEnumerationCacheWin::FamilyDataResult::~FamilyDataResult() = default;
FontEnumerationCacheWin::FamilyDataResult::FamilyDataResult() = default;
// static
FontEnumerationCache* FontEnumerationCache::GetInstance() {
static base::NoDestructor<FontEnumerationCacheWin> instance;
return instance.get();
}
void FontEnumerationCacheWin::InitializeDirectWrite() {
if (direct_write_initialized_)
return;
direct_write_initialized_ = true;
Microsoft::WRL::ComPtr<IDWriteFactory> factory;
gfx::win::CreateDWriteFactory(&factory);
if (factory == nullptr) {
// We won't be able to load fonts, but we should still return messages so
// renderers don't hang.
status_ = FontEnumerationStatus::kUnexpectedError;
return;
}
HRESULT hr = factory->GetSystemFontCollection(&collection_);
DCHECK(SUCCEEDED(hr));
if (!collection_) {
base::UmaHistogramSparse(
"Fonts.AccessAPI.EnumerationCache.Dwrite.GetSystemFontCollectionResult",
hr);
status_ = FontEnumerationStatus::kUnexpectedError;
return;
}
}
void FontEnumerationCacheWin::SchedulePrepareFontEnumerationCache() {
DCHECK(base::FeatureList::IsEnabled(blink::features::kFontAccess));
{
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::MAY_BLOCK);
InitializeDirectWrite();
}
enumeration_errors_.clear();
scoped_refptr<base::SequencedTaskRunner> results_task_runner =
base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT});
results_task_runner->PostTask(
FROM_HERE,
base::BindOnce(&FontEnumerationCacheWin::PrepareFontEnumerationCache,
// Safe because this is an initialized singleton.
base::Unretained(this)));
}
void FontEnumerationCacheWin::PrepareFontEnumerationCache() {
DCHECK(!enumeration_cache_built_->IsSet());
DCHECK(!enumeration_timer_);
enumeration_timer_ = std::make_unique<base::ElapsedTimer>();
font_enumeration_table_ = std::make_unique<blink::FontEnumerationTable>();
{
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::MAY_BLOCK);
outstanding_family_results_ = collection_->GetFontFamilyCount();
base::UmaHistogramCustomCounts(
"Fonts.AccessAPI.EnumerationCache.Dwrite.FamilyCount",
outstanding_family_results_, 1, 5000, 50);
}
std::string locale =
locale_override_.value_or(base::i18n::GetConfiguredLocale());
for (UINT32 family_index = 0; family_index < outstanding_family_results_;
++family_index) {
// Specify base::ThreadPolicy::MUST_USE_FOREGROUND because a priority
// inversion was observed https://crbug.com/960263 for the enumeration
// of font names for @font-face src local matching.
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::ThreadPolicy::MUST_USE_FOREGROUND},
base::BindOnce(&ExtractNamesFromFamily, collection_, family_index,
locale),
base::BindOnce(
&FontEnumerationCacheWin::AppendFontDataAndFinalizeIfNeeded,
// Safe because this is an initialized singleton.
base::Unretained(this)));
}
}
void FontEnumerationCacheWin::AppendFontDataAndFinalizeIfNeeded(
std::unique_ptr<FamilyDataResult> family_data_result) {
outstanding_family_results_--;
// If this task's response came late for some reason, we do not need the
// results anymore and the table was already finalized.
if (enumeration_cache_built_->IsSet())
return;
if (FAILED(family_data_result->exit_hresult))
enumeration_errors_[family_data_result->exit_hresult]++;
// Used to filter duplicates.
std::set<std::string> fonts_seen;
int duplicate_count = 0;
for (const auto& font_meta : family_data_result->fonts) {
const std::string& postscript_name = font_meta.postscript_name();
if (fonts_seen.count(postscript_name) != 0) {
++duplicate_count;
// Skip duplicates.
continue;
}
fonts_seen.insert(postscript_name);
blink::FontEnumerationTable_FontMetadata* added_font_meta =
font_enumeration_table_->add_fonts();
*added_font_meta = font_meta;
}
if (!outstanding_family_results_) {
FinalizeEnumerationCache();
}
base::UmaHistogramCounts100(
"Fonts.AccessAPI.EnumerationCache.DuplicateFontCount", duplicate_count);
}
void FontEnumerationCacheWin::FinalizeEnumerationCache() {
DCHECK(!enumeration_cache_built_->IsSet());
DCHECK(enumeration_timer_);
if (enumeration_errors_.size() > 0) {
auto most_frequent_hresult = std::max_element(
std::begin(enumeration_errors_), std::end(enumeration_errors_),
[](const decltype(enumeration_errors_)::value_type& a,
decltype(enumeration_errors_)::value_type& b) {
return a.second < b.second;
});
base::UmaHistogramSparse(
"Fonts.AccessAPI.EnumerationCache.Dwrite."
"MostFrequentEnumerationFailure",
most_frequent_hresult->first);
}
// Ensures that the FontEnumerationTable gets released when this function goes
// out of scope.
std::unique_ptr<blink::FontEnumerationTable> enumeration_table(
std::move(font_enumeration_table_));
BuildEnumerationCache(std::move(enumeration_table));
base::UmaHistogramMediumTimes("Fonts.AccessAPI.EnumerationTime",
enumeration_timer_->Elapsed());
enumeration_timer_.reset();
// Respond to pending and future requests.
StartCallbacksTaskQueue();
}
} // namespace content