Chromium Code Reviews| Index: content/browser/renderer_host/dwrite_font_proxy_message_filter_win.cc |
| diff --git a/content/browser/renderer_host/dwrite_font_proxy_message_filter_win.cc b/content/browser/renderer_host/dwrite_font_proxy_message_filter_win.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..6abd7b472019722ed6a8a1c3ff7baf18519f2376 |
| --- /dev/null |
| +++ b/content/browser/renderer_host/dwrite_font_proxy_message_filter_win.cc |
| @@ -0,0 +1,349 @@ |
| +// Copyright 2015 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/renderer_host/dwrite_font_proxy_message_filter_win.h" |
| + |
| +#include <dwrite.h> |
| +#include <shlobj.h> |
| + |
| +#include <set> |
| +#include <utility> |
| + |
| +#include "base/callback_helpers.h" |
| +#include "base/i18n/case_conversion.h" |
| +#include "base/logging.h" |
| +#include "base/metrics/histogram_macros.h" |
| +#include "base/strings/string16.h" |
| +#include "base/strings/string_util.h" |
| +#include "base/strings/utf_string_conversions.h" |
| +#include "content/common/dwrite_font_proxy_messages.h" |
| +#include "ipc/ipc_message_macros.h" |
| +#include "ui/gfx/win/direct_write.h" |
| + |
| +namespace mswr = Microsoft::WRL; |
| + |
| +namespace content { |
| + |
| +// This enum is used to define the buckets for an enumerated UMA histogram. |
| +// Hence, |
| +// (a) existing enumerated constants should never be deleted or reordered, and |
| +// (b) new constants should only be appended at the end of the enumeration. |
| +enum DirectWriteFontLoaderType { |
| + FILE_SYSTEM_FONT_DIR = 0, |
| + FILE_OUTSIDE_SANDBOX = 1, |
| + OTHER_LOADER = 2, |
| + |
| + FONT_LOADER_TYPE_MAX_VALUE |
| +}; |
| + |
| +DWriteFontProxyMessageFilter::DWriteFontProxyMessageFilter() |
| + : BrowserMessageFilter(DWriteFontProxyMsgStart) { |
| + mswr::ComPtr<IDWriteFactory> factory; |
| + gfx::win::GetDWriteFactory(&factory); |
| + if (factory == nullptr) { |
| + // We won't be able to load fonts, but we should still return messages so |
| + // renderers don't hang if they for some reason send us a font message. |
| + return; |
| + } |
| + |
| + HRESULT hr = factory->GetSystemFontCollection(&collection_); |
| + DCHECK(SUCCEEDED(hr)); |
| + |
| + std::vector<base::char16> font_path_chars; |
| + // SHGetSpecialFolderPath requires at least MAX_PATH characters. |
| + font_path_chars.resize(MAX_PATH); |
| + DCHECK(SHGetSpecialFolderPath(NULL /* hwndOwner - reserved */, |
| + font_path_chars.data(), CSIDL_FONTS, |
| + FALSE /* fCreate */)); |
| + windows_fonts_path_ = base::i18n::FoldCase(font_path_chars.data()); |
| +} |
| + |
| +bool DWriteFontProxyMessageFilter::OnMessageReceived( |
| + const IPC::Message& message) { |
| + bool handled = true; |
| + IPC_BEGIN_MESSAGE_MAP(DWriteFontProxyMessageFilter, message) |
| + IPC_MESSAGE_HANDLER_DELAY_REPLY(DWriteFontProxyMsg_FindFamily, OnFindFamily) |
| + IPC_MESSAGE_HANDLER(DWriteFontProxyMsg_GetFamilyCount, OnGetFamilyCount) |
| + IPC_MESSAGE_HANDLER_DELAY_REPLY(DWriteFontProxyMsg_GetFamilyNames, |
| + OnGetFamilyNames) |
| + IPC_MESSAGE_HANDLER_DELAY_REPLY(DWriteFontProxyMsg_GetFontFiles, |
| + OnGetFontFiles) |
| + IPC_MESSAGE_UNHANDLED(handled = false) |
| + IPC_END_MESSAGE_MAP() |
| + return handled; |
| +} |
| + |
| +void DWriteFontProxyMessageFilter::OnFindFamily( |
| + const base::string16& family_name, |
| + IPC::Message* reply_message) { |
| + TRACE_EVENT0("dwrite", "FontProxyHost::OnFindFamily"); |
| + BrowserThread::PostTask( |
|
jam
2015/11/09 16:32:08
I forgot to mention this earlier. no need to have
Ilya Kulshin
2015/11/11 01:22:11
Thanks, that works much better.
|
| + BrowserThread::FILE, FROM_HERE, |
| + base::Bind(&DWriteFontProxyMessageFilter::DoFindFamily, this, family_name, |
| + reply_message)); |
| +} |
| + |
| +void DWriteFontProxyMessageFilter::DoFindFamily( |
| + const base::string16& family_name, |
| + IPC::Message* reply_message) { |
| + base::ScopedClosureRunner send_runner( |
| + base::Bind(base::IgnoreResult(&DWriteFontProxyMessageFilter::Send), this, |
| + reply_message)); |
| + DCHECK(collection_ != nullptr); |
| + if (collection_ != nullptr) { |
| + TRACE_EVENT0("dwrite", "FontProxyHost::DoFindFamily"); |
| + BOOL exists = FALSE; |
| + uint32 index; |
| + HRESULT hr = |
| + collection_->FindFamilyName(family_name.data(), &index, &exists); |
| + if (SUCCEEDED(hr)) { |
| + DWriteFontProxyMsg_FindFamily::WriteReplyParams(reply_message, index); |
| + return; |
| + } |
| + } |
| + DWriteFontProxyMsg_FindFamily::WriteReplyParams(reply_message, UINT32_MAX); |
| +} |
| + |
| +void DWriteFontProxyMessageFilter::OnGetFamilyCount(uint32* count) { |
| + TRACE_EVENT0("dwrite", "FontProxyHost::OnGetFamilyCount"); |
| + DCHECK(collection_ != nullptr); |
| + if (collection_ == nullptr) |
| + *count = 0; |
| + else |
| + *count = collection_->GetFontFamilyCount(); |
| +} |
| + |
| +void DWriteFontProxyMessageFilter::OnGetFamilyNames( |
| + uint32 family_index, |
| + IPC::Message* reply_message) { |
| + TRACE_EVENT0("dwrite", "FontProxyHost::OnGetFamilyNames"); |
| + BrowserThread::PostTask( |
| + BrowserThread::FILE, FROM_HERE, |
| + base::Bind(&DWriteFontProxyMessageFilter::DoGetFamilyNames, this, |
| + family_index, reply_message)); |
| +} |
| + |
| +void DWriteFontProxyMessageFilter::DoGetFamilyNames( |
| + uint32 family_index, |
| + IPC::Message* reply_message) { |
| + base::ScopedClosureRunner send_runner( |
| + base::Bind(base::IgnoreResult(&DWriteFontProxyMessageFilter::Send), this, |
| + reply_message)); |
| + DCHECK(collection_ != nullptr); |
| + if (collection_ == nullptr) |
| + return; |
| + |
| + TRACE_EVENT0("dwrite", "FontProxyHost::DoGetFamilyNames"); |
| + |
| + mswr::ComPtr<IDWriteFontFamily> family; |
| + HRESULT hr = collection_->GetFontFamily(family_index, &family); |
| + if (!SUCCEEDED(hr)) |
| + return; |
| + |
| + mswr::ComPtr<IDWriteLocalizedStrings> localized_names; |
| + hr = family->GetFamilyNames(&localized_names); |
| + if (!SUCCEEDED(hr)) |
| + return; |
| + |
| + unsigned int string_count = localized_names->GetCount(); |
| + |
| + std::vector<base::char16> locale; |
| + std::vector<base::char16> name; |
| + std::vector<std::pair<base::string16, base::string16>> family_names; |
| + for (unsigned int index = 0; index < string_count; index++) { |
| + uint32 length = 0; |
| + hr = localized_names->GetLocaleNameLength(index, &length); |
| + if (!SUCCEEDED(hr)) |
| + return; |
| + length++; // Reserve space for the null terminator. |
| + locale.resize(length); |
| + hr = localized_names->GetLocaleName(index, locale.data(), length); |
| + if (!SUCCEEDED(hr)) |
| + return; |
| + DCHECK(locale[length - 1] == L'\0'); |
| + |
| + length = 0; |
| + hr = localized_names->GetStringLength(index, &length); |
| + if (!SUCCEEDED(hr)) |
| + return; |
| + length++; // Reserve space for the null terminator. |
| + name.resize(length); |
| + hr = localized_names->GetString(index, name.data(), length); |
| + if (!SUCCEEDED(hr)) |
| + return; |
| + DCHECK(name[length - 1] == L'\0'); |
|
Alexei Svitkine (slow)
2015/11/06 22:11:45
Can this be a DCHECK_EQ()
Same for other ones.
Ilya Kulshin
2015/11/11 01:22:11
Done.
|
| + |
| + // Would be great to use emplace_back instead. |
| + family_names.push_back(std::pair<base::string16, base::string16>( |
| + base::string16(locale.data()), base::string16(name.data()))); |
| + } |
| + DWriteFontProxyMsg_GetFamilyNames::WriteReplyParams(reply_message, |
| + family_names); |
| +} |
| + |
| +void DWriteFontProxyMessageFilter::OnGetFontFiles(uint32 family_index, |
| + IPC::Message* reply_message) { |
| + TRACE_EVENT0("dwrite", "FontProxyHost::OnGetFontFiles"); |
| + BrowserThread::PostTask( |
| + BrowserThread::FILE, FROM_HERE, |
| + base::Bind(&DWriteFontProxyMessageFilter::DoGetFontFiles, this, |
| + family_index, reply_message)); |
| +} |
| + |
| +const wchar_t* kFontsToIgnore[] = { |
|
Alexei Svitkine (slow)
2015/11/06 22:11:45
These should be in an anon namespace at the top of
Ilya Kulshin
2015/11/11 01:22:11
Done.
|
| + // "Gill Sans Ultra Bold" turns into an Ultra Bold weight "Gill Sans" in |
| + // DirectWrite, but most users don't have any other weights. The regular |
| + // weight font is named "Gill Sans MT", but that ends up in a different |
| + // family with that name. On Mac, there's a "Gill Sans" with various |
| + // weights, |
| + // so CSS authors use { 'font-family': 'Gill Sans', 'Gill Sans MT', ... } |
| + // and |
| + // because of the DirectWrite family futzing, they end up with an Ultra Bold |
| + // font, when they just wanted "Gill Sans". Mozilla implemented a more |
| + // complicated hack where they effectively rename the Ultra Bold font to |
| + // "Gill Sans MT Ultra Bold", but because the Ultra Bold font is so ugly |
| + // anyway, we simply ignore it. See |
| + // http://www.microsoft.com/typography/fonts/font.aspx?FMID=978 for a |
| + // picture |
| + // of the font, and the file name. We also ignore "Gill Sans Ultra Bold |
| + // Condensed". |
| + L"gilsanub.ttf", L"gillubcd.ttf", |
| +}; |
| + |
| +void DWriteFontProxyMessageFilter::DoGetFontFiles(uint32 family_index, |
| + IPC::Message* reply_message) { |
| + base::ScopedClosureRunner send_runner( |
| + base::Bind(base::IgnoreResult(&DWriteFontProxyMessageFilter::Send), this, |
| + reply_message)); |
| + DCHECK(collection_ != nullptr); |
| + if (collection_ == nullptr) |
| + return; |
| + |
| + TRACE_EVENT0("dwrite", "FontProxyHost::DoGetFontFiles"); |
| + |
| + mswr::ComPtr<IDWriteFontFamily> family; |
| + HRESULT hr = collection_->GetFontFamily(family_index, &family); |
| + if (!SUCCEEDED(hr)) |
| + return; |
| + |
| + uint32 font_count = family->GetFontCount(); |
| + |
| + std::set<base::string16> path_set; |
| + std::vector<base::char16> file_path_chars; |
| + // Iterate through all the fonts in the family, and all the files for those |
| + // fonts. If anything goes wrong, bail on the entire family to avoid having |
| + // a partially-loaded font family. |
| + for (unsigned int font_index = 0; font_index < font_count; font_index++) { |
| + mswr::ComPtr<IDWriteFont> font; |
| + hr = family->GetFont(font_index, &font); |
| + if (!SUCCEEDED(hr)) |
| + return; |
| + |
| + mswr::ComPtr<IDWriteFontFace> font_face; |
| + hr = font->CreateFontFace(&font_face); |
| + if (!SUCCEEDED(hr)) |
| + return; |
| + |
| + uint32 file_count; |
| + hr = font_face->GetFiles(&file_count, nullptr); |
| + if (!SUCCEEDED(hr)) |
| + return; |
| + |
| + std::vector<mswr::ComPtr<IDWriteFontFile>> font_files; |
| + font_files.resize(file_count); |
| + hr = font_face->GetFiles( |
| + &file_count, reinterpret_cast<IDWriteFontFile**>(font_files.data())); |
| + if (!SUCCEEDED(hr)) |
| + return; |
| + |
| + for (unsigned int file_index = 0; file_index < file_count; file_index++) { |
| + mswr::ComPtr<IDWriteFontFileLoader> loader; |
| + hr = font_files[file_index]->GetLoader(&loader); |
| + if (!SUCCEEDED(hr)) |
| + return; |
| + |
| + mswr::ComPtr<IDWriteLocalFontFileLoader> local_loader; |
| + hr = loader.CopyTo(local_loader.GetAddressOf()); // QueryInterface. |
| + |
| + if (hr == E_NOINTERFACE) { |
| + // We could get here if the system font collection contains fonts that |
| + // are backed by something other than files in the system fonts folder. |
| + // I don't think that is actually possible, so for now we'll just |
| + // ignore it (result will be that we'll be unable to match any styles |
| + // for this font, forcing blink/skia to fall back to whatever font is |
| + // next). If we get telemetry indicating that this case actually |
| + // happens, we can implement this by exposing the loader via ipc. That |
| + // will likely by loading the font data into shared memory, although we |
| + // could proxy the stream reads directly instead. |
| + UMA_HISTOGRAM_ENUMERATION("DirectWrite.Fonts.Proxy.LoaderType", |
| + OTHER_LOADER, FONT_LOADER_TYPE_MAX_VALUE); |
| + DCHECK(false); |
| + return; |
| + } else if (!SUCCEEDED(hr)) { |
| + return; |
| + } |
| + |
| + const void* key; |
| + uint32 key_size; |
| + hr = font_files[file_index]->GetReferenceKey(&key, &key_size); |
| + if (!SUCCEEDED(hr)) |
| + return; |
| + |
| + uint32 path_length = 0; |
| + local_loader->GetFilePathLengthFromKey(key, key_size, &path_length); |
| + path_length++; // Reserve space for the null terminator. |
| + file_path_chars.resize(path_length); |
| + hr = local_loader->GetFilePathFromKey(key, key_size, |
| + file_path_chars.data(), |
| + path_length); |
| + if (!SUCCEEDED(hr)) |
| + return; |
| + |
| + base::string16 file_path = base::i18n::FoldCase(file_path_chars.data()); |
| + if (!base::StartsWith(file_path, windows_fonts_path_, |
| + base::CompareCase::SENSITIVE)) { |
| + // Skip loading fonts from outside the system fonts directory, since |
| + // these families will not be accessible to the renderer process. If |
| + // this turns out to be a common case, we can either grant the renderer |
| + // access to these files (not sure if this is actually possible), or |
| + // load the file data ourselves and hand it to the renderer. |
| + UMA_HISTOGRAM_ENUMERATION("DirectWrite.Fonts.Proxy.LoaderType", |
| + FILE_OUTSIDE_SANDBOX, |
| + FONT_LOADER_TYPE_MAX_VALUE); |
| + DCHECK(false); |
| + return; |
| + } |
| + |
| + // Refer to comments in kFontsToIgnore for this block. |
| + bool skip_font = false; |
| + for (const auto& ignore : kFontsToIgnore) { |
| + // Ok to do ascii comparison since the strings we are looking for are |
| + // all ascii. |
| + if (base::EndsWith(file_path, ignore, |
| + base::CompareCase::INSENSITIVE_ASCII)) { |
| + // Unlike most other cases in this function, we do not abort loading |
| + // the entire family, since we want to specifically ignore particular |
| + // font styles and load the rest of the family if it exists. The |
| + // renderer can deal with a family with zero files if that ends up |
| + // being the case. |
| + skip_font = true; |
| + break; |
| + } |
| + } |
| + if (skip_font) |
| + continue; |
| + |
| + UMA_HISTOGRAM_ENUMERATION("DirectWrite.Fonts.Proxy.LoaderType", |
|
Alexei Svitkine (slow)
2015/11/06 22:11:45
Please make a helper function to log this histogra
Ilya Kulshin
2015/11/11 01:22:11
Done.
|
| + FILE_SYSTEM_FONT_DIR, |
| + FONT_LOADER_TYPE_MAX_VALUE); |
| + path_set.insert(file_path); |
| + } |
| + } |
| + |
| + std::vector<base::string16> file_paths; |
| + file_paths.assign(path_set.begin(), path_set.end()); |
| + DWriteFontProxyMsg_GetFontFiles::WriteReplyParams(reply_message, file_paths); |
| +} |
| + |
| +} // namespace content |