Bug 1399206 - Port bug 1375978 to TB: enumerate fonts asynchronously. r=jorgk DONTBUILD
authorRichard Marti <richard.marti@gmail.com>
Tue, 12 Sep 2017 21:22:14 +0200
changeset 29701 e480213445a5f2c4f006092fe3481bdb61cad8a6
parent 29700 c16fe9415457932004c836f76d038321112ecc7d
child 29702 b41092b15ade90d66e56aa5e52e33a9ea5395361
push id378
push userclokep@gmail.com
push dateMon, 13 Nov 2017 18:45:35 +0000
reviewersjorgk
bugs1399206, 1375978
Bug 1399206 - Port bug 1375978 to TB: enumerate fonts asynchronously. r=jorgk DONTBUILD
mail/components/preferences/display.js
mail/components/preferences/fonts.js
--- a/mail/components/preferences/display.js
+++ b/mail/components/preferences/display.js
@@ -34,25 +34,34 @@ var gDisplayPane = {
   // FONTS
 
   /**
    * Populates the default font list in UI.
    */
   _rebuildFonts: function ()
   {
     var langGroupPref = document.getElementById("font.language.group");
-    this._selectDefaultLanguageGroup(langGroupPref.value,
-          this._readDefaultFontTypeForLanguage(langGroupPref.value) == "serif");
+    this._safelySelectDefaultLanguageGroup(langGroupPref.value);
   },
 
   /**
    * Select the default language group.
    */
-  _selectDefaultLanguageGroup: function (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%";
 
     // Make sure font.name-list is created before font.name so that it's
     // available at the time readFontSelection below is called.
@@ -87,25 +96,32 @@ var gDisplayPane = {
 
       var element = document.getElementById(prefs[i].element);
       if (element) {
         // Make sure we have the font list ready for readFontSelection below to
         // work. readFontSelection gets called at onsyncfrompreference, but the
         // exact semantics of when it is called (whether during setAttribute or
         // during setElementValue) aren't obvious.
         if (prefs[i].fonttype)
-          FontBuilder.buildFontList(aLanguageGroup, prefs[i].fonttype, element);
+          await FontBuilder.buildFontList(aLanguageGroup, prefs[i].fonttype, element);
 
         element.setAttribute("preference", preference.id);
 
         preference.setElementValue(element);
       }
     }
   },
 
+  _safelySelectDefaultLanguageGroup(aLanguageGroup) {
+    var isSerif = this._readDefaultFontTypeForLanguage(aLanguageGroup) == "serif";
+    this._selectDefaultLanguageGroupPromise =
+      this._selectDefaultLanguageGroup(aLanguageGroup, isSerif)
+        .catch(Components.utils.reportError);
+  },
+
   /**
    * Returns the type of the current default font for the language denoted by
    * aLanguageGroup.
    */
   _readDefaultFontTypeForLanguage: function (aLanguageGroup)
   {
     const kDefaultFontType = "font.default.%LANG%";
     var defaultFontTypePref = kDefaultFontType.replace(/%LANG%/, aLanguageGroup);
--- a/mail/components/preferences/fonts.js
+++ b/mail/components/preferences/fonts.js
@@ -12,18 +12,28 @@ var kFontNameFmtMonospace     = "font.na
 var kFontNameListFmtSerif     = "font.name-list.serif.%LANG%";
 var kFontNameListFmtSansSerif = "font.name-list.sans-serif.%LANG%";
 var kFontNameListFmtMonospace = "font.name-list.monospace.%LANG%";
 var kFontSizeFmtVariable      = "font.size.variable.%LANG%";
 var kFontSizeFmtFixed         = "font.size.fixed.%LANG%";
 var kFontMinSizeFmt           = "font.minimum-size.%LANG%";
 
 var gFontsDialog = {
-  _selectLanguageGroup: function (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          },
@@ -44,27 +54,33 @@ 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: function ()
   {
     var languagePref = document.getElementById("font.language.group");
-    this._selectLanguageGroup(languagePref.value);
+    this._safelySelectLanguageGroup(languagePref.value);
     return undefined;
   },
 
   readFontSelection: function (aElement)
   {
     // Determine the appropriate value to select, for the following cases:
     // - there is no setting
     // - the font selected by the user is no longer present (e.g. deleted from