Bug 1157064 - implementation of font-display. r=heycam,khuey
authorJohn Daggett <jdaggett@mozilla.com>
Thu, 07 Jan 2016 14:03:05 +0900
changeset 315905 6d9b17e5e7b55c4a9be4b6739d46a11106eab1f3
parent 315904 5fe7d4e2fd2c30be2a60d201e9325f25fb59aeb8
child 315906 eb7f975d67af9ebee74c9ba796098000ae238db6
push id1079
push userjlund@mozilla.com
push dateFri, 15 Apr 2016 21:02:33 +0000
treeherdermozilla-release@575fbf6786d5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam, khuey
bugs1157064
milestone46.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 1157064 - implementation of font-display. r=heycam,khuey
dom/webidl/FontFace.webidl
gfx/thebes/gfxUserFontSet.cpp
gfx/thebes/gfxUserFontSet.h
layout/style/FontFace.cpp
layout/style/FontFace.h
layout/style/FontFaceSet.cpp
layout/style/FontFaceSet.h
layout/style/nsFontFaceLoader.cpp
layout/style/nsFontFaceLoader.h
layout/style/test/descriptor_database.js
layout/style/test/test_descriptor_storage.html
layout/style/test/test_font_loading_api.html
modules/libpref/init/all.js
--- a/dom/webidl/FontFace.webidl
+++ b/dom/webidl/FontFace.webidl
@@ -14,16 +14,17 @@ typedef (ArrayBuffer or ArrayBufferView)
 
 dictionary FontFaceDescriptors {
   DOMString style = "normal";
   DOMString weight = "normal";
   DOMString stretch = "normal";
   DOMString unicodeRange = "U+0-10FFFF";
   DOMString variant = "normal";
   DOMString featureSettings = "normal";
+  DOMString display = "auto";
 };
 
 enum FontFaceLoadStatus { "unloaded", "loading", "loaded", "error" };
 
 // Bug 1072107 is for exposing this in workers.
 // [Exposed=(Window,Worker)]
 [Constructor(DOMString family,
              (DOMString or BinaryData) source,
@@ -32,16 +33,17 @@ enum FontFaceLoadStatus { "unloaded", "l
 interface FontFace {
   [SetterThrows] attribute DOMString family;
   [SetterThrows] attribute DOMString style;
   [SetterThrows] attribute DOMString weight;
   [SetterThrows] attribute DOMString stretch;
   [SetterThrows] attribute DOMString unicodeRange;
   [SetterThrows] attribute DOMString variant;
   [SetterThrows] attribute DOMString featureSettings;
+  [SetterThrows, Pref="layout.css.font-display.enabled"] attribute DOMString display;
 
   readonly attribute FontFaceLoadStatus status;
 
   [Throws]
   Promise<FontFace> load();
 
   [Throws]
   readonly attribute Promise<FontFace> loaded;
--- a/gfx/thebes/gfxUserFontSet.cpp
+++ b/gfx/thebes/gfxUserFontSet.cpp
@@ -106,21 +106,23 @@ private:
 
 gfxUserFontEntry::gfxUserFontEntry(gfxUserFontSet* aFontSet,
              const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
              uint32_t aWeight,
              int32_t aStretch,
              uint8_t aStyle,
              const nsTArray<gfxFontFeature>& aFeatureSettings,
              uint32_t aLanguageOverride,
-             gfxSparseBitSet* aUnicodeRanges)
+             gfxSparseBitSet* aUnicodeRanges,
+             uint8_t aFontDisplay)
     : gfxFontEntry(NS_LITERAL_STRING("userfont")),
       mUserFontLoadState(STATUS_NOT_LOADED),
       mFontDataLoadingState(NOT_LOADING),
       mUnsupportedFormat(false),
+      mFontDisplay(aFontDisplay),
       mLoader(nullptr),
       mFontSet(aFontSet)
 {
     MOZ_ASSERT(aWeight != 0,
                "aWeight must not be 0; use NS_FONT_WEIGHT_NORMAL instead");
     mIsUserFontContainer = true;
     mSrcList = aFontFaceSrcList;
     mSrcIndex = 0;
@@ -141,24 +143,26 @@ gfxUserFontEntry::~gfxUserFontEntry()
 
 bool
 gfxUserFontEntry::Matches(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                           uint32_t aWeight,
                           int32_t aStretch,
                           uint8_t aStyle,
                           const nsTArray<gfxFontFeature>& aFeatureSettings,
                           uint32_t aLanguageOverride,
-                          gfxSparseBitSet* aUnicodeRanges)
+                          gfxSparseBitSet* aUnicodeRanges,
+                          uint8_t aFontDisplay)
 {
     return mWeight == aWeight &&
            mStretch == aStretch &&
            mStyle == aStyle &&
            mFeatureSettings == aFeatureSettings &&
            mLanguageOverride == aLanguageOverride &&
            mSrcList == aFontFaceSrcList &&
+           mFontDisplay == aFontDisplay &&
            ((!aUnicodeRanges && !mCharacterMap) ||
             (aUnicodeRanges && mCharacterMap && mCharacterMap->Equals(aUnicodeRanges)));
 }
 
 gfxFont*
 gfxUserFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle, bool aNeedsBold)
 {
     NS_NOTREACHED("should only be creating a gfxFont"
@@ -722,38 +726,43 @@ gfxUserFontEntry::FontDataDownloadComple
                                            uint32_t aLength,
                                            nsresult aDownloadStatus)
 {
     // forget about the loader, as we no longer potentially need to cancel it
     // if the entry is obsoleted
     mLoader = nullptr;
 
     // download successful, make platform font using font data
-    if (NS_SUCCEEDED(aDownloadStatus)) {
+    if (NS_SUCCEEDED(aDownloadStatus) &&
+        mFontDataLoadingState != LOADING_TIMED_OUT) {
         bool loaded = LoadPlatformFont(aFontData, aLength);
         aFontData = nullptr;
 
         if (loaded) {
             IncrementGeneration();
             return true;
         }
 
     } else {
         // download failed
         mFontSet->LogMessage(this,
-                             "download failed", nsIScriptError::errorFlag,
+                             (mFontDataLoadingState != LOADING_TIMED_OUT ?
+                              "download failed" : "download timed out"),
+                             nsIScriptError::errorFlag,
                              aDownloadStatus);
     }
 
     if (aFontData) {
         free((void*)aFontData);
     }
 
-    // error occurred, load next src
-    LoadNextSrc();
+    // error occurred, load next src if load not yet timed out
+    if (mFontDataLoadingState != LOADING_TIMED_OUT) {
+      LoadNextSrc();
+    }
 
     // We ignore the status returned by LoadNext();
     // even if loading failed, we need to bump the font-set generation
     // and return true in order to trigger reflow, so that fallback
     // will be used where the text was "masked" by the pending download
     IncrementGeneration();
     return true;
 }
@@ -790,89 +799,93 @@ already_AddRefed<gfxUserFontEntry>
 gfxUserFontSet::FindOrCreateUserFontEntry(
                                const nsAString& aFamilyName,
                                const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                                uint32_t aWeight,
                                int32_t aStretch,
                                uint8_t aStyle,
                                const nsTArray<gfxFontFeature>& aFeatureSettings,
                                uint32_t aLanguageOverride,
-                               gfxSparseBitSet* aUnicodeRanges)
+                               gfxSparseBitSet* aUnicodeRanges,
+                               uint8_t aFontDisplay)
 {
     RefPtr<gfxUserFontEntry> entry;
 
     // If there's already a userfont entry in the family whose descriptors all match,
     // we can just move it to the end of the list instead of adding a new
     // face that will always "shadow" the old one.
     // Note that we can't do this for platform font entries, even if the
     // style descriptors match, as they might have had a different source list,
     // but we no longer have the old source list available to check.
     gfxUserFontFamily* family = LookupFamily(aFamilyName);
     if (family) {
         entry = FindExistingUserFontEntry(family, aFontFaceSrcList, aWeight,
                                           aStretch, aStyle,
                                           aFeatureSettings, aLanguageOverride,
-                                          aUnicodeRanges);
+                                          aUnicodeRanges, aFontDisplay);
     }
 
     if (!entry) {
       entry = CreateUserFontEntry(aFontFaceSrcList, aWeight, aStretch,
                                   aStyle, aFeatureSettings,
-                                  aLanguageOverride, aUnicodeRanges);
+                                  aLanguageOverride, aUnicodeRanges,
+                                  aFontDisplay);
       entry->mFamilyName = aFamilyName;
     }
 
     return entry.forget();
 }
 
 already_AddRefed<gfxUserFontEntry>
 gfxUserFontSet::CreateUserFontEntry(
                                const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                                uint32_t aWeight,
                                int32_t aStretch,
                                uint8_t aStyle,
                                const nsTArray<gfxFontFeature>& aFeatureSettings,
                                uint32_t aLanguageOverride,
-                               gfxSparseBitSet* aUnicodeRanges)
+                               gfxSparseBitSet* aUnicodeRanges,
+                               uint8_t aFontDisplay)
 {
 
     RefPtr<gfxUserFontEntry> userFontEntry =
         new gfxUserFontEntry(this, aFontFaceSrcList, aWeight,
                               aStretch, aStyle, aFeatureSettings,
-                              aLanguageOverride, aUnicodeRanges);
+                              aLanguageOverride, aUnicodeRanges, aFontDisplay);
     return userFontEntry.forget();
 }
 
 gfxUserFontEntry*
 gfxUserFontSet::FindExistingUserFontEntry(
                                gfxUserFontFamily* aFamily,
                                const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                                uint32_t aWeight,
                                int32_t aStretch,
                                uint8_t aStyle,
                                const nsTArray<gfxFontFeature>& aFeatureSettings,
                                uint32_t aLanguageOverride,
-                               gfxSparseBitSet* aUnicodeRanges)
+                               gfxSparseBitSet* aUnicodeRanges,
+                               uint8_t aFontDisplay)
 {
     MOZ_ASSERT(aWeight != 0,
                "aWeight must not be 0; use NS_FONT_WEIGHT_NORMAL instead");
 
     nsTArray<RefPtr<gfxFontEntry>>& fontList = aFamily->GetFontList();
 
     for (size_t i = 0, count = fontList.Length(); i < count; i++) {
         if (!fontList[i]->mIsUserFontContainer) {
             continue;
         }
 
         gfxUserFontEntry* existingUserFontEntry =
             static_cast<gfxUserFontEntry*>(fontList[i].get());
         if (!existingUserFontEntry->Matches(aFontFaceSrcList,
                                             aWeight, aStretch, aStyle,
                                             aFeatureSettings, aLanguageOverride,
-                                            aUnicodeRanges)) {
+                                            aUnicodeRanges, aFontDisplay)) {
             continue;
         }
 
         return existingUserFontEntry;
     }
 
     return nullptr;
 }
@@ -881,21 +894,22 @@ void
 gfxUserFontSet::AddUserFontEntry(const nsAString& aFamilyName,
                                  gfxUserFontEntry* aUserFontEntry)
 {
     gfxUserFontFamily* family = GetFamily(aFamilyName);
     family->AddFontEntry(aUserFontEntry);
 
     if (LOG_ENABLED()) {
         LOG(("userfonts (%p) added to \"%s\" (%p) style: %s weight: %d "
-             "stretch: %d",
+             "stretch: %d display: %d",
              this, NS_ConvertUTF16toUTF8(aFamilyName).get(), aUserFontEntry,
              (aUserFontEntry->IsItalic() ? "italic" :
               (aUserFontEntry->IsOblique() ? "oblique" : "normal")),
-             aUserFontEntry->Weight(), aUserFontEntry->Stretch()));
+             aUserFontEntry->Weight(), aUserFontEntry->Stretch(),
+             aUserFontEntry->GetFontDisplay()));
     }
 }
 
 gfxUserFontEntry*
 gfxUserFontSet::FindUserFontEntryAndLoad(gfxFontFamily* aFamily,
                                          const gfxFontStyle& aFontStyle,
                                          bool& aNeedsBold,
                                          bool& aWaitForUserFont)
--- a/gfx/thebes/gfxUserFontSet.h
+++ b/gfx/thebes/gfxUserFontSet.h
@@ -209,29 +209,31 @@ public:
     // TODO: support for unicode ranges not yet implemented
     virtual already_AddRefed<gfxUserFontEntry> CreateUserFontEntry(
                               const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                               uint32_t aWeight,
                               int32_t aStretch,
                               uint8_t aStyle,
                               const nsTArray<gfxFontFeature>& aFeatureSettings,
                               uint32_t aLanguageOverride,
-                              gfxSparseBitSet* aUnicodeRanges) = 0;
+                              gfxSparseBitSet* aUnicodeRanges,
+                              uint8_t aFontDisplay) = 0;
 
     // creates a font face for the specified family, or returns an existing
     // matching entry on the family if there is one
     already_AddRefed<gfxUserFontEntry> FindOrCreateUserFontEntry(
                                const nsAString& aFamilyName,
                                const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                                uint32_t aWeight,
                                int32_t aStretch,
                                uint8_t aStyle,
                                const nsTArray<gfxFontFeature>& aFeatureSettings,
                                uint32_t aLanguageOverride,
-                               gfxSparseBitSet* aUnicodeRanges);
+                               gfxSparseBitSet* aUnicodeRanges,
+                               uint8_t aFontDisplay);
 
     // add in a font face for which we have the gfxUserFontEntry already
     void AddUserFontEntry(const nsAString& aFamilyName,
                           gfxUserFontEntry* aUserFontEntry);
 
     // Whether there is a face with this family name
     bool HasFamily(const nsAString& aFamilyName) const
     {
@@ -503,17 +505,18 @@ protected:
     gfxUserFontEntry* FindExistingUserFontEntry(
                                    gfxUserFontFamily* aFamily,
                                    const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                                    uint32_t aWeight,
                                    int32_t aStretch,
                                    uint8_t aStyle,
                                    const nsTArray<gfxFontFeature>& aFeatureSettings,
                                    uint32_t aLanguageOverride,
-                                   gfxSparseBitSet* aUnicodeRanges);
+                                   gfxSparseBitSet* aUnicodeRanges,
+                                   uint8_t aFontDisplay);
 
     // creates a new gfxUserFontFamily in mFontFamilies, or returns an existing
     // family if there is one
     gfxUserFontFamily* GetFamily(const nsAString& aFamilyName);
 
     // font families defined by @font-face rules
     nsRefPtrHashtable<nsStringHashKey, gfxUserFontFamily> mFontFamilies;
 
@@ -546,28 +549,30 @@ public:
 
     gfxUserFontEntry(gfxUserFontSet* aFontSet,
                      const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                      uint32_t aWeight,
                      int32_t aStretch,
                      uint8_t aStyle,
                      const nsTArray<gfxFontFeature>& aFeatureSettings,
                      uint32_t aLanguageOverride,
-                     gfxSparseBitSet* aUnicodeRanges);
+                     gfxSparseBitSet* aUnicodeRanges,
+                     uint8_t aFontDisplay);
 
     virtual ~gfxUserFontEntry();
 
     // Return whether the entry matches the given list of attributes
     bool Matches(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                  uint32_t aWeight,
                  int32_t aStretch,
                  uint8_t aStyle,
                  const nsTArray<gfxFontFeature>& aFeatureSettings,
                  uint32_t aLanguageOverride,
-                 gfxSparseBitSet* aUnicodeRanges);
+                 gfxSparseBitSet* aUnicodeRanges,
+                 uint8_t aFontDisplay);
 
     virtual gfxFont* CreateFontInstance(const gfxFontStyle* aFontStyle,
                                         bool aNeedsBold);
 
     gfxFontEntry* GetPlatformFontEntry() const { return mPlatformFontEntry; }
 
     // is the font loading or loaded, or did it fail?
     UserFontLoadState LoadState() const { return mUserFontLoadState; }
@@ -586,16 +591,18 @@ public:
         }
         return true;
     }
 
     gfxCharacterMap* GetUnicodeRangeMap() const {
         return mCharacterMap.get();
     }
 
+    uint8_t GetFontDisplay() const { return mFontDisplay; }
+
     // load the font - starts the loading of sources which continues until
     // a valid font resource is found or all sources fail
     void Load();
 
     // methods to expose some information to FontFaceSet::UserFontSet
     // since we can't make that class a friend
     void SetLoader(nsFontFaceLoader* aLoader) { mLoader = aLoader; }
     nsFontFaceLoader* GetLoader() { return mLoader; }
@@ -656,21 +663,23 @@ protected:
     // note that code depends on the ordering of these values!
     enum FontDataLoadingState {
         NOT_LOADING = 0,     // not started to load any font resources yet
         LOADING_STARTED,     // loading has started; hide fallback font
         LOADING_ALMOST_DONE, // timeout happened but we're nearly done,
                              // so keep hiding fallback font
         LOADING_SLOWLY,      // timeout happened and we're not nearly done,
                              // so use the fallback font
+        LOADING_TIMED_OUT,   // font load took too long
         LOADING_FAILED       // failed to load any source: use fallback
     };
     FontDataLoadingState     mFontDataLoadingState;
 
     bool                     mUnsupportedFormat;
+    uint8_t                  mFontDisplay; // timing of userfont fallback
 
     RefPtr<gfxFontEntry>   mPlatformFontEntry;
     nsTArray<gfxFontFaceSrc> mSrcList;
     uint32_t                 mSrcIndex; // index of loading src item
     // This field is managed by the nsFontFaceLoader. In the destructor and Cancel()
     // methods of nsFontFaceLoader this reference is nulled out.
     nsFontFaceLoader* MOZ_NON_OWNING_REF mLoader; // current loader for this entry, if any
     gfxUserFontSet*          mFontSet; // font-set which owns this userfont entry
--- a/layout/style/FontFace.cpp
+++ b/layout/style/FontFace.cpp
@@ -350,16 +350,30 @@ FontFace::GetFeatureSettings(nsString& a
 
 void
 FontFace::SetFeatureSettings(const nsAString& aValue, ErrorResult& aRv)
 {
   mFontFaceSet->FlushUserFontSet();
   SetDescriptor(eCSSFontDesc_FontFeatureSettings, aValue, aRv);
 }
 
+void
+FontFace::GetDisplay(nsString& aResult)
+{
+  mFontFaceSet->FlushUserFontSet();
+  GetDesc(eCSSFontDesc_Display, eCSSProperty_UNKNOWN, aResult);
+}
+
+void
+FontFace::SetDisplay(const nsAString& aValue, ErrorResult& aRv)
+{
+  mFontFaceSet->FlushUserFontSet();
+  SetDescriptor(eCSSFontDesc_Display, aValue, aRv);
+}
+
 FontFaceLoadStatus
 FontFace::Status()
 {
   return mStatus;
 }
 
 Promise*
 FontFace::Load(ErrorResult& aRv)
@@ -543,17 +557,20 @@ FontFace::SetDescriptors(const nsAString
       !ParseDescriptor(eCSSFontDesc_Stretch,
                        aDescriptors.mStretch,
                        mDescriptors->mStretch) ||
       !ParseDescriptor(eCSSFontDesc_UnicodeRange,
                        aDescriptors.mUnicodeRange,
                        mDescriptors->mUnicodeRange) ||
       !ParseDescriptor(eCSSFontDesc_FontFeatureSettings,
                        aDescriptors.mFeatureSettings,
-                       mDescriptors->mFontFeatureSettings)) {
+                       mDescriptors->mFontFeatureSettings) ||
+      !ParseDescriptor(eCSSFontDesc_Display,
+                       aDescriptors.mDisplay,
+                       mDescriptors->mDisplay)) {
     // XXX Handle font-variant once we support it (bug 1055385).
 
     // If any of the descriptors failed to parse, none of them should be set
     // on the FontFace.
     mDescriptors = new CSSFontFaceDescriptors;
 
     if (mLoaded) {
       mLoaded->MaybeReject(NS_ERROR_DOM_SYNTAX_ERR);
@@ -579,39 +596,46 @@ FontFace::GetDesc(nsCSSFontDesc aDescID,
 }
 
 void
 FontFace::GetDesc(nsCSSFontDesc aDescID,
                   nsCSSProperty aPropID,
                   nsString& aResult) const
 {
   MOZ_ASSERT(aDescID == eCSSFontDesc_UnicodeRange ||
+             aDescID == eCSSFontDesc_Display ||
              aPropID != eCSSProperty_UNKNOWN,
              "only pass eCSSProperty_UNKNOWN for eCSSFontDesc_UnicodeRange");
 
   nsCSSValue value;
   GetDesc(aDescID, value);
 
   aResult.Truncate();
 
   // Fill in a default value for missing descriptors.
   if (value.GetUnit() == eCSSUnit_Null) {
     if (aDescID == eCSSFontDesc_UnicodeRange) {
       aResult.AssignLiteral("U+0-10FFFF");
+    } else if (aDescID == eCSSFontDesc_Display) {
+      aResult.AssignLiteral("auto");
     } else if (aDescID != eCSSFontDesc_Family &&
                aDescID != eCSSFontDesc_Src) {
       aResult.AssignLiteral("normal");
     }
     return;
   }
 
   if (aDescID == eCSSFontDesc_UnicodeRange) {
     // Since there's no unicode-range property, we can't use
     // nsCSSValue::AppendToString to serialize this descriptor.
     nsStyleUtil::AppendUnicodeRange(value, aResult);
+  } else if (aDescID == eCSSFontDesc_Display) {
+    AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(value.GetIntValue(),
+                                                  nsCSSProps::kFontDisplayKTable),
+                       aResult);
   } else {
     value.AppendToString(aPropID, aResult, nsCSSValue::eNormalized);
   }
 }
 
 void
 FontFace::SetUserFontEntry(gfxUserFontEntry* aEntry)
 {
--- a/layout/style/FontFace.h
+++ b/layout/style/FontFace.h
@@ -42,20 +42,21 @@ public:
   public:
     Entry(gfxUserFontSet* aFontSet,
           const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
           uint32_t aWeight,
           int32_t aStretch,
           uint8_t aStyle,
           const nsTArray<gfxFontFeature>& aFeatureSettings,
           uint32_t aLanguageOverride,
-          gfxSparseBitSet* aUnicodeRanges)
+          gfxSparseBitSet* aUnicodeRanges,
+          uint8_t aFontDisplay)
       : gfxUserFontEntry(aFontSet, aFontFaceSrcList, aWeight, aStretch,
                          aStyle, aFeatureSettings, aLanguageOverride,
-                         aUnicodeRanges) {}
+                         aUnicodeRanges, aFontDisplay) {}
 
     virtual void SetLoadState(UserFontLoadState aLoadState) override;
     virtual void GetUserFontSets(nsTArray<gfxUserFontSet*>& aResult) override;
     const nsAutoTArray<FontFace*,1>& GetFontFaces() { return mFontFaces; }
 
   protected:
     // The FontFace objects that use this user font entry.  We need to store
     // an array of these, not just a single pointer, since the user font
@@ -143,16 +144,18 @@ public:
   void GetStretch(nsString& aResult);
   void SetStretch(const nsAString& aValue, mozilla::ErrorResult& aRv);
   void GetUnicodeRange(nsString& aResult);
   void SetUnicodeRange(const nsAString& aValue, mozilla::ErrorResult& aRv);
   void GetVariant(nsString& aResult);
   void SetVariant(const nsAString& aValue, mozilla::ErrorResult& aRv);
   void GetFeatureSettings(nsString& aResult);
   void SetFeatureSettings(const nsAString& aValue, mozilla::ErrorResult& aRv);
+  void GetDisplay(nsString& aResult);
+  void SetDisplay(const nsAString& aValue, mozilla::ErrorResult& aRv);
 
   mozilla::dom::FontFaceLoadStatus Status();
   mozilla::dom::Promise* Load(mozilla::ErrorResult& aRv);
   mozilla::dom::Promise* GetLoaded(mozilla::ErrorResult& aRv);
 
 private:
   FontFace(nsISupports* aParent, FontFaceSet* aFontFaceSet);
   ~FontFace();
--- a/layout/style/FontFaceSet.cpp
+++ b/layout/style/FontFaceSet.cpp
@@ -971,16 +971,17 @@ FontFaceSet::FindOrCreateUserFontEntryFr
 {
   nsCSSValue val;
   nsCSSUnit unit;
 
   uint32_t weight = NS_STYLE_FONT_WEIGHT_NORMAL;
   int32_t stretch = NS_STYLE_FONT_STRETCH_NORMAL;
   uint8_t italicStyle = NS_STYLE_FONT_STYLE_NORMAL;
   uint32_t languageOverride = NO_FONT_LANGUAGE_OVERRIDE;
+  uint8_t fontDisplay = NS_FONT_DISPLAY_AUTO;
 
   // set up weight
   aFontFace->GetDesc(eCSSFontDesc_Weight, val);
   unit = val.GetUnit();
   if (unit == eCSSUnit_Integer || unit == eCSSUnit_Enumerated) {
     weight = val.GetIntValue();
     if (weight == 0) {
       weight = NS_STYLE_FONT_WEIGHT_NORMAL;
@@ -1011,16 +1012,26 @@ FontFaceSet::FindOrCreateUserFontEntryFr
     italicStyle = val.GetIntValue();
   } else if (unit == eCSSUnit_Normal) {
     italicStyle = NS_STYLE_FONT_STYLE_NORMAL;
   } else {
     NS_ASSERTION(unit == eCSSUnit_Null,
                  "@font-face style has unexpected unit");
   }
 
+  // set up font display
+  aFontFace->GetDesc(eCSSFontDesc_Display, val);
+  unit = val.GetUnit();
+  if (unit == eCSSUnit_Enumerated) {
+    fontDisplay = val.GetIntValue();
+  } else {
+    NS_ASSERTION(unit == eCSSUnit_Null,
+                 "@font-face style has unexpected unit");
+  }
+
   // set up font features
   nsTArray<gfxFontFeature> featureSettings;
   aFontFace->GetDesc(eCSSFontDesc_FontFeatureSettings, val);
   unit = val.GetUnit();
   if (unit == eCSSUnit_Normal) {
     // empty list of features
   } else if (unit == eCSSUnit_PairList || unit == eCSSUnit_PairListDep) {
     nsRuleNode::ComputeFontFeatures(val.GetPairListValue(), featureSettings);
@@ -1156,17 +1167,17 @@ FontFaceSet::FindOrCreateUserFontEntryFr
     return nullptr;
   }
 
   RefPtr<gfxUserFontEntry> entry =
     mUserFontSet->FindOrCreateUserFontEntry(aFamilyName, srcArray, weight,
                                             stretch, italicStyle,
                                             featureSettings,
                                             languageOverride,
-                                            unicodeRanges);
+                                            unicodeRanges, fontDisplay);
   return entry.forget();
 }
 
 nsCSSFontFaceRule*
 FontFaceSet::FindRuleForEntry(gfxFontEntry* aFontEntry)
 {
   NS_ASSERTION(!aFontEntry->mIsUserFontContainer, "only platform font entries");
   for (uint32_t i = 0; i < mRuleFaces.Length(); ++i) {
@@ -1800,18 +1811,20 @@ FontFaceSet::UserFontSet::DoRebuildUserF
 /* virtual */ already_AddRefed<gfxUserFontEntry>
 FontFaceSet::UserFontSet::CreateUserFontEntry(
                                const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                                uint32_t aWeight,
                                int32_t aStretch,
                                uint8_t aStyle,
                                const nsTArray<gfxFontFeature>& aFeatureSettings,
                                uint32_t aLanguageOverride,
-                               gfxSparseBitSet* aUnicodeRanges)
+                               gfxSparseBitSet* aUnicodeRanges,
+                               uint8_t aFontDisplay)
 {
   RefPtr<gfxUserFontEntry> entry =
     new FontFace::Entry(this, aFontFaceSrcList, aWeight, aStretch, aStyle,
-                        aFeatureSettings, aLanguageOverride, aUnicodeRanges);
+                        aFeatureSettings, aLanguageOverride, aUnicodeRanges,
+                        aFontDisplay);
   return entry.forget();
 }
 
 #undef LOG_ENABLED
 #undef LOG
--- a/layout/style/FontFaceSet.h
+++ b/layout/style/FontFaceSet.h
@@ -84,17 +84,18 @@ public:
     virtual void DoRebuildUserFontSet() override;
     virtual already_AddRefed<gfxUserFontEntry> CreateUserFontEntry(
                                    const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                                    uint32_t aWeight,
                                    int32_t aStretch,
                                    uint8_t aStyle,
                                    const nsTArray<gfxFontFeature>& aFeatureSettings,
                                    uint32_t aLanguageOverride,
-                                   gfxSparseBitSet* aUnicodeRanges) override;
+                                   gfxSparseBitSet* aUnicodeRanges,
+                                   uint8_t aFontDisplay) override;
 
   private:
     RefPtr<FontFaceSet> mFontFaceSet;
   };
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FontFaceSet, DOMEventTargetHelper)
   NS_DECL_NSIDOMEVENTLISTENER
--- a/layout/style/nsFontFaceLoader.cpp
+++ b/layout/style/nsFontFaceLoader.cpp
@@ -26,16 +26,28 @@
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 #define LOG(args) MOZ_LOG(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug, args)
 #define LOG_ENABLED() MOZ_LOG_TEST(gfxUserFontSet::GetUserFontsLog(), \
                                   LogLevel::Debug)
 
+static uint32_t
+GetFallbackDelay()
+{
+  return Preferences::GetInt("gfx.downloadable_fonts.fallback_delay", 3000);
+}
+
+static uint32_t
+GetShortFallbackDelay()
+{
+  return Preferences::GetInt("gfx.downloadable_fonts.fallback_delay_short", 100);
+}
+
 nsFontFaceLoader::nsFontFaceLoader(gfxUserFontEntry* aUserFontEntry,
                                    nsIURI* aFontURI,
                                    FontFaceSet* aFontFaceSet,
                                    nsIChannel* aChannel)
   : mUserFontEntry(aUserFontEntry),
     mFontURI(aFontURI),
     mFontFaceSet(aFontFaceSet),
     mChannel(aChannel)
@@ -55,18 +67,25 @@ nsFontFaceLoader::~nsFontFaceLoader()
   if (mFontFaceSet) {
     mFontFaceSet->RemoveLoader(this);
   }
 }
 
 void
 nsFontFaceLoader::StartedLoading(nsIStreamLoader* aStreamLoader)
 {
-  int32_t loadTimeout =
-    Preferences::GetInt("gfx.downloadable_fonts.fallback_delay", 3000);
+  int32_t loadTimeout;
+  uint8_t fontDisplay = GetFontDisplay();
+  if (fontDisplay == NS_FONT_DISPLAY_AUTO ||
+      fontDisplay == NS_FONT_DISPLAY_BLOCK) {
+    loadTimeout = GetFallbackDelay();
+  } else {
+    loadTimeout = GetShortFallbackDelay();
+  }
+
   if (loadTimeout > 0) {
     mLoadTimer = do_CreateInstance("@mozilla.org/timer;1");
     if (mLoadTimer) {
       mLoadTimer->InitWithFuncCallback(LoadTimerCallback,
                                        static_cast<void*>(this),
                                        loadTimeout,
                                        nsITimer::TYPE_ONE_SHOT);
     }
@@ -82,58 +101,91 @@ nsFontFaceLoader::LoadTimerCallback(nsIT
   nsFontFaceLoader* loader = static_cast<nsFontFaceLoader*>(aClosure);
 
   if (!loader->mFontFaceSet) {
     // We've been canceled
     return;
   }
 
   gfxUserFontEntry* ufe = loader->mUserFontEntry.get();
-  bool updateUserFontSet = true;
+  uint8_t fontDisplay = loader->GetFontDisplay();
+
+  // Depending upon the value of the font-display descriptor for the font,
+  // their may be one or two timeouts associated with each font. The LOADING_SLOWLY
+  // state indicates that the fallback font is shown. The LOADING_TIMED_OUT
+  // state indicates that the fallback font is shown *and* the downloaded font
+  // resource will not replace the fallback font when the load completes.
 
-  // If the entry is loading, check whether it's >75% done; if so,
-  // we allow another timeout period before showing a fallback font.
-  if (ufe->mFontDataLoadingState == gfxUserFontEntry::LOADING_STARTED) {
-    int64_t contentLength;
-    uint32_t numBytesRead;
-    if (NS_SUCCEEDED(loader->mChannel->GetContentLength(&contentLength)) &&
-        contentLength > 0 &&
-        contentLength < UINT32_MAX &&
-        NS_SUCCEEDED(loader->mStreamLoader->GetNumBytesRead(&numBytesRead)) &&
-        numBytesRead > 3 * (uint32_t(contentLength) >> 2))
-    {
-      // More than 3/4 the data has been downloaded, so allow 50% extra
-      // time and hope the remainder will arrive before the additional
-      // time expires.
-      ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_ALMOST_DONE;
-      uint32_t delay;
-      loader->mLoadTimer->GetDelay(&delay);
-      loader->mLoadTimer->InitWithFuncCallback(LoadTimerCallback,
-                                               static_cast<void*>(loader),
-                                               delay >> 1,
-                                               nsITimer::TYPE_ONE_SHOT);
-      updateUserFontSet = false;
-      LOG(("userfonts (%p) 75%% done, resetting timer\n", loader));
+  bool updateUserFontSet = true;
+  switch (fontDisplay) {
+    case NS_FONT_DISPLAY_AUTO:
+    case NS_FONT_DISPLAY_BLOCK:
+      // If the entry is loading, check whether it's >75% done; if so,
+      // we allow another timeout period before showing a fallback font.
+      if (ufe->mFontDataLoadingState == gfxUserFontEntry::LOADING_STARTED) {
+        int64_t contentLength;
+        uint32_t numBytesRead;
+        if (NS_SUCCEEDED(loader->mChannel->GetContentLength(&contentLength)) &&
+            contentLength > 0 &&
+            contentLength < UINT32_MAX &&
+            NS_SUCCEEDED(loader->mStreamLoader->GetNumBytesRead(&numBytesRead)) &&
+            numBytesRead > 3 * (uint32_t(contentLength) >> 2))
+        {
+          // More than 3/4 the data has been downloaded, so allow 50% extra
+          // time and hope the remainder will arrive before the additional
+          // time expires.
+          ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_ALMOST_DONE;
+          uint32_t delay;
+          loader->mLoadTimer->GetDelay(&delay);
+          loader->mLoadTimer->InitWithFuncCallback(LoadTimerCallback,
+                                                   static_cast<void*>(loader),
+                                                   delay >> 1,
+                                                   nsITimer::TYPE_ONE_SHOT);
+          updateUserFontSet = false;
+          LOG(("userfonts (%p) 75%% done, resetting timer\n", loader));
+        }
+      }
+      if (updateUserFontSet) {
+        ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
+      }
+      break;
+    case NS_FONT_DISPLAY_SWAP:
+      ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
+      break;
+    case NS_FONT_DISPLAY_FALLBACK: {
+      if (ufe->mFontDataLoadingState == gfxUserFontEntry::LOADING_STARTED) {
+        ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
+      } else {
+        ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_TIMED_OUT;
+        updateUserFontSet = false;
+      }
+      break;
     }
+    case NS_FONT_DISPLAY_OPTIONAL:
+      ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_TIMED_OUT;
+      break;
+
+    default:
+      NS_NOTREACHED("strange font-display value");
+      break;
   }
 
   // If the font is not 75% loaded, or if we've already timed out once
   // before, we mark this entry as "loading slowly", so the fallback
   // font will be used in the meantime, and tell the context to refresh.
   if (updateUserFontSet) {
-    ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
     nsTArray<gfxUserFontSet*> fontSets;
     ufe->GetUserFontSets(fontSets);
     for (gfxUserFontSet* fontSet : fontSets) {
       nsPresContext* ctx = FontFaceSet::GetPresContextFor(fontSet);
       if (ctx) {
         fontSet->IncrementGeneration();
         ctx->UserFontSetUpdated(ufe);
-        LOG(("userfonts (%p) timeout reflow for pres context %p\n",
-             loader, ctx));
+        LOG(("userfonts (%p) timeout reflow for pres context %p display %d\n",
+             loader, ctx, fontDisplay));
       }
     }
   }
 }
 
 NS_IMPL_ISUPPORTS(nsFontFaceLoader, nsIStreamLoaderObserver)
 
 NS_IMETHODIMP
@@ -150,16 +202,26 @@ nsFontFaceLoader::OnStreamComplete(nsISt
 
   mFontFaceSet->RemoveLoader(this);
 
   TimeStamp doneTime = TimeStamp::Now();
   TimeDuration downloadTime = doneTime - mStartTime;
   uint32_t downloadTimeMS = uint32_t(downloadTime.ToMilliseconds());
   Telemetry::Accumulate(Telemetry::WEBFONT_DOWNLOAD_TIME, downloadTimeMS);
 
+  if (GetFontDisplay() == NS_FONT_DISPLAY_FALLBACK) {
+    uint32_t loadTimeout = GetFallbackDelay();
+    if (downloadTimeMS > loadTimeout &&
+        (mUserFontEntry->mFontDataLoadingState ==
+         gfxUserFontEntry::LOADING_SLOWLY)) {
+      mUserFontEntry->mFontDataLoadingState =
+        gfxUserFontEntry::LOADING_TIMED_OUT;
+    }
+  }
+
   if (LOG_ENABLED()) {
     nsAutoCString fontURI;
     mFontURI->GetSpec(fontURI);
     if (NS_SUCCEEDED(aStatus)) {
       LOG(("userfonts (%p) download completed - font uri: (%s) time: %d ms\n",
            this, fontURI.get(), downloadTimeMS));
     } else {
       LOG(("userfonts (%p) download failed - font uri: (%s) error: %8.8x\n",
@@ -266,8 +328,18 @@ nsFontFaceLoader::CheckLoadAllowed(nsIPr
                                  nsContentUtils::GetSecurityManager());
 
   if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
     return NS_ERROR_CONTENT_BLOCKED;
   }
 
   return NS_OK;
 }
+
+uint8_t
+nsFontFaceLoader::GetFontDisplay()
+{
+  uint8_t fontDisplay = NS_FONT_DISPLAY_AUTO;
+  if (Preferences::GetBool("layout.css.font-display.enabled")) {
+    fontDisplay = mUserFontEntry->GetFontDisplay();
+  }
+  return fontDisplay;
+}
--- a/layout/style/nsFontFaceLoader.h
+++ b/layout/style/nsFontFaceLoader.h
@@ -45,16 +45,19 @@ public:
   static nsresult CheckLoadAllowed(nsIPrincipal* aSourcePrincipal,
                                    nsIURI* aTargetURI,
                                    nsISupports* aContext);
   gfxUserFontEntry* GetUserFontEntry() const { return mUserFontEntry; }
 
 protected:
   virtual ~nsFontFaceLoader();
 
+  // helper method for determining the font-display value
+  uint8_t GetFontDisplay();
+
 private:
   RefPtr<gfxUserFontEntry>  mUserFontEntry;
   nsCOMPtr<nsIURI>        mFontURI;
   RefPtr<mozilla::dom::FontFaceSet> mFontFaceSet;
   nsCOMPtr<nsIChannel>    mChannel;
   nsCOMPtr<nsITimer>      mLoadTimer;
   TimeStamp               mStartTime;
   nsIStreamLoader*        mStreamLoader;
--- a/layout/style/test/descriptor_database.js
+++ b/layout/style/test/descriptor_database.js
@@ -58,10 +58,15 @@ var gCSSFontFaceDescriptors = {
 			"url(404.ttf) ! important",
 			"url(404.ttf) ! hello",
 		]
 	},
 	"unicode-range": {
 		domProp: null,
 		values: [ "U+0-10FFFF", "U+3-7B3", "U+3??", "U+6A", "U+3????", "U+???", "U+302-302", "U+0-7,U+A-C", "U+100-17F,U+200-17F", "U+3??, U+500-513 ,U+612 , U+4????", "U+1FFF,U+200-27F" ],
 		invalid_values: [ "U+1????-2????", "U+0-7,A-C", "U+100-17F,200-17F", "U+6A!important", "U+6A)" ]
+	},
+	"font-display": {
+		domProp: null,
+		values: [ "auto", "block", "swap", "fallback", "optional" ],
+		invalid_values: [ "normal", "initial" ]
 	}
 }
--- a/layout/style/test/test_descriptor_storage.html
+++ b/layout/style/test/test_descriptor_storage.html
@@ -86,26 +86,34 @@ function test_descriptor(descriptor)
   var idx;
   for (idx in info.values)
     test_value(info.values[idx]);
 }
 
 // To avoid triggering the slow script dialog, we have to test one
 // descriptor at a time.
 SimpleTest.waitForExplicitFinish();
-var descs = [];
-for (var desc in gCSSFontFaceDescriptors)
-  descs.push(desc);
-descs = descs.reverse();
-function do_one() {
-  if (descs.length == 0) {
-    SimpleTest.finish();
-    return;
+function runTest() {
+  var descs = [];
+  for (var desc in gCSSFontFaceDescriptors)
+    descs.push(desc);
+  descs = descs.reverse();
+  function do_one() {
+    if (descs.length == 0) {
+      SimpleTest.finish();
+      return;
+    }
+    test_descriptor(descs.pop());
+    SimpleTest.executeSoon(do_one);
   }
-  test_descriptor(descs.pop());
   SimpleTest.executeSoon(do_one);
 }
-SimpleTest.executeSoon(do_one);
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(5);
+
+SpecialPowers.pushPrefEnv({ set: [["layout.css.font-display.enabled", true]] },
+                          runTest);
 
 </script>
 </pre>
 </body>
 </html>
--- a/layout/style/test/test_font_loading_api.html
+++ b/layout/style/test/test_font_loading_api.html
@@ -14,50 +14,54 @@
 // Map of FontFace descriptor attribute names to @font-face rule descriptor
 // names.
 var descriptorNames = {
   style: "font-style",
   weight: "font-weight",
   stretch: "font-stretch",
   unicodeRange: "unicode-range",
   variant: "font-variant",
-  featureSettings: "font-feature-settings"
+  featureSettings: "font-feature-settings",
+  display: "font-display"
 };
 
 // Default values for the FontFace descriptor attributes other than family, as
 // Gecko currently serializes them.
 var defaultValues = {
   style: "normal",
   weight: "normal",
   stretch: "normal",
   unicodeRange: "U+0-10FFFF",
   variant: "normal",
-  featureSettings: "normal"
+  featureSettings: "normal",
+  display: "auto"
 };
 
 // Non-default values for the FontFace descriptor attributes other than family
 // along with how Gecko currently serializes them.  Each value is chosen to be
 // different from the default value and also has a different serialized form.
 var nonDefaultValues = {
   style: ["Italic", "italic"],
   weight: ["Bold", "bold"],
   stretch: ["Ultra-Condensed", "ultra-condensed"],
   unicodeRange: ["U+3??", "U+300-3FF"],
   variant: ["Small-Caps", "small-caps"],
-  featureSettings: ["'dlig' on", "\"dlig\""]
+  featureSettings: ["'dlig' on", "\"dlig\""],
+  display: ["Block", "block"]
 };
 
 // Invalid values for the FontFace descriptor attributes other than family.
 var invalidValues = {
   style: "initial",
   weight: "bolder",
   stretch: "wider",
   unicodeRange: "U+1????-2????",
   variant: "inherit",
-  featureSettings: "dlig"
+  featureSettings: "dlig",
+  display: "normal"
 };
 
 // Invalid font family names.
 var invalidFontFamilyNames = [
   "", "\"\"", "sans-serif", "A, B", "inherit", "a 1"
 ];
 
 // Font family list where at least one is likely to be available on
@@ -154,18 +158,16 @@ function awaitRefresh() {
       requestAnimationFrame(aResolve);
     });
   }
 
   return awaitOneRefresh().then(awaitOneRefresh);
 }
 
 function runTest() {
-  SimpleTest.waitForExplicitFinish();
-
   // Document and window from inside the display:none iframe.
   var nframe = document.getElementById("n");
   var ndocument = nframe.contentDocument;
   var nwindow = nframe.contentWindow;
 
   // Document and window from inside the visible iframe.
   var vframe = document.getElementById("v");
   var vdocument = vframe.contentDocument;
@@ -1854,17 +1856,22 @@ function runTest() {
     ok(false, "Something failed: " + aError);
     SimpleTest.finish();
 
   });
 }
 
 function start() {
   if (SpecialPowers.getBoolPref("layout.css.font-loading-api.enabled")) {
-    runTest();
+    SpecialPowers.pushPrefEnv({ set: [["layout.css.font-display.enabled", true]] },
+                              runTest);
   } else {
     ok(true, "CSS Font Loading API is not enabled.");
   }
 }
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(5);
+
 </script>
 
 <style></style>
 <div></div>
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -625,16 +625,17 @@ pref("gfx.perf-warnings.enabled", false)
 // See eCMSMode in gfx/thebes/gfxPlatform.h
 pref("gfx.color_management.mode", 2);
 pref("gfx.color_management.display_profile", "");
 pref("gfx.color_management.rendering_intent", 0);
 pref("gfx.color_management.enablev4", false);
 
 pref("gfx.downloadable_fonts.enabled", true);
 pref("gfx.downloadable_fonts.fallback_delay", 3000);
+pref("gfx.downloadable_fonts.fallback_delay_short", 100);
 
 // disable downloadable font cache so that behavior is consistently
 // the uncached load behavior across pages (useful for testing reflow problems)
 pref("gfx.downloadable_fonts.disable_cache", false);
 
 pref("gfx.downloadable_fonts.woff2.enabled", true);
 
 #ifdef ANDROID