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..7f78c0a7238c1cc72dfba7c4d6ac34058aa46943 |
--- /dev/null |
+++ b/content/browser/renderer_host/dwrite_font_proxy_message_filter_win.cc |
@@ -0,0 +1,350 @@ |
+// 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/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 "content/shell/common/shell_switches.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 DWriteFontLoaderType { |
+ kFileSystemFontDir = 0, |
+ kFileOutsideSandbox = 1, |
+ kOtherLoader = 2, |
+ |
+ kFontLoaderTypeMaxValue |
+}; |
+ |
+DWriteFontProxyMessageFilter::DWriteFontProxyMessageFilter() |
+ : BrowserMessageFilter(DWriteFontProxyMsgStart), |
+ postTaskAndReply_(&BrowserThread::PostTaskAndReply) { |
+ Init(); |
+} |
+ |
+DWriteFontProxyMessageFilter::DWriteFontProxyMessageFilter( |
+ PostTaskAndReplyFn postTaskAndReply) |
jam
2015/11/03 00:18:54
i dont think you need this extra complexity with t
Ilya Kulshin
2015/11/03 18:56:21
Yes, that's exactly what I needed. Thanks for the
|
+ : BrowserMessageFilter(DWriteFontProxyMsgStart), |
+ postTaskAndReply_(postTaskAndReply) { |
+ Init(); |
+} |
+ |
+void DWriteFontProxyMessageFilter::Init() { |
+ 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> fontPathChars; |
+ // SHGetSpecialFolderPath requires at least MAX_PATH characters. |
+ fontPathChars.resize(MAX_PATH); |
+ DCHECK(SHGetSpecialFolderPath(NULL /* hwndOwner - reserved */, |
+ fontPathChars.data(), CSIDL_FONTS, |
+ FALSE /* fCreate */)); |
+ windowsFontsPath_ = base::i18n::FoldCase(fontPathChars.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& familyName, |
+ IPC::Message* reply_message) { |
+ TRACE_EVENT0("dwrite", "FontProxyHost::OnFindFamily"); |
+ postTaskAndReply_( |
+ BrowserThread::FILE, FROM_HERE, |
+ base::Bind(&DWriteFontProxyMessageFilter::DoFindFamily, this, familyName, |
+ reply_message), |
+ base::Bind(base::IgnoreResult(&DWriteFontProxyMessageFilter::Send), this, |
jam
2015/11/03 00:18:54
no need to use PostTaskAndReply. a messagefilter c
Ilya Kulshin
2015/11/03 18:56:21
Done.
|
+ reply_message)); |
+} |
+ |
+void DWriteFontProxyMessageFilter::DoFindFamily( |
+ const base::string16& familyName, |
+ IPC::Message* reply_message) { |
+ DCHECK(collection_ != nullptr); |
+ if (collection_ != nullptr) { |
+ TRACE_EVENT0("dwrite", "FontProxyHost::DoFindFamily"); |
+ BOOL exists = FALSE; |
+ uint32 index; |
+ HRESULT hr = |
+ collection_->FindFamilyName(familyName.data(), &index, &exists); |
+ if (SUCCEEDED(hr)) { |
+ DWriteFontProxyMsg_FindFamily::WriteReplyParams(reply_message, index); |
+ return; |
+ } |
+ } |
+ DWriteFontProxyMsg_FindFamily::WriteReplyParams(reply_message, UINT32_MAX); |
jam
2015/11/03 00:18:54
no need to use IPC_DELAY_REPLY here since you alwa
Ilya Kulshin
2015/11/03 18:56:21
This is still a callback into direct write, and I'
jam
2015/11/09 16:32:08
I'm not sure what you mean. All I'm saying is don'
|
+} |
+ |
+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 familyIndex, |
+ IPC::Message* reply_message) { |
+ TRACE_EVENT0("dwrite", "FontProxyHost::OnGetFamilyNames"); |
+ postTaskAndReply_( |
+ BrowserThread::FILE, FROM_HERE, |
+ base::Bind(&DWriteFontProxyMessageFilter::DoGetFamilyNames, this, |
+ familyIndex, reply_message), |
+ base::Bind(base::IgnoreResult(&DWriteFontProxyMessageFilter::Send), this, |
+ reply_message)); |
+} |
+ |
+void DWriteFontProxyMessageFilter::DoGetFamilyNames( |
+ uint32 familyIndex, |
+ IPC::Message* reply_message) { |
+ DCHECK(collection_ != nullptr); |
+ if (collection_ == nullptr) |
+ return; |
+ |
+ TRACE_EVENT0("dwrite", "FontProxyHost::DoGetFamilyNames"); |
+ |
+ mswr::ComPtr<IDWriteFontFamily> family; |
+ HRESULT hr = collection_->GetFontFamily(familyIndex, &family); |
+ if (!SUCCEEDED(hr)) |
+ return; |
+ |
+ mswr::ComPtr<IDWriteLocalizedStrings> localizedNames; |
+ hr = family->GetFamilyNames(&localizedNames); |
+ if (!SUCCEEDED(hr)) |
+ return; |
+ |
+ unsigned int stringCount = localizedNames->GetCount(); |
+ |
+ std::vector<base::char16> locale; |
+ std::vector<base::char16> name; |
+ std::vector<std::pair<base::string16, base::string16>> familyNames; |
+ for (unsigned int index = 0; index < stringCount; index++) { |
+ uint32 length = 0; |
+ hr = localizedNames->GetLocaleNameLength(index, &length); |
+ if (!SUCCEEDED(hr)) |
+ return; |
+ length++; // Reserve space for the null terminator. |
+ locale.resize(length); |
+ hr = localizedNames->GetLocaleName(index, locale.data(), length); |
+ if (!SUCCEEDED(hr)) |
+ return; |
+ DCHECK(locale[length - 1] == L'\0'); |
+ |
+ length = 0; |
+ hr = localizedNames->GetStringLength(index, &length); |
+ if (!SUCCEEDED(hr)) |
+ return; |
+ length++; // Reserve space for the null terminator. |
+ name.resize(length); |
+ hr = localizedNames->GetString(index, name.data(), length); |
+ if (!SUCCEEDED(hr)) |
+ return; |
+ DCHECK(name[length - 1] == L'\0'); |
+ |
+ // Would be great to use emplace_back instead. |
+ familyNames.push_back(std::pair<base::string16, base::string16>( |
+ base::string16(locale.data()), base::string16(name.data()))); |
+ } |
+ DWriteFontProxyMsg_GetFamilyNames::WriteReplyParams(reply_message, |
+ familyNames); |
+} |
+ |
+void DWriteFontProxyMessageFilter::OnGetFontFiles(uint32 familyIndex, |
+ IPC::Message* reply_message) { |
+ TRACE_EVENT0("dwrite", "FontProxyHost::OnGetFontFiles"); |
+ postTaskAndReply_( |
+ BrowserThread::FILE, FROM_HERE, |
+ base::Bind(&DWriteFontProxyMessageFilter::DoGetFontFiles, this, |
+ familyIndex, reply_message), |
+ base::Bind(base::IgnoreResult(&DWriteFontProxyMessageFilter::Send), this, |
+ reply_message)); |
+} |
+ |
+const wchar_t* kFontsToIgnore[] = { |
+ // "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 familyIndex, |
+ IPC::Message* reply_message) { |
+ DCHECK(collection_ != nullptr); |
+ if (collection_ == nullptr) |
+ return; |
+ |
+ TRACE_EVENT0("dwrite", "FontProxyHost::DoGetFontFiles"); |
+ |
+ mswr::ComPtr<IDWriteFontFamily> family; |
+ HRESULT hr = collection_->GetFontFamily(familyIndex, &family); |
+ if (!SUCCEEDED(hr)) |
+ return; |
+ |
+ uint32 fontCount = family->GetFontCount(); |
+ |
+ std::set<base::string16> pathSet; |
+ std::vector<base::char16> filePathChars; |
+ // 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 fontIndex = 0; fontIndex < fontCount; fontIndex++) { |
+ mswr::ComPtr<IDWriteFont> font; |
+ hr = family->GetFont(fontIndex, &font); |
+ if (!SUCCEEDED(hr)) |
+ return; |
+ |
+ mswr::ComPtr<IDWriteFontFace> fontFace; |
+ hr = font->CreateFontFace(&fontFace); |
+ if (!SUCCEEDED(hr)) |
+ return; |
+ |
+ uint32 fileCount; |
+ hr = fontFace->GetFiles(&fileCount, nullptr); |
+ if (!SUCCEEDED(hr)) |
+ return; |
+ |
+ std::vector<mswr::ComPtr<IDWriteFontFile>> fontFiles; |
+ fontFiles.resize(fileCount); |
+ hr = fontFace->GetFiles( |
+ &fileCount, reinterpret_cast<IDWriteFontFile**>(fontFiles.data())); |
+ if (!SUCCEEDED(hr)) |
+ return; |
+ |
+ for (unsigned int fileIndex = 0; fileIndex < fileCount; fileIndex++) { |
+ mswr::ComPtr<IDWriteFontFileLoader> loader; |
+ hr = fontFiles[fileIndex]->GetLoader(&loader); |
+ if (!SUCCEEDED(hr)) |
+ return; |
+ |
+ mswr::ComPtr<IDWriteLocalFontFileLoader> localLoader; |
+ hr = loader.CopyTo(localLoader.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("DWriteFontProxy.LoaderType", kOtherLoader, |
+ kFontLoaderTypeMaxValue); |
+ DCHECK(false); |
+ return; |
+ } else if (!SUCCEEDED(hr)) { |
+ return; |
+ } |
+ |
+ const void* key; |
+ uint32 keySize; |
+ hr = fontFiles[fileIndex]->GetReferenceKey(&key, &keySize); |
+ if (!SUCCEEDED(hr)) |
+ return; |
+ |
+ uint32 pathLength = 0; |
+ localLoader->GetFilePathLengthFromKey(key, keySize, &pathLength); |
+ pathLength++; // Reserve space for the null terminator. |
+ filePathChars.resize(pathLength); |
+ hr = localLoader->GetFilePathFromKey(key, keySize, filePathChars.data(), |
+ pathLength); |
+ if (!SUCCEEDED(hr)) |
+ return; |
+ |
+ base::string16 filePath = base::i18n::FoldCase(filePathChars.data()); |
+ if (!base::StartsWith(filePath, windowsFontsPath_, |
+ 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("DWriteFontProxy.LoaderType", |
+ kFileOutsideSandbox, kFontLoaderTypeMaxValue); |
+ DCHECK(false); |
+ return; |
+ } |
+ |
+ // Refer to comments in kFontsToIgnore for this block. |
+ for (const auto& ignore : kFontsToIgnore) { |
+ // Ok to do ascii comparison since the strings we are looking for are |
+ // all ascii. |
+ if (base::EndsWith(filePath, 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. |
+ continue; |
+ } |
+ } |
+ UMA_HISTOGRAM_ENUMERATION("DWriteFontProxy.LoaderType", |
+ kFileSystemFontDir, kFontLoaderTypeMaxValue); |
+ pathSet.insert(filePath); |
+ } |
+ } |
+ |
+ std::vector<base::string16> filePaths; |
+ filePaths.assign(pathSet.begin(), pathSet.end()); |
+ DWriteFontProxyMsg_GetFontFiles::WriteReplyParams(reply_message, filePaths); |
+} |
+ |
+} // namespace content |