Move font unique name lookup table construction to own singleton class

Preparation for background scheduling of font unique name table
construction at browser startup. In a forthcoming CL, threading
primitives are added to support scheduling the font lookup table
construction at startup. For now, move the existing code to a separate
singleton class.

Bug: 889864
Change-Id: Idaa0bdf4cd7128e42cdc36d409eab37ae83c1d1a
Reviewed-on: https://chromium-review.googlesource.com/c/1458186
Commit-Queue: Dominik Röttsches <drott@chromium.org>
Reviewed-by: Emil A Eklund <eae@chromium.org>
Cr-Commit-Position: refs/heads/master@{#630099}
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 5e2c320..eaeb87e 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1319,6 +1319,8 @@
     "renderer_host/display_util.h",
     "renderer_host/dwrite_font_file_util_win.cc",
     "renderer_host/dwrite_font_file_util_win.h",
+    "renderer_host/dwrite_font_lookup_table_builder_win.cc",
+    "renderer_host/dwrite_font_lookup_table_builder_win.h",
     "renderer_host/dwrite_font_proxy_impl_win.cc",
     "renderer_host/dwrite_font_proxy_impl_win.h",
     "renderer_host/dwrite_font_uma_logging_win.cc",
diff --git a/content/browser/renderer_host/dwrite_font_lookup_table_builder_win.cc b/content/browser/renderer_host/dwrite_font_lookup_table_builder_win.cc
new file mode 100644
index 0000000..4e61349
--- /dev/null
+++ b/content/browser/renderer_host/dwrite_font_lookup_table_builder_win.cc
@@ -0,0 +1,283 @@
+// Copyright 2019 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_lookup_table_builder_win.h"
+
+#include <dwrite.h>
+#include <dwrite_2.h>
+#include <algorithm>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/i18n/case_conversion.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/no_destructor.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "content/browser/renderer_host/dwrite_font_file_util_win.h"
+#include "content/browser/renderer_host/dwrite_font_uma_logging_win.h"
+#include "third_party/blink/public/common/font_unique_name_lookup/font_unique_name_table.pb.h"
+#include "third_party/blink/public/common/font_unique_name_lookup/icu_fold_case_util.h"
+#include "ui/gfx/win/direct_write.h"
+
+namespace content {
+
+using namespace dwrite_font_file_util;
+using namespace dwrite_font_uma_logging;
+
+namespace {
+// The unresponsive renderer timeout is 30 seconds (kDefaultCommitTimeout). As a
+// starting point, let's set the max time for indexing fonts to a third of that,
+// 10 seconds and record a UMA histogram for how long indexing usually
+// takes. Once we have UMA data, we can look into reducing this timeout. This
+// timeout is meant to cover pathological cases of font indexing where a Windows
+// installation has an unusually large collection of fonts. In practice,
+// building the unique font name table should not take longer than tens of
+// milliseconds (~26 ms on a developer machine, Windows 10, default fonts).
+const base::TimeDelta kFontIndexingTimeout = base::TimeDelta::FromSeconds(10);
+
+const base::TimeDelta kIndexingSlowDownForTesting =
+    base::TimeDelta::FromMilliseconds(1200);
+
+bool ExtractCaseFoldedLocalizedStrings(
+    IDWriteLocalizedStrings* dwrite_localized_strings,
+    std::vector<std::string>* localized_strings) {
+  if (!dwrite_localized_strings->GetCount())
+    return false;
+
+  localized_strings->clear();
+  localized_strings->reserve(dwrite_localized_strings->GetCount());
+  for (UINT32 j = 0; j < dwrite_localized_strings->GetCount(); ++j) {
+    UINT32 length;
+    HRESULT hr = dwrite_localized_strings->GetStringLength(j, &length);
+    if (FAILED(hr))
+      continue;
+    std::wstring localized_name;
+    localized_name.resize(length + 1);
+    hr = dwrite_localized_strings->GetString(j, &localized_name[0], length + 1);
+    if (FAILED(hr)) {
+      continue;
+    }
+    localized_name.resize(length);
+    // The documentation for the API call does not specify an encoding but the
+    // results are wchar_t and FireFox considers them UTF-16, as seen here:
+    // https://dxr.mozilla.org/mozilla-central/source/gfx/thebes/gfxDWriteFontList.cpp#90
+    // so we'll assume that.
+    localized_strings->push_back(base::UTF16ToUTF8(
+        base::i18n::FoldCase(base::string16(localized_name))));
+  }
+  return true;
+}
+
+}  // namespace
+
+DWriteFontLookupTableBuilder::DWriteFontLookupTableBuilder() = default;
+
+DWriteFontLookupTableBuilder::~DWriteFontLookupTableBuilder() = default;
+
+base::ReadOnlySharedMemoryRegion
+DWriteFontLookupTableBuilder::DuplicatedMemoryRegion() {
+  CHECK(EnsureFontUniqueNameTable());
+  return font_table_memory_.region.Duplicate();
+}
+
+bool DWriteFontLookupTableBuilder::IsFontUniqueNameTableValid() {
+  return font_table_memory_.IsValid() && font_table_memory_.mapping.size();
+}
+
+void DWriteFontLookupTableBuilder::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 if they for some reason send us a font message.
+    return;
+  }
+
+  // QueryInterface for IDWriteFactory2. It's ok for this to fail if we are
+  // running an older version of DirectWrite (earlier than Win8.1).
+  factory.As<IDWriteFactory2>(&factory2_);
+
+  HRESULT hr = factory->GetSystemFontCollection(&collection_);
+  DCHECK(SUCCEEDED(hr));
+
+  if (!collection_) {
+    base::UmaHistogramSparse(
+        "DirectWrite.Fonts.Proxy.GetSystemFontCollectionResult", hr);
+    LogMessageFilterError(MessageFilterError::ERROR_NO_COLLECTION);
+    return;
+  }
+}
+
+bool DWriteFontLookupTableBuilder::EnsureFontUniqueNameTable() {
+  if (IsFontUniqueNameTableValid())
+    return true;
+
+  BuildFontUniqueNameTable();
+  return IsFontUniqueNameTableValid();
+}
+
+void DWriteFontLookupTableBuilder::BuildFontUniqueNameTable() {
+  InitializeDirectWrite();
+
+  base::TimeTicks time_ticks = base::TimeTicks::Now();
+  blink::FontUniqueNameTable font_unique_name_table;
+  bool timed_out = false;
+
+  // The stored_for_platform_version_identifier proto field is used for
+  // persisting the table to disk and identifiying whether and update to the
+  // table is needed when loading it back. This functionality is not used on
+  // Windows, hence setting it to the empty string is sufficient.
+  font_unique_name_table.set_stored_for_platform_version_identifier("");
+
+  base::string16 windows_fonts_path = GetWindowsFontsPath();
+
+  for (UINT32 family_index = 0;
+       family_index < collection_->GetFontFamilyCount(); ++family_index) {
+    if (base::TimeTicks::Now() - time_ticks > kFontIndexingTimeout) {
+      timed_out = true;
+      break;
+    }
+
+    Microsoft::WRL::ComPtr<IDWriteFontFamily> family;
+    HRESULT hr = collection_->GetFontFamily(family_index, &family);
+    if (FAILED(hr)) {
+      return;
+    }
+    UINT32 font_count = family->GetFontCount();
+
+    for (UINT32 font_index = 0; font_index < font_count; ++font_index) {
+      Microsoft::WRL::ComPtr<IDWriteFont> font;
+      hr = family->GetFont(font_index, &font);
+      if (FAILED(hr))
+        return;
+
+      if (font->GetSimulations() != DWRITE_FONT_SIMULATIONS_NONE)
+        continue;
+
+      std::set<base::string16> path_set;
+      std::set<base::string16> custom_font_path_set;
+      uint32_t ttc_index = 0;
+      if (!AddFilesForFont(font.Get(), windows_fonts_path, &path_set,
+                           &custom_font_path_set, &ttc_index)) {
+        // It's possible to not be able to retrieve a font file for a font that
+        // is in the system font collection, see https://crbug.com/922183. If we
+        // were not able to retrieve a file for a registered font, we do not
+        // need to add it to the map.
+        continue;
+      }
+
+      // After having received clarification from Microsoft, the API is designed
+      // for allowing multiple files to be returned, if MS was to support a file
+      // format like Type1 fonts with this API, but for now only ever returns 1
+      // font file as only TrueType / OpenType fonts are supported.
+      CHECK_EQ(path_set.size() + custom_font_path_set.size(), 1u);
+      // If this font is placed in a custom font path location, we pass it to
+      // Blink, and we'll track with UMA there if such a font path is matched
+      // and used. If this happens more than very rarely, we will need to add an
+      // out-of-process loading mechanism for loading those uniquely matched
+      // font files.
+      base::FilePath file_path(path_set.size() ? *path_set.begin()
+                                               : *custom_font_path_set.begin());
+      CHECK(!file_path.empty());
+
+      // Add file entry to map.
+      blink::FontUniqueNameTable_UniqueFont* added_unique_font =
+          font_unique_name_table.add_fonts();
+      added_unique_font->set_file_path(file_path.AsUTF8Unsafe());
+      added_unique_font->set_ttc_index(ttc_index);
+
+      int added_font_index = font_unique_name_table.fonts_size() - 1;
+
+      auto extract_and_append_names =
+          [&font_unique_name_table, &hr, &font, &added_font_index](
+              DWRITE_INFORMATIONAL_STRING_ID font_info_string_id) {
+            // Now get names, and make them point to the added font.
+            IDWriteLocalizedStrings* font_id_keyed_names;
+            BOOL has_id_keyed_names;
+            hr = font->GetInformationalStrings(
+                font_info_string_id, &font_id_keyed_names, &has_id_keyed_names);
+            if (FAILED(hr) || !has_id_keyed_names)
+              return;
+
+            std::vector<std::string> extracted_names;
+            ExtractCaseFoldedLocalizedStrings(font_id_keyed_names,
+                                              &extracted_names);
+            for (auto& extracted_name : extracted_names) {
+              blink::FontUniqueNameTable_UniqueNameToFontMapping* name_mapping =
+                  font_unique_name_table.add_name_map();
+              name_mapping->set_font_name(extracted_name);
+              name_mapping->set_font_index(added_font_index);
+            }
+          };
+
+      extract_and_append_names(DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_NAME);
+      extract_and_append_names(DWRITE_INFORMATIONAL_STRING_FULL_NAME);
+    }
+
+    if (UNLIKELY(slow_down_indexing_for_testing_))
+      base::PlatformThread::Sleep(kIndexingSlowDownForTesting);
+  }
+
+  if (timed_out) {
+    LOG(ERROR) << "Creating unique font lookup table timed out, emptying "
+                  "partial table.";
+    font_unique_name_table.clear_fonts();
+    font_unique_name_table.clear_name_map();
+  }
+
+  // Sort names for using binary search on this proto in FontTableMatcher.
+  std::sort(font_unique_name_table.mutable_name_map()->begin(),
+            font_unique_name_table.mutable_name_map()->end(),
+            [](const blink::FontUniqueNameTable_UniqueNameToFontMapping& a,
+               const blink::FontUniqueNameTable_UniqueNameToFontMapping& b) {
+              return a.font_name() < b.font_name();
+            });
+
+  font_table_memory_ = base::ReadOnlySharedMemoryRegion::Create(
+      font_unique_name_table.ByteSizeLong());
+  if (!IsFontUniqueNameTableValid()) {
+    return;
+  }
+
+  if (!font_unique_name_table.SerializeToArray(
+          font_table_memory_.mapping.memory(),
+          font_table_memory_.mapping.size())) {
+    font_table_memory_ = base::MappedReadOnlyRegion();
+    return;
+  }
+
+  UMA_HISTOGRAM_TIMES("DirectWrite.Fonts.Proxy.LookupTableBuildTime",
+                      base::TimeTicks::Now() - time_ticks);
+  // The size is usually tens of kilobytes, ~50kb on a standard Windows 10
+  // installation, 1MB should be a more than high enough upper limit.
+  UMA_HISTOGRAM_CUSTOM_COUNTS("DirectWrite.Fonts.Proxy.LookupTableSize",
+                              font_table_memory_.mapping.size() / 1024, 1, 1000,
+                              50);
+}
+
+void DWriteFontLookupTableBuilder::SetSlowDownIndexingForTesting(
+    bool slow_down) {
+  slow_down_indexing_for_testing_ = slow_down;
+}
+
+void DWriteFontLookupTableBuilder::ResetLookupTableForTesting() {
+  font_table_memory_ = base::MappedReadOnlyRegion();
+}
+
+DWriteFontLookupTableBuilder* DWriteFontLookupTableBuilder::GetInstance() {
+  static base::NoDestructor<DWriteFontLookupTableBuilder> instance;
+  return instance.get();
+}
+
+}  // namespace content
diff --git a/content/browser/renderer_host/dwrite_font_lookup_table_builder_win.h b/content/browser/renderer_host/dwrite_font_lookup_table_builder_win.h
new file mode 100644
index 0000000..a004c5a
--- /dev/null
+++ b/content/browser/renderer_host/dwrite_font_lookup_table_builder_win.h
@@ -0,0 +1,75 @@
+// Copyright 2019 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.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_DWRITE_FONT_LOOKUP_TABLE_BUILDER_WIN_H_
+#define CONTENT_BROWSER_RENDERER_HOST_DWRITE_FONT_LOOKUP_TABLE_BUILDER_WIN_H_
+
+#include <dwrite.h>
+#include <dwrite_2.h>
+#include <wrl.h>
+
+#include "base/macros.h"
+#include "base/memory/read_only_shared_memory_region.h"
+#include "base/memory/singleton.h"
+#include "base/synchronization/waitable_event.h"
+#include "content/common/content_export.h"
+
+namespace base {
+template <typename T>
+class NoDestructor;
+}
+
+namespace content {
+
+// Singleton class which encapsulates building the font unique name table lookup
+// once, then serving the built table as a ReadOnlySharedMemoryRegion. Receives
+// requests for accessing this table from DWriteFontProxyImpl after Mojo IPC
+// calls from the renderer.
+class CONTENT_EXPORT DWriteFontLookupTableBuilder {
+ public:
+  static DWriteFontLookupTableBuilder* GetInstance();
+  void SetSlowDownIndexingForTesting(bool);
+  // Needed to trigger rebuilding the lookup table, when testing using
+  // slowed-down indexing. Otherwise, the test methods would use the already
+  // cached lookup table.
+  void ResetLookupTableForTesting();
+
+  // Retrieve the prepared memory region if it is available.
+  // EnsureFontUniqueNameTable() should be checked before. This method hits an
+  // assertion otherwise.
+  base::ReadOnlySharedMemoryRegion DuplicatedMemoryRegion();
+
+  // Wait for the internal WaitableEvent to be signaled if needed and return
+  // true if the font unique name lookup table was successfully constructed.
+  bool EnsureFontUniqueNameTable();
+
+ private:
+  friend class base::NoDestructor<DWriteFontLookupTableBuilder>;
+
+  void BuildFontUniqueNameTable();
+
+  bool IsFontUniqueNameTableValid();
+
+  // Checks if the unique font table has been built already, and if not, builds
+  // it by enumerating fonts from the collection, extracting their file
+  // locations, ttc indices and names.
+  void InitializeDirectWrite();
+  DWriteFontLookupTableBuilder();
+  ~DWriteFontLookupTableBuilder();
+
+  // This can only be replaced from the construction sequence. Once
+  // font_table_built_ is signaled, it can be read from everywhere.
+  base::MappedReadOnlyRegion font_table_memory_;
+
+  bool direct_write_initialized_ = false;
+  Microsoft::WRL::ComPtr<IDWriteFontCollection> collection_;
+  Microsoft::WRL::ComPtr<IDWriteFactory2> factory2_;
+  bool slow_down_indexing_for_testing_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(DWriteFontLookupTableBuilder);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_RENDERER_HOST_DWRITE_FONT_LOOKUP_TABLE_BUILDER_WIN_H_
diff --git a/content/browser/renderer_host/dwrite_font_lookup_table_builder_win_unittest.cc b/content/browser/renderer_host/dwrite_font_lookup_table_builder_win_unittest.cc
new file mode 100644
index 0000000..0f35ed6
--- /dev/null
+++ b/content/browser/renderer_host/dwrite_font_lookup_table_builder_win_unittest.cc
@@ -0,0 +1,81 @@
+// Copyright 2019 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_lookup_table_builder_win.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/files/file.h"
+#include "base/test/scoped_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/font_unique_name_lookup/font_table_matcher.h"
+
+namespace content {
+
+namespace {
+
+std::vector<std::pair<std::string, uint32_t>> expected_test_fonts = {
+    {u8"CambriaMath", 1},
+    {u8"Ming-Lt-HKSCS-ExtB", 2},
+    {u8"NSimSun", 1},
+    {u8"calibri-bolditalic", 0}};
+
+class DWriteFontLookupTableBuilderTest : public testing::Test {
+ public:
+  DWriteFontLookupTableBuilderTest() = default;
+
+ private:
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+};
+
+}  // namespace
+
+// Run a test similar to DWriteFontProxyImplUnitTest, TestFindUniqueFont but
+// without going through Mojo and running it on the DWRiteFontLookupTableBuilder
+// class directly.
+TEST_F(DWriteFontLookupTableBuilderTest, TestFindUniqueFontDirect) {
+  DWriteFontLookupTableBuilder* font_lookup_table_builder =
+      DWriteFontLookupTableBuilder::GetInstance();
+  font_lookup_table_builder->EnsureFontUniqueNameTable();
+  base::ReadOnlySharedMemoryRegion font_table_memory =
+      font_lookup_table_builder->DuplicatedMemoryRegion();
+  blink::FontTableMatcher font_table_matcher(font_table_memory.Map());
+
+  for (auto& test_font_name_index : expected_test_fonts) {
+    base::Optional<blink::FontTableMatcher::MatchResult> match_result =
+        font_table_matcher.MatchName(test_font_name_index.first);
+    CHECK(match_result) << "No font matched for font name: "
+                        << test_font_name_index.first;
+    base::File unique_font_file(
+        base::FilePath::FromUTF8Unsafe(match_result->font_path),
+        base::File::FLAG_OPEN | base::File::FLAG_READ);
+    CHECK(unique_font_file.IsValid());
+    CHECK_GT(unique_font_file.GetLength(), 0);
+    CHECK_EQ(test_font_name_index.second, match_result->ttc_index);
+  }
+}
+
+TEST_F(DWriteFontLookupTableBuilderTest, TestTimeout) {
+  DWriteFontLookupTableBuilder* font_lookup_table_builder =
+      DWriteFontLookupTableBuilder::GetInstance();
+  font_lookup_table_builder->ResetLookupTableForTesting();
+  font_lookup_table_builder->SetSlowDownIndexingForTesting(true);
+  font_lookup_table_builder->EnsureFontUniqueNameTable();
+  base::ReadOnlySharedMemoryRegion font_table_memory =
+      font_lookup_table_builder->DuplicatedMemoryRegion();
+  blink::FontTableMatcher font_table_matcher(font_table_memory.Map());
+
+  for (auto& test_font_name_index : expected_test_fonts) {
+    base::Optional<blink::FontTableMatcher::MatchResult> match_result =
+        font_table_matcher.MatchName(test_font_name_index.first);
+    CHECK(!match_result);
+  }
+  // Need to reset the table again so that it can be rebuilt successfully
+  // without the artificial timeout when running the next test.
+  font_lookup_table_builder->ResetLookupTableForTesting();
+}
+
+}  // namespace content
diff --git a/content/browser/renderer_host/dwrite_font_proxy_impl_win.cc b/content/browser/renderer_host/dwrite_font_proxy_impl_win.cc
index 4038aaa..0ff0007 100644
--- a/content/browser/renderer_host/dwrite_font_proxy_impl_win.cc
+++ b/content/browser/renderer_host/dwrite_font_proxy_impl_win.cc
@@ -111,54 +111,10 @@
   return true;
 }
 
-// The unresponsive renderer timeout is 30 seconds (kDefaultCommitTimeout). As a
-// starting point, let's set the max time for indexing fonts to a third of that,
-// 10 seconds and record a UMA histogram for how long indexing usually
-// takes. Once we have UMA data, we can look into reducing this timeout. This
-// timeout is meant to cover pathological cases of font indexing where a Windows
-// installation has an unusually large collection of fonts. In practice,
-// building the unique font name table should not take longer than tens of
-// milliseconds (~26 ms on a developer machine, Windows 10, default fonts).
-const base::TimeDelta kFontIndexingTimeout = base::TimeDelta::FromSeconds(10);
-
-const base::TimeDelta kIndexingSlowDownForTesting =
-    base::TimeDelta::FromMilliseconds(1200);
-
-bool extract_case_folded_localized_strings(
-    IDWriteLocalizedStrings* dwrite_localized_strings,
-    std::vector<std::string>* localized_strings) {
-  if (!dwrite_localized_strings->GetCount())
-    return false;
-
-  localized_strings->clear();
-  localized_strings->reserve(dwrite_localized_strings->GetCount());
-  for (UINT32 j = 0; j < dwrite_localized_strings->GetCount(); ++j) {
-    UINT32 length;
-    HRESULT hr = dwrite_localized_strings->GetStringLength(j, &length);
-    if (FAILED(hr))
-      continue;
-    std::wstring localized_name;
-    localized_name.resize(length + 1);
-    hr = dwrite_localized_strings->GetString(j, &localized_name[0], length + 1);
-    if (FAILED(hr)) {
-      continue;
-    }
-    localized_name.resize(length);
-    // The documentation for the API call does not specify an encoding but the
-    // results are wchar_t and FireFox considers them UTF-16, as seen here:
-    // https://dxr.mozilla.org/mozilla-central/source/gfx/thebes/gfxDWriteFontList.cpp#90
-    // so we'll assume that.
-    localized_strings->push_back(base::UTF16ToUTF8(
-        base::i18n::FoldCase(base::string16(localized_name))));
-  }
-  return true;
-}
-
 }  // namespace
 
 DWriteFontProxyImpl::DWriteFontProxyImpl()
-    : windows_fonts_path_(GetWindowsFontsPath()),
-      slow_down_indexing_for_testing_(false) {}
+    : windows_fonts_path_(GetWindowsFontsPath()) {}
 
 DWriteFontProxyImpl::~DWriteFontProxyImpl() = default;
 
@@ -174,10 +130,6 @@
   windows_fonts_path_.swap(path);
 }
 
-void DWriteFontProxyImpl::SetSlowDownIndexingForTesting(bool slow_down) {
-  slow_down_indexing_for_testing_ = slow_down;
-}
-
 void DWriteFontProxyImpl::FindFamily(const base::string16& family_name,
                                      FindFamilyCallback callback) {
   InitializeDirectWrite();
@@ -439,171 +391,17 @@
   DCHECK_GT(result->mapped_length, 0u);
 }
 
-bool DWriteFontProxyImpl::IsFontUniqueNameTableValid() {
-  return font_unique_name_table_memory_.IsValid() &&
-         font_unique_name_table_memory_.mapping.size();
-}
-
-bool DWriteFontProxyImpl::EnsureFontUniqueNameTable() {
-  if (IsFontUniqueNameTableValid())
-    return true;
-
-  base::TimeTicks time_ticks = base::TimeTicks::Now();
-
-  blink::FontUniqueNameTable font_unique_name_table;
-
-  bool timed_out = false;
-
-  // The stored_for_platform_version_identifier proto field is used for
-  // persisting the table to disk and identifiying whether and update to the
-  // table is needed when loading it back. This functionality is not used on
-  // Windows, hence setting it to the empty string is sufficient.
-  font_unique_name_table.set_stored_for_platform_version_identifier("");
-
-  for (UINT32 family_index = 0;
-       family_index < collection_->GetFontFamilyCount(); ++family_index) {
-    if (base::TimeTicks::Now() - time_ticks > kFontIndexingTimeout) {
-      timed_out = true;
-      break;
-    }
-
-    mswr::ComPtr<IDWriteFontFamily> family;
-    HRESULT hr = collection_->GetFontFamily(family_index, &family);
-    if (FAILED(hr))
-      return false;
-    UINT32 font_count = family->GetFontCount();
-
-    for (UINT32 font_index = 0; font_index < font_count; ++font_index) {
-      mswr::ComPtr<IDWriteFont> font;
-      hr = family->GetFont(font_index, &font);
-      if (FAILED(hr)) {
-        if (IsLastResortFallbackFont(family_index))
-          LogMessageFilterError(
-              MessageFilterError::LAST_RESORT_FONT_GET_FONT_FAILED);
-        return false;
-      }
-
-      if (font->GetSimulations() != DWRITE_FONT_SIMULATIONS_NONE)
-        continue;
-
-      std::set<base::string16> path_set;
-      std::set<base::string16> custom_font_path_set;
-      uint32_t ttc_index = 0;
-      if (!AddFilesForFont(font.Get(), windows_fonts_path_, &path_set,
-                           &custom_font_path_set, &ttc_index)) {
-        if (IsLastResortFallbackFont(family_index))
-          LogMessageFilterError(
-              MessageFilterError::LAST_RESORT_FONT_ADD_FILES_FAILED);
-
-        // It's possible to not be able to retrieve a font file for a font that
-        // is in the system font collection, see https://crbug.com/922183. If we
-        // were not able to retrieve a file for a registered font, we do not
-        // need to add it to the map.
-        continue;
-      }
-
-      // After having received clarification from Microsoft, the API is designed
-      // for allowing multiple files to be returned, if MS was to support a file
-      // format like Type1 fonts with this API, but for now only ever returns 1
-      // font file as only TrueType / OpenType fonts are supported.
-      CHECK_EQ(path_set.size() + custom_font_path_set.size(), 1u);
-      // If this font is placed in a custom font path location, we pass it to
-      // Blink, and we'll track with UMA there if such a font path is matched
-      // and used. If this happens more than very rarely, we will need to add an
-      // out-of-process loading mechanism for loading those uniquely matched
-      // font files.
-      base::FilePath file_path(path_set.size() ? *path_set.begin()
-                                               : *custom_font_path_set.begin());
-      CHECK(!file_path.empty());
-
-      // Add file entry to map.
-      blink::FontUniqueNameTable_UniqueFont* added_unique_font =
-          font_unique_name_table.add_fonts();
-      added_unique_font->set_file_path(file_path.AsUTF8Unsafe());
-      added_unique_font->set_ttc_index(ttc_index);
-
-      int added_font_index = font_unique_name_table.fonts_size() - 1;
-
-      auto extract_and_append_names =
-          [&font_unique_name_table, &hr, &font, &added_font_index](
-              DWRITE_INFORMATIONAL_STRING_ID font_info_string_id) {
-            // Now get names, and make them point to the added font.
-            IDWriteLocalizedStrings* font_id_keyed_names;
-            BOOL has_id_keyed_names;
-            hr = font->GetInformationalStrings(
-                font_info_string_id, &font_id_keyed_names, &has_id_keyed_names);
-            if (FAILED(hr) || !has_id_keyed_names)
-              return;
-
-            std::vector<std::string> extracted_names;
-            extract_case_folded_localized_strings(font_id_keyed_names,
-                                                  &extracted_names);
-            for (auto& extracted_name : extracted_names) {
-              blink::FontUniqueNameTable_UniqueNameToFontMapping* name_mapping =
-                  font_unique_name_table.add_name_map();
-              name_mapping->set_font_name(extracted_name);
-              name_mapping->set_font_index(added_font_index);
-            }
-          };
-
-      extract_and_append_names(DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_NAME);
-      extract_and_append_names(DWRITE_INFORMATIONAL_STRING_FULL_NAME);
-    }
-
-    if (UNLIKELY(slow_down_indexing_for_testing_))
-      base::PlatformThread::Sleep(kIndexingSlowDownForTesting);
-  }
-
-  if (timed_out) {
-    LOG(ERROR) << "Creating unique font lookup table timed out, emptying "
-                  "partial table.";
-    font_unique_name_table.clear_fonts();
-    font_unique_name_table.clear_name_map();
-  }
-
-  // Sort names for using binary search on this proto in FontTableMatcher.
-  std::sort(font_unique_name_table.mutable_name_map()->begin(),
-            font_unique_name_table.mutable_name_map()->end(),
-            [](const blink::FontUniqueNameTable_UniqueNameToFontMapping& a,
-               const blink::FontUniqueNameTable_UniqueNameToFontMapping& b) {
-              return a.font_name() < b.font_name();
-            });
-
-  font_unique_name_table_memory_ = base::ReadOnlySharedMemoryRegion::Create(
-      font_unique_name_table.ByteSizeLong());
-  if (!IsFontUniqueNameTableValid())
-    return false;
-
-  if (!font_unique_name_table.SerializeToArray(
-          font_unique_name_table_memory_.mapping.memory(),
-          font_unique_name_table_memory_.mapping.size())) {
-    font_unique_name_table_memory_ = base::MappedReadOnlyRegion();
-    return false;
-  }
-
-  UMA_HISTOGRAM_TIMES("DirectWrite.Fonts.Proxy.LookupTableBuildTime",
-                      base::TimeTicks::Now() - time_ticks);
-  // The size is usually tens of kilobytes, ~50kb on a standard Windows 10
-  // installation, 1MB should be a more than high enough upper limit.
-  UMA_HISTOGRAM_CUSTOM_COUNTS(
-      "DirectWrite.Fonts.Proxy.LookupTableSize",
-      font_unique_name_table_memory_.mapping.size() / 1024, 1, 1000, 50);
-
-  return true;
-}
-
 void DWriteFontProxyImpl::GetUniqueNameLookupTable(
     GetUniqueNameLookupTableCallback callback) {
   InitializeDirectWrite();
   callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
       std::move(callback), base::ReadOnlySharedMemoryRegion());
-  if (!collection_)
+
+  if (!DWriteFontLookupTableBuilder::GetInstance()->EnsureFontUniqueNameTable())
     return;
 
-  if (!EnsureFontUniqueNameTable())
-    return;
-
-  std::move(callback).Run(font_unique_name_table_memory_.region.Duplicate());
+  std::move(callback).Run(
+      DWriteFontLookupTableBuilder::GetInstance()->DuplicatedMemoryRegion());
 }
 
 void DWriteFontProxyImpl::InitializeDirectWrite() {
diff --git a/content/browser/renderer_host/dwrite_font_proxy_impl_win.h b/content/browser/renderer_host/dwrite_font_proxy_impl_win.h
index 6912e98..db77d04 100644
--- a/content/browser/renderer_host/dwrite_font_proxy_impl_win.h
+++ b/content/browser/renderer_host/dwrite_font_proxy_impl_win.h
@@ -17,6 +17,7 @@
 #include "base/memory/read_only_shared_memory_region.h"
 #include "base/memory/ref_counted.h"
 #include "base/strings/string16.h"
+#include "content/browser/renderer_host/dwrite_font_lookup_table_builder_win.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/browser_message_filter.h"
 #include "content/public/browser/browser_thread.h"
@@ -41,7 +42,6 @@
                      const service_manager::BindSourceInfo& source_info);
 
   void SetWindowsFontsPathForTesting(base::string16 path);
-  void SetSlowDownIndexingForTesting(bool);
 
  protected:
   // blink::mojom::DWriteFontProxy:
@@ -65,20 +65,14 @@
   void InitializeDirectWrite();
 
  private:
-  // Checks if the unique font table has been built already, and if not, builds
-  // it by enumerating fonts from the collection, extracting their file
-  // locations, ttc indices and names.
-  bool EnsureFontUniqueNameTable();
   bool IsLastResortFallbackFont(uint32_t font_index);
 
  private:
-  bool IsFontUniqueNameTableValid();
   bool direct_write_initialized_ = false;
   Microsoft::WRL::ComPtr<IDWriteFontCollection> collection_;
   Microsoft::WRL::ComPtr<IDWriteFactory2> factory2_;
   Microsoft::WRL::ComPtr<IDWriteFontFallback> font_fallback_;
   base::string16 windows_fonts_path_;
-  bool slow_down_indexing_for_testing_;
   base::MappedReadOnlyRegion font_unique_name_table_memory_;
 
   // Temp code to help track down crbug.com/561873
diff --git a/content/browser/renderer_host/dwrite_font_proxy_impl_win_unittest.cc b/content/browser/renderer_host/dwrite_font_proxy_impl_win_unittest.cc
index 3e9b88e..b35adbfc 100644
--- a/content/browser/renderer_host/dwrite_font_proxy_impl_win_unittest.cc
+++ b/content/browser/renderer_host/dwrite_font_proxy_impl_win_unittest.cc
@@ -210,18 +210,6 @@
   }
 }
 
-TEST_F(DWriteFontProxyImplUnitTest, TestFontIndexingTimeout) {
-  impl_.SetSlowDownIndexingForTesting(true);
-  base::ReadOnlySharedMemoryRegion font_table_memory;
-  dwrite_font_proxy().GetUniqueNameLookupTable(&font_table_memory);
-  blink::FontTableMatcher font_table_matcher(font_table_memory.Map());
-
-  for (auto& test_font_name_index : expected_test_fonts) {
-    base::Optional<blink::FontTableMatcher::MatchResult> match_result =
-        font_table_matcher.MatchName(test_font_name_index.first);
-    CHECK(!match_result);
-  }
-}
 
 }  // namespace
 
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index ae952983..8542e73 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1546,6 +1546,7 @@
     "../browser/presentation/presentation_service_impl_unittest.cc",
     "../browser/renderer_host/clipboard_host_impl_unittest.cc",
     "../browser/renderer_host/cursor_manager_unittest.cc",
+    "../browser/renderer_host/dwrite_font_lookup_table_builder_win_unittest.cc",
     "../browser/renderer_host/dwrite_font_proxy_impl_win_unittest.cc",
     "../browser/renderer_host/embedded_frame_sink_provider_impl_unittest.cc",
     "../browser/renderer_host/frame_token_message_queue_unittest.cc",