Bug 1375978 - enumerate fonts asynchronously; r=jfkthame,nhnt11
authorMyk Melez <myk@mykzilla.org>
Tue, 12 Sep 2017 11:05:37 -0700
changeset 429814 f255ec4e8c361e4526b7bfb1083cecb41fabbdd1
parent 429813 e58e11f74cc02a5aec2b4588190385b1980654e0
child 429815 246cba2233e3103ac3e0f160c40ccec9e6946ec9
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjfkthame, nhnt11
bugs1375978
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1375978 - enumerate fonts asynchronously; r=jfkthame,nhnt11
browser/components/preferences/fonts.js
browser/components/preferences/in-content/main.js
browser/components/preferences/in-content/tests/browser_basic_rebuild_fonts_test.js
browser/themes/shared/incontentprefs/preferences.inc.css
gfx/src/nsIFontEnumerator.idl
gfx/src/nsThebesFontEnumerator.cpp
gfx/thebes/gfxMacPlatformFontList.mm
gfx/thebes/gfxPlatformFontList.cpp
gfx/thebes/gfxPlatformFontList.h
toolkit/mozapps/preferences/fontbuilder.js
--- a/browser/components/preferences/fonts.js
+++ b/browser/components/preferences/fonts.js
@@ -14,17 +14,28 @@ const kFontNameFmtMonospace     = "font.
 const kFontNameListFmtSerif     = "font.name-list.serif.%LANG%";
 const kFontNameListFmtSansSerif = "font.name-list.sans-serif.%LANG%";
 const kFontNameListFmtMonospace = "font.name-list.monospace.%LANG%";
 const kFontSizeFmtVariable      = "font.size.variable.%LANG%";
 const kFontSizeFmtFixed         = "font.size.fixed.%LANG%";
 const kFontMinSizeFmt           = "font.minimum-size.%LANG%";
 
 var gFontsDialog = {
-  _selectLanguageGroup(aLanguageGroup) {
+  _selectLanguageGroupPromise: Promise.resolve(),
+
+  async _selectLanguageGroup(aLanguageGroup) {
+    // Avoid overlapping language group selections by awaiting the resolution
+    // of the previous one.  We do this because this function is re-entrant,
+    // as inserting <preference> elements into the DOM sometimes triggers a call
+    // back into this function.  And since this function is also asynchronous,
+    // that call can enter this function before the previous run has completed,
+    // which would corrupt the font menulists.  Awaiting the previous call's
+    // resolution avoids that fate.
+    await this._selectLanguageGroupPromise;
+
     var prefs = [{ format: kDefaultFontType,          type: "string",   element: "defaultFontType", fonttype: null},
                  { format: kFontNameFmtSerif,         type: "fontname", element: "serif",      fonttype: "serif"       },
                  { format: kFontNameFmtSansSerif,     type: "fontname", element: "sans-serif", fonttype: "sans-serif"  },
                  { format: kFontNameFmtMonospace,     type: "fontname", element: "monospace",  fonttype: "monospace"   },
                  { format: kFontNameListFmtSerif,     type: "unichar",  element: null,         fonttype: "serif"       },
                  { format: kFontNameListFmtSansSerif, type: "unichar",  element: null,         fonttype: "sans-serif"  },
                  { format: kFontNameListFmtMonospace, type: "unichar",  element: null,         fonttype: "monospace"   },
                  { format: kFontSizeFmtVariable,      type: "int",      element: "sizeVar",    fonttype: null          },
@@ -45,26 +56,32 @@ var gFontsDialog = {
       if (!prefs[i].element)
         continue;
 
       var element = document.getElementById(prefs[i].element);
       if (element) {
         element.setAttribute("preference", preference.id);
 
         if (prefs[i].fonttype)
-          FontBuilder.buildFontList(aLanguageGroup, prefs[i].fonttype, element);
+          await FontBuilder.buildFontList(aLanguageGroup, prefs[i].fonttype, element);
 
         preference.setElementValue(element);
       }
     }
   },
 
+  _safelySelectLanguageGroup(aLanguageGroup) {
+    this._selectLanguageGroupPromise =
+      this._selectLanguageGroup(aLanguageGroup)
+        .catch(Components.utils.reportError);
+  },
+
   readFontLanguageGroup() {
     var languagePref = document.getElementById("font.language.group");
-    this._selectLanguageGroup(languagePref.value);
+    this._safelySelectLanguageGroup(languagePref.value);
     return undefined;
   },
 
   readUseDocumentFonts() {
     var preference = document.getElementById("browser.display.use_document_fonts");
     return preference.value == 1;
   },
 
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -992,18 +992,17 @@ var gMainPane = {
    */
   _rebuildFonts() {
     var preferences = document.getElementById("mainPreferences");
     // Ensure preferences are "visible" to ensure bindings work.
     preferences.hidden = false;
     // Force flush:
     preferences.clientHeight;
     var langGroupPref = document.getElementById("font.language.group");
-    this._selectDefaultLanguageGroup(langGroupPref.value,
-      this._readDefaultFontTypeForLanguage(langGroupPref.value) == "serif");
+    this._safelySelectDefaultLanguageGroup(langGroupPref.value);
   },
 
   /**
    * Returns the type of the current default font for the language denoted by
    * aLanguageGroup.
    */
   _readDefaultFontTypeForLanguage(aLanguageGroup) {
     const kDefaultFontType = "font.default.%LANG%";
@@ -1015,17 +1014,28 @@ var gMainPane = {
       preference.setAttribute("name", defaultFontTypePref);
       preference.setAttribute("type", "string");
       preference.setAttribute("onchange", "gMainPane._rebuildFonts();");
       document.getElementById("mainPreferences").appendChild(preference);
     }
     return preference.value;
   },
 
-  _selectDefaultLanguageGroup(aLanguageGroup, aIsSerif) {
+  _selectDefaultLanguageGroupPromise: Promise.resolve(),
+
+  async _selectDefaultLanguageGroup(aLanguageGroup, aIsSerif) {
+    // Avoid overlapping language group selections by awaiting the resolution
+    // of the previous one.  We do this because this function is re-entrant,
+    // as inserting <preference> elements into the DOM sometimes triggers a call
+    // back into this function.  And since this function is also asynchronous,
+    // that call can enter this function before the previous run has completed,
+    // which would corrupt the font menulists.  Awaiting the previous call's
+    // resolution avoids that fate.
+    await this._selectDefaultLanguageGroupPromise;
+
     const kFontNameFmtSerif = "font.name.serif.%LANG%";
     const kFontNameFmtSansSerif = "font.name.sans-serif.%LANG%";
     const kFontNameListFmtSerif = "font.name-list.serif.%LANG%";
     const kFontNameListFmtSansSerif = "font.name-list.sans-serif.%LANG%";
     const kFontSizeFmtVariable = "font.size.variable.%LANG%";
 
     var preferences = document.getElementById("mainPreferences");
     var prefs = [{
@@ -1060,23 +1070,30 @@ var gMainPane = {
       if (!prefs[i].element)
         continue;
 
       var element = document.getElementById(prefs[i].element);
       if (element) {
         element.setAttribute("preference", preference.id);
 
         if (prefs[i].fonttype)
-          FontBuilder.buildFontList(aLanguageGroup, prefs[i].fonttype, element);
+          await FontBuilder.buildFontList(aLanguageGroup, prefs[i].fonttype, element);
 
         preference.setElementValue(element);
       }
     }
   },
 
+  _safelySelectDefaultLanguageGroup(aLanguageGroup) {
+    var isSerif = this._readDefaultFontTypeForLanguage(aLanguageGroup) == "serif";
+    this._selectDefaultLanguageGroupPromise =
+      this._selectDefaultLanguageGroup(aLanguageGroup, isSerif)
+        .catch(Components.utils.reportError);
+  },
+
   /**
    * Stores the original value of the spellchecking preference to enable proper
    * restoration if unchanged (since we're mapping a tristate onto a checkbox).
    */
   _storedSpellCheck: 0,
 
   /**
    * Returns true if any spellchecking is enabled and false otherwise, caching
--- a/browser/components/preferences/in-content/tests/browser_basic_rebuild_fonts_test.js
+++ b/browser/components/preferences/in-content/tests/browser_basic_rebuild_fonts_test.js
@@ -1,16 +1,17 @@
 Services.prefs.setBoolPref("browser.preferences.instantApply", true);
 
 registerCleanupFunction(function() {
   Services.prefs.clearUserPref("browser.preferences.instantApply");
 });
 
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true });
+  await gBrowser.contentWindow.gMainPane._selectDefaultLanguageGroupPromise;
   let doc = gBrowser.contentDocument;
   var langGroup = Services.prefs.getComplexValue("font.language.group", Ci.nsIPrefLocalizedString).data
   is(doc.getElementById("font.language.group").value, langGroup,
      "Language group should be set correctly.");
 
   let defaultFontType = Services.prefs.getCharPref("font.default." + langGroup);
   let fontFamily = Services.prefs.getCharPref("font.name." + defaultFontType + "." + langGroup);
   let fontFamilyField = doc.getElementById("defaultFont");
@@ -24,87 +25,104 @@ add_task(async function() {
   doc.getElementById("advancedFonts").click();
   let win = await promiseSubDialogLoaded;
   doc = win.document;
 
   // Simulate a dumb font backend.
   win.FontBuilder._enumerator = {
     _list: ["MockedFont1", "MockedFont2", "MockedFont3"],
     _defaultFont: null,
-    EnumerateFonts(lang, type, list) {
-      return this._list;
+    EnumerateFontsAsync(lang, type) {
+      return Promise.resolve(this._list);
     },
-    EnumerateAllFonts() {
-      return this._list;
+    EnumerateAllFontsAsync() {
+      return Promise.resolve(this._list);
     },
     getDefaultFont() { return this._defaultFont; },
     getStandardFamilyName(name) { return name; },
   };
   win.FontBuilder._allFonts = null;
   win.FontBuilder._langGroupSupported = false;
 
   let langGroupElement = doc.getElementById("font.language.group");
   let selectLangsField = doc.getElementById("selectLangs");
   let serifField = doc.getElementById("serif");
   let armenian = "x-armn";
   let western = "x-western";
 
+  // Await rebuilding of the font lists, which happens asynchronously in
+  // gFontsDialog._selectLanguageGroup.  Testing code needs to call this
+  // function and await its resolution after changing langGroupElement's value
+  // (or doing anything else that triggers a call to _selectLanguageGroup).
+  function fontListsRebuilt() {
+    return win.gFontsDialog._selectLanguageGroupPromise;
+  }
+
   langGroupElement.value = armenian;
+  await fontListsRebuilt();
   selectLangsField.value = armenian;
   is(serifField.value, "", "Font family should not be set.");
 
   let armenianSerifElement = doc.getElementById("font.name.serif.x-armn");
 
   langGroupElement.value = western;
+  await fontListsRebuilt();
   selectLangsField.value = western;
 
   // Simulate a font backend supporting language-specific enumeration.
   // NB: FontBuilder has cached the return value from EnumerateAllFonts(),
   // so _allFonts will always have 3 elements regardless of subsequent
   // _list changes.
   win.FontBuilder._enumerator._list = ["MockedFont2"];
 
   langGroupElement.value = armenian;
+  await fontListsRebuilt();
   selectLangsField.value = armenian;
   is(serifField.value, "", "Font family should still be empty for indicating using 'default' font.");
 
   langGroupElement.value = western;
+  await fontListsRebuilt();
   selectLangsField.value = western;
 
   // Simulate a system that has no fonts for the specified language.
   win.FontBuilder._enumerator._list = [];
 
   langGroupElement.value = armenian;
+  await fontListsRebuilt();
   selectLangsField.value = armenian;
   is(serifField.value, "", "Font family should not be set.");
 
   // Setting default font to "MockedFont3".  Then, when serifField.value is
   // empty, it should indicate using "MockedFont3" but it shouldn't be saved
   // to "MockedFont3" in the pref.  It should be resolved at runtime.
   win.FontBuilder._enumerator._list = ["MockedFont1", "MockedFont2", "MockedFont3"];
   win.FontBuilder._enumerator._defaultFont = "MockedFont3";
   langGroupElement.value = armenian;
+  await fontListsRebuilt();
   selectLangsField.value = armenian;
   is(serifField.value, "", "Font family should be empty even if there is a default font.");
 
   armenianSerifElement.value = "MockedFont2";
   serifField.value = "MockedFont2";
   is(serifField.value, "MockedFont2", "Font family should be \"MockedFont2\" for now.");
 
   langGroupElement.value = western;
+  await fontListsRebuilt();
   selectLangsField.value = western;
   is(serifField.value, "", "Font family of other language should not be set.");
 
   langGroupElement.value = armenian;
+  await fontListsRebuilt();
   selectLangsField.value = armenian;
   is(serifField.value, "MockedFont2", "Font family should not be changed even after switching the language.");
 
   // If MochedFont2 is removed from the system, the value should be treated
   // as empty (i.e., 'default' font) after rebuilding the font list.
   win.FontBuilder._enumerator._list = ["MockedFont1", "MockedFont3"];
   win.FontBuilder._enumerator._allFonts = ["MockedFont1", "MockedFont3"];
   serifField.removeAllItems(); // This will cause rebuilding the font list from available fonts.
   langGroupElement.value = armenian;
+  await fontListsRebuilt();
   selectLangsField.value = armenian;
   is(serifField.value, "", "Font family should become empty due to the font uninstalled.");
 
   gBrowser.removeCurrentTab();
 });
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -424,19 +424,23 @@ separator.thin:not([orient="vertical"]) 
   -moz-box-flex: 1;
 }
 
 /**
  * End Dialog
  */
 
 /**
- * Font dialog menulist fixes
+ * Font group and font dialog menulist fixes
  */
 
+#defaultFont {
+  width: 0;
+}
+
 #defaultFontType,
 #serif,
 #sans-serif,
 #monospace {
   min-width: 30ch;
 }
 
 /**
--- a/gfx/src/nsIFontEnumerator.idl
+++ b/gfx/src/nsIFontEnumerator.idl
@@ -28,16 +28,37 @@ interface nsIFontEnumerator : nsISupport
    * @param  aCount     returns number of names returned
    * @param  aResult    returns array of names
    * @return void
    */
   void EnumerateFonts(in string aLangGroup, in string aGeneric,
     out uint32_t aCount, [retval, array, size_is(aCount)] out wstring aResult);
 
   /**
+   * Return a promise that resolves to a sorted array of the names of all
+   * installed fonts.
+   *
+   * @return Promise that resolves to Array
+   */
+  [implicit_jscontext]
+  jsval EnumerateAllFontsAsync();
+
+  /**
+   * Return a promise that resolves to a sorted array of names of fonts
+   * that support the given language group and are suitable for use as the given
+   * CSS generic font.
+   *
+   * @param  aLangGroup language group
+   * @param  aGeneric   CSS generic font
+   * @return Promise that resolves to Array
+   */
+  [implicit_jscontext]
+  jsval EnumerateFontsAsync(in string aLangGroup, in string aGeneric);
+
+  /**
     @param  aLangGroup language group
     @return bool do we have a font for this language group
    */
   void HaveFontFor(in string aLangGroup, [retval] out boolean aResult);
 
   /**
    * @param  aLangGroup language group
    * @param  aGeneric CSS generic font
--- a/gfx/src/nsThebesFontEnumerator.cpp
+++ b/gfx/src/nsThebesFontEnumerator.cpp
@@ -2,16 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsThebesFontEnumerator.h"
 #include <stdint.h>                     // for uint32_t
 #include "gfxPlatform.h"                // for gfxPlatform
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT_HELPER2
+#include "mozilla/dom/Promise.h"        // for mozilla::dom::Promise
 #include "nsCOMPtr.h"                   // for nsCOMPtr
 #include "nsDebug.h"                    // for NS_ENSURE_ARG_POINTER
 #include "nsError.h"                    // for NS_OK, NS_FAILED, nsresult
 #include "nsIAtom.h"                    // for nsIAtom, NS_Atomize
 #include "nsID.h"
 #include "nsMemory.h"                   // for nsMemory
 #include "nsString.h"               // for nsAutoCString, nsAutoString, etc
 #include "nsTArray.h"                   // for nsTArray, nsTArray_Impl, etc
@@ -71,16 +72,157 @@ nsThebesFontEnumerator::EnumerateFonts(c
     }
 
     *aResult = fs;
     *aCount = fontList.Length();
 
     return NS_OK;
 }
 
+struct EnumerateFontsPromise final
+{
+    explicit EnumerateFontsPromise(mozilla::dom::Promise* aPromise)
+        : mPromise(aPromise)
+    {
+        MOZ_ASSERT(aPromise);
+        MOZ_ASSERT(NS_IsMainThread());
+    }
+
+    RefPtr<mozilla::dom::Promise> mPromise;
+};
+
+class EnumerateFontsResult final : public Runnable
+{
+public:
+    EnumerateFontsResult(nsresult aRv,
+                         UniquePtr<EnumerateFontsPromise> aEnumerateFontsPromise,
+                         nsTArray<nsString> aFontList)
+        : Runnable("EnumerateFontsResult")
+        , mRv(aRv)
+        , mEnumerateFontsPromise(Move(aEnumerateFontsPromise))
+        , mFontList(aFontList)
+        , mWorkerThread(do_GetCurrentThread())
+    {
+        MOZ_ASSERT(!NS_IsMainThread());
+    }
+
+    NS_IMETHOD Run()
+    {
+        MOZ_ASSERT(NS_IsMainThread());
+
+        if (NS_FAILED(mRv)) {
+            mEnumerateFontsPromise->mPromise->MaybeReject(mRv);
+        } else {
+            mEnumerateFontsPromise->mPromise->MaybeResolve(mFontList);
+        }
+
+        mWorkerThread->Shutdown();
+
+        return NS_OK;
+    }
+
+private:
+    nsresult mRv;
+    UniquePtr<EnumerateFontsPromise> mEnumerateFontsPromise;
+    nsTArray<nsString> mFontList;
+    nsCOMPtr<nsIThread> mWorkerThread;
+};
+
+class EnumerateFontsTask final : public Runnable
+{
+public:
+    EnumerateFontsTask(nsIAtom* aLangGroupAtom,
+                       const nsAutoCString& aGeneric,
+                       UniquePtr<EnumerateFontsPromise> aEnumerateFontsPromise)
+        : Runnable("EnumerateFontsTask")
+        , mLangGroupAtom(aLangGroupAtom)
+        , mGeneric(aGeneric)
+        , mEnumerateFontsPromise(Move(aEnumerateFontsPromise))
+    {
+        MOZ_ASSERT(NS_IsMainThread());
+    }
+
+    NS_IMETHOD Run()
+    {
+        MOZ_ASSERT(!NS_IsMainThread());
+
+        nsTArray<nsString> fontList;
+
+        nsresult rv = gfxPlatform::GetPlatform()->
+            GetFontList(mLangGroupAtom, mGeneric, fontList);
+        nsCOMPtr<nsIRunnable> runnable = new EnumerateFontsResult(
+            rv, Move(mEnumerateFontsPromise), Move(fontList));
+        NS_DispatchToMainThread(runnable.forget());
+
+        return NS_OK;
+    }
+
+private:
+    nsCOMPtr<nsIAtom> mLangGroupAtom;
+    nsAutoCStringN<16> mGeneric;
+    UniquePtr<EnumerateFontsPromise> mEnumerateFontsPromise;
+};
+
+NS_IMETHODIMP
+nsThebesFontEnumerator::EnumerateAllFontsAsync(JSContext* aCx,
+                                               JS::MutableHandleValue aRval)
+{
+    return EnumerateFontsAsync(nullptr, nullptr, aCx, aRval);
+}
+
+NS_IMETHODIMP
+nsThebesFontEnumerator::EnumerateFontsAsync(const char* aLangGroup,
+                                            const char* aGeneric,
+                                            JSContext* aCx,
+                                            JS::MutableHandleValue aRval)
+{
+    MOZ_ASSERT(NS_IsMainThread());
+
+    nsCOMPtr<nsIGlobalObject> global =
+        xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx));
+    NS_ENSURE_TRUE(global, NS_ERROR_UNEXPECTED);
+
+    ErrorResult errv;
+    RefPtr<mozilla::dom::Promise> promise = dom::Promise::Create(global, errv);
+    if (errv.Failed()) {
+        return errv.StealNSResult();
+    }
+
+    auto enumerateFontsPromise = MakeUnique<EnumerateFontsPromise>(promise);
+
+    nsCOMPtr<nsIThread> thread;
+    nsresult rv = NS_NewNamedThread("FontEnumThread", getter_AddRefs(thread));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsIAtom> langGroupAtom;
+    if (aLangGroup) {
+        nsAutoCStringN<16> lowered;
+        lowered.Assign(aLangGroup);
+        ToLowerCase(lowered);
+        langGroupAtom = NS_Atomize(lowered);
+    }
+
+    nsAutoCString generic;
+    if (aGeneric) {
+        generic.Assign(aGeneric);
+    } else {
+        generic.SetIsVoid(true);
+    }
+
+    nsCOMPtr<nsIRunnable> runnable = new EnumerateFontsTask(
+        langGroupAtom, generic, Move(enumerateFontsPromise));
+    thread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+
+    if (!ToJSValue(aCx, promise, aRval)) {
+        return NS_ERROR_FAILURE;
+    }
+
+    return NS_OK;
+}
+
 NS_IMETHODIMP
 nsThebesFontEnumerator::HaveFontFor(const char *aLangGroup,
                                     bool *aResult)
 {
     NS_ENSURE_ARG_POINTER(aResult);
 
     *aResult = true;
     return NS_OK;
--- a/gfx/thebes/gfxMacPlatformFontList.mm
+++ b/gfx/thebes/gfxMacPlatformFontList.mm
@@ -45,16 +45,17 @@
 #import <AppKit/AppKit.h>
 
 #include "gfxPlatformMac.h"
 #include "gfxMacPlatformFontList.h"
 #include "gfxMacFont.h"
 #include "gfxUserFontSet.h"
 #include "harfbuzz/hb.h"
 
+#include "MainThreadUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsTArray.h"
 
 #include "nsDirectoryServiceUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsISimpleEnumerator.h"
 #include "nsCharTraits.h"
@@ -497,17 +498,21 @@ protected:
     double mSizeHint;
 };
 
 void
 gfxMacFontFamily::LocalizedName(nsAString& aLocalizedName)
 {
     nsAutoreleasePool localPool;
 
-    if (!HasOtherFamilyNames()) {
+    // It's unsafe to call HasOtherFamilyNames off the main thread because
+    // it entrains FindStyleVariations, which calls GetWeightOverride, which
+    // retrieves prefs.  And the pref names can change (via user overrides),
+    // so we can't use gfxPrefs to access them.
+    if (NS_IsMainThread() && !HasOtherFamilyNames()) {
         aLocalizedName = mName;
         return;
     }
 
     NSString *family = GetNSStringForString(mName);
     NSString *localized = [sFontManager
                            localizedNameForFamily:family
                                              face:nil];
--- a/gfx/thebes/gfxPlatformFontList.cpp
+++ b/gfx/thebes/gfxPlatformFontList.cpp
@@ -16,16 +16,17 @@
 #include "nsServiceManagerUtils.h"
 #include "nsUnicharUtils.h"
 #include "nsUnicodeRange.h"
 #include "nsUnicodeProperties.h"
 
 #include "mozilla/Attributes.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/Mutex.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/gfx/2D.h"
 
 #include <locale.h>
 
 using namespace mozilla;
@@ -171,18 +172,18 @@ gfxPlatformFontList::MemoryReporter::Col
             sizes.mFontTableCacheSize,
             "Memory used for cached font metrics and layout tables.");
     }
 
     return NS_OK;
 }
 
 gfxPlatformFontList::gfxPlatformFontList(bool aNeedFullnamePostscriptNames)
-    : mFontFamilies(64), mOtherFamilyNames(16),
-      mBadUnderlineFamilyNames(8), mSharedCmaps(8),
+    : mFontFamiliesMutex("gfxPlatformFontList::mFontFamiliesMutex"), mFontFamilies(64),
+      mOtherFamilyNames(16), mBadUnderlineFamilyNames(8), mSharedCmaps(8),
       mStartIndex(0), mIncrement(1), mNumFamilies(0), mFontlistInitCount(0),
       mFontFamilyWhitelistActive(false)
 {
     mOtherFamilyNamesInitialized = false;
 
     if (aNeedFullnamePostscriptNames) {
         mExtraNames = MakeUnique<ExtraNames>();
     }
@@ -281,16 +282,17 @@ gfxPlatformFontList::InitFontList()
     if (fontCache) {
         fontCache->AgeAllGenerations();
         fontCache->FlushShapedWordCaches();
     }
 
     gfxPlatform::PurgeSkiaFontCache();
 
     CancelInitOtherFamilyNamesTask();
+    MutexAutoLock lock(mFontFamiliesMutex);
     mFontFamilies.Clear();
     mOtherFamilyNames.Clear();
     mOtherFamilyNamesInitialized = false;
 
     if (mExtraNames) {
         mExtraNames->mFullnames.Clear();
         mExtraNames->mPostscriptNames.Clear();
     }
@@ -476,16 +478,17 @@ gfxPlatformFontList::UpdateFontList()
     RebuildLocalFonts();
 }
 
 void
 gfxPlatformFontList::GetFontList(nsIAtom *aLangGroup,
                                  const nsACString& aGenericFamily,
                                  nsTArray<nsString>& aListOfFonts)
 {
+    MutexAutoLock lock(mFontFamiliesMutex);
     for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) {
         RefPtr<gfxFontFamily>& family = iter.Data();
         if (family->FilterForFontList(aLangGroup, aGenericFamily)) {
             nsAutoString localizedFamilyName;
             family->LocalizedName(localizedFamilyName);
             aListOfFonts.AppendElement(localizedFamilyName);
         }
     }
--- a/gfx/thebes/gfxPlatformFontList.h
+++ b/gfx/thebes/gfxPlatformFontList.h
@@ -15,16 +15,17 @@
 #include "gfxFont.h"
 #include "gfxFontConstants.h"
 #include "gfxPlatform.h"
 #include "gfxFontFamilyList.h"
 
 #include "nsIMemoryReporter.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/Mutex.h"
 #include "mozilla/RangedArray.h"
 #include "nsLanguageAtomService.h"
 
 class CharMapHashKey : public PLDHashEntryHdr
 {
 public:
     typedef gfxCharacterMap* KeyType;
     typedef const gfxCharacterMap* KeyTypePointer;
@@ -511,16 +512,19 @@ protected:
     static size_t
     SizeOfFontEntryTableExcludingThis(const FontEntryTable& aTable,
                                       mozilla::MallocSizeOf aMallocSizeOf);
 
     // Platform-specific helper for GetDefaultFont(...).
     virtual gfxFontFamily*
     GetDefaultFontForPlatform(const gfxFontStyle* aStyle) = 0;
 
+    // Protects mFontFamilies.
+    mozilla::Mutex mFontFamiliesMutex;
+
     // canonical family name ==> family entry (unique, one name per family entry)
     FontFamilyTable mFontFamilies;
 
     // other family name ==> family entry (not unique, can have multiple names per
     // family entry, only names *other* than the canonical names are stored here)
     FontFamilyTable mOtherFamilyNames;
 
     // flag set after InitOtherFamilyNames is called upon first name lookup miss
--- a/toolkit/mozapps/preferences/fontbuilder.js
+++ b/toolkit/mozapps/preferences/fontbuilder.js
@@ -11,34 +11,34 @@ var FontBuilder = {
       this._enumerator = Components.classes["@mozilla.org/gfx/fontenumerator;1"]
                                    .createInstance(Components.interfaces.nsIFontEnumerator);
     }
     return this._enumerator;
   },
 
   _allFonts: null,
   _langGroupSupported: false,
-  buildFontList(aLanguage, aFontType, aMenuList) {
+  async buildFontList(aLanguage, aFontType, aMenuList) {
     // Reset the list
     while (aMenuList.hasChildNodes())
       aMenuList.firstChild.remove();
 
     var defaultFont = null;
     // Load Font Lists
-    var fonts = this.enumerator.EnumerateFonts(aLanguage, aFontType, { } );
+    var fonts = await this.enumerator.EnumerateFontsAsync(aLanguage, aFontType);
     if (fonts.length > 0)
       defaultFont = this.enumerator.getDefaultFont(aLanguage, aFontType);
     else {
-      fonts = this.enumerator.EnumerateFonts(aLanguage, "", { });
+      fonts = await this.enumerator.EnumerateFontsAsync(aLanguage, "");
       if (fonts.length > 0)
         defaultFont = this.enumerator.getDefaultFont(aLanguage, "");
     }
 
     if (!this._allFonts)
-      this._allFonts = this.enumerator.EnumerateAllFonts({});
+      this._allFonts = await this.enumerator.EnumerateAllFontsAsync({});
 
     // Build the UI for the Default Font and Fonts for this CSS type.
     var popup = document.createElement("menupopup");
     var separator;
     if (fonts.length > 0) {
       if (defaultFont) {
         var bundlePreferences = document.getElementById("bundlePreferences");
         var label = bundlePreferences.getFormattedString("labelDefaultFont", [defaultFont]);