Bug 1028497 - Part 21: Implement the FontFace constructor's parsing of descriptors. r=bzbarsky,jdaggett
authorCameron McCormack <cam@mcc.id.au>
Thu, 02 Oct 2014 12:32:09 +1000
changeset 231549 a558b520cc089a3d271a03fe1242fdc30876d4fe
parent 231548 19897140f7f2e1480fee807164d1a56d9c6cef08
child 231550 ec544f1e834910a37b2a7f913dc6a6b369e72193
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbzbarsky, jdaggett
bugs1028497
milestone35.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 1028497 - Part 21: Implement the FontFace constructor's parsing of descriptors. r=bzbarsky,jdaggett This implements the bulk of the FontFace JS constructor, which parses the descriptors passed in. We need a notion now of whether a FontFace is "initialized", since the spec requires us to go through the event loop before parsing the 'src' descriptor. So a couple of places now have to check whether the FontFace is fully initialized, and we have a method to inform the FontFaceSet when a FontFace becomes initialized, in case we added it to the FontFaceSet before it was initialized (easy to do with |document.fonts.add(new FontFace(...))|.
layout/style/FontFace.cpp
layout/style/FontFace.h
layout/style/FontFaceSet.cpp
layout/style/FontFaceSet.h
--- a/layout/style/FontFace.cpp
+++ b/layout/style/FontFace.cpp
@@ -3,22 +3,132 @@
  * 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 "mozilla/dom/FontFace.h"
 
 #include "mozilla/dom/FontFaceBinding.h"
 #include "mozilla/dom/FontFaceSet.h"
 #include "mozilla/dom/Promise.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/dom/UnionTypes.h"
 #include "nsCSSParser.h"
 #include "nsCSSRules.h"
 #include "nsIDocument.h"
 #include "nsStyleUtil.h"
 
-using namespace mozilla::dom;
+namespace mozilla {
+namespace dom {
+
+// -- FontFaceInitializer ----------------------------------------------------
+
+/**
+ * A task that is dispatched to the event queue to call Initialize() on a
+ * FontFace object with the source information that was passed to the JS
+ * constructor.
+ */
+class FontFaceInitializer : public nsIRunnable
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  FontFaceInitializer(FontFace* aFontFace)
+    : mFontFace(aFontFace)
+    , mSourceBuffer(nullptr)
+    , mSourceBufferLength(0) {}
+
+  void SetSource(const nsAString& aString);
+  void SetSource(const ArrayBuffer& aArrayBuffer);
+  void SetSource(const ArrayBufferView& aArrayBufferView);
+
+  NS_IMETHOD Run();
+
+  nsRefPtr<FontFace> mFontFace;
+  FontFace::SourceType mSourceType;
+  nsString mSourceString;
+  uint8_t* mSourceBuffer;  // allocated with NS_Alloc
+  uint32_t mSourceBufferLength;
+
+protected:
+  virtual ~FontFaceInitializer();
+};
+
+NS_IMPL_ISUPPORTS(FontFaceInitializer, nsIRunnable)
+
+FontFaceInitializer::~FontFaceInitializer()
+{
+  if (mSourceBuffer) {
+    NS_Free(mSourceBuffer);
+  }
+}
+
+void
+FontFaceInitializer::SetSource(const nsAString& aString)
+{
+  mSourceType = FontFace::eSourceType_URLs;
+  mSourceString = aString;
+}
+
+void
+FontFaceInitializer::SetSource(const ArrayBuffer& aArrayBuffer)
+{
+  mSourceType = FontFace::eSourceType_Buffer;
+  // XXX Do something with the array buffer data.
+}
+
+void
+FontFaceInitializer::SetSource(const ArrayBufferView& aArrayBufferView)
+{
+  mSourceType = FontFace::eSourceType_Buffer;
+  // XXX Do something with the array buffer data.
+}
+
+NS_IMETHODIMP
+FontFaceInitializer::Run()
+{
+  mFontFace->Initialize(this);
+  return NS_OK;
+}
+
+// -- FontFaceStatusSetter ---------------------------------------------------
+
+/**
+ * A task that is dispatched to the event queue to asynchronously call
+ * SetStatus() on a FontFace object.
+ */
+class FontFaceStatusSetter : public nsIRunnable
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  FontFaceStatusSetter(FontFace* aFontFace,
+                       FontFaceLoadStatus aStatus)
+    : mFontFace(aFontFace)
+    , mStatus(aStatus) {}
+
+  NS_IMETHOD Run();
+
+protected:
+  virtual ~FontFaceStatusSetter() {}
+
+private:
+  nsRefPtr<FontFace> mFontFace;
+  FontFaceLoadStatus mStatus;
+};
+
+NS_IMPL_ISUPPORTS(FontFaceStatusSetter, nsIRunnable)
+
+NS_IMETHODIMP
+FontFaceStatusSetter::Run()
+{
+  mFontFace->SetStatus(mStatus);
+  return NS_OK;
+}
+
+// -- FontFace ---------------------------------------------------------------
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(FontFace)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FontFace)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoaded)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRule)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet)
@@ -47,18 +157,23 @@ NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(FontFace)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(FontFace)
 
 FontFace::FontFace(nsISupports* aParent, nsPresContext* aPresContext)
   : mParent(aParent)
   , mPresContext(aPresContext)
   , mStatus(FontFaceLoadStatus::Unloaded)
+  , mSourceType(SourceType(0))
+  , mSourceBuffer(nullptr)
+  , mSourceBufferLength(0)
   , mFontFaceSet(aPresContext->Fonts())
   , mInFontFaceSet(false)
+  , mInitialized(false)
+  , mLoadWhenInitialized(false)
 {
   MOZ_COUNT_CTOR(FontFace);
 
   SetIsDOMBinding();
 
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aParent);
 
   if (global) {
@@ -71,16 +186,20 @@ FontFace::~FontFace()
 {
   MOZ_COUNT_DTOR(FontFace);
 
   SetUserFontEntry(nullptr);
 
   if (mFontFaceSet && !IsInFontFaceSet()) {
     mFontFaceSet->RemoveUnavailableFontFace(this);
   }
+
+  if (mSourceBuffer) {
+    NS_Free(mSourceBuffer);
+  }
 }
 
 JSObject*
 FontFace::WrapObject(JSContext* aCx)
 {
   return FontFaceBinding::Wrap(aCx, this);
 }
 
@@ -105,17 +224,19 @@ already_AddRefed<FontFace>
 FontFace::CreateForRule(nsISupports* aGlobal,
                         nsPresContext* aPresContext,
                         nsCSSFontFaceRule* aRule,
                         gfxUserFontEntry* aUserFontEntry)
 {
   nsCOMPtr<nsIGlobalObject> globalObject = do_QueryInterface(aGlobal);
 
   nsRefPtr<FontFace> obj = new FontFace(aGlobal, aPresContext);
+  obj->mInitialized = true;
   obj->mRule = aRule;
+  obj->mSourceType = eSourceType_FontFaceRule;
   obj->mInFontFaceSet = true;
   obj->SetUserFontEntry(aUserFontEntry);
   return obj.forget();
 }
 
 already_AddRefed<FontFace>
 FontFace::Constructor(const GlobalObject& aGlobal,
                       const nsAString& aFamily,
@@ -140,31 +261,90 @@ FontFace::Constructor(const GlobalObject
   nsPresContext* presContext = shell->GetPresContext();
   if (!presContext) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   nsRefPtr<FontFace> obj = new FontFace(global, presContext);
   obj->mFontFaceSet->AddUnavailableFontFace(obj);
+  if (!obj->SetDescriptors(aFamily, aDescriptors)) {
+    return obj.forget();
+  }
+
+  nsRefPtr<FontFaceInitializer> task = new FontFaceInitializer(obj);
+
+  if (aSource.IsArrayBuffer()) {
+    task->SetSource(aSource.GetAsArrayBuffer());
+  } else if (aSource.IsArrayBufferView()) {
+    task->SetSource(aSource.GetAsArrayBufferView());
+  } else {
+    MOZ_ASSERT(aSource.IsString());
+    task->SetSource(aSource.GetAsString());
+  }
+
+  NS_DispatchToMainThread(task);
+
   return obj.forget();
 }
 
 void
+FontFace::Initialize(FontFaceInitializer* aInitializer)
+{
+  MOZ_ASSERT(!IsConnected());
+  MOZ_ASSERT(mSourceType == SourceType(0));
+
+  if (aInitializer->mSourceType == eSourceType_URLs) {
+    if (!ParseDescriptor(eCSSFontDesc_Src,
+                         aInitializer->mSourceString,
+                         mDescriptors->mSrc)) {
+      if (mLoaded) {
+        // The asynchronous SetStatus call we are about to do assumes that for
+        // FontFace objects with sources other than ArrayBuffer(View)s, that the
+        // mLoaded Promise is rejected with a network error.  We get
+        // in here beforehand to set it to the required syntax error.
+        mLoaded->MaybeReject(NS_ERROR_DOM_SYNTAX_ERR);
+      }
+
+      // Queue a task to set the status to "error".
+      nsCOMPtr<nsIRunnable> statusSetterTask =
+        new FontFaceStatusSetter(this, FontFaceLoadStatus::Error);
+      NS_DispatchToMainThread(statusSetterTask);
+      return;
+    }
+
+    mSourceType = eSourceType_URLs;
+
+    // Now that we have parsed the src descriptor, we are initialized.
+    OnInitialized();
+    return;
+  }
+
+  // We've been given an ArrayBuffer or ArrayBufferView as the source.
+  MOZ_ASSERT(aInitializer->mSourceType == eSourceType_Buffer);
+
+  // XXX Handle array buffers.
+}
+
+void
 FontFace::GetFamily(nsString& aResult)
 {
   mPresContext->FlushUserFontSet();
 
   // Serialize the same way as in nsCSSFontFaceStyleDecl::GetPropertyValue.
   nsCSSValue value;
   GetDesc(eCSSFontDesc_Family, value);
 
   aResult.Truncate();
   nsDependentString family(value.GetStringBufferValue());
-  nsStyleUtil::AppendEscapedCSSString(family, aResult);
+  if (!family.IsEmpty()) {
+    // The string length can be zero when the author passed an invalid
+    // family name or an invalid descriptor to the JS FontFace constructor.
+    nsStyleUtil::AppendEscapedCSSString(family, aResult);
+  }
 }
 
 void
 FontFace::SetFamily(const nsAString& aValue, ErrorResult& aRv)
 {
   mPresContext->FlushUserFontSet();
   SetDescriptor(eCSSFontDesc_Family, aValue, aRv);
 }
@@ -279,23 +459,41 @@ FontFace::Load(ErrorResult& aRv)
 {
   mPresContext->FlushUserFontSet();
 
   if (!mLoaded) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
-  if (mStatus != FontFaceLoadStatus::Unloaded) {
+  // Calling Load on a FontFace constructed with an ArrayBuffer data source,
+  // or on one that is already loading (or has finished loading), has no
+  // effect.
+  if (mSourceType == eSourceType_Buffer ||
+      mStatus != FontFaceLoadStatus::Unloaded) {
     return mLoaded;
   }
 
+  // Calling the user font entry's Load method will end up setting our
+  // status to Loading, but the spec requires us to set it to Loading
+  // here.
   SetStatus(FontFaceLoadStatus::Loading);
 
-  mUserFontEntry->Load();
+  if (mInitialized) {
+    // XXX For FontFace objects not in the FontFaceSet, we will need a
+    // way to create a user font entry.
+    if (mUserFontEntry) {
+      mUserFontEntry->Load();
+    }
+  } else {
+    // We can only load an initialized font; this will cause the font to be
+    // loaded once it has been initialized.
+    mLoadWhenInitialized = true;
+  }
+
   return mLoaded;
 }
 
 Promise*
 FontFace::GetLoaded(ErrorResult& aRv)
 {
   mPresContext->FlushUserFontSet();
 
@@ -362,37 +560,98 @@ FontFace::ParseDescriptor(nsCSSFontDesc 
   return true;
 }
 
 void
 FontFace::SetDescriptor(nsCSSFontDesc aFontDesc,
                         const nsAString& aValue,
                         ErrorResult& aRv)
 {
-  NS_ASSERTION(!mRule, "we don't handle rule-connected FontFace objects yet");
-  if (mRule) {
+  NS_ASSERTION(!IsConnected(),
+               "we don't handle rule-connected FontFace objects yet");
+  if (IsConnected()) {
     return;
   }
 
   nsCSSValue parsedValue;
   if (!ParseDescriptor(aFontDesc, aValue, parsedValue)) {
     aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
     return;
   }
 
   mDescriptors->Get(aFontDesc) = parsedValue;
 
   // XXX Setting descriptors doesn't actually have any effect on FontFace
   // objects that have started loading or have already been loaded.
 }
 
+bool
+FontFace::SetDescriptors(const nsAString& aFamily,
+                         const FontFaceDescriptors& aDescriptors)
+{
+  MOZ_ASSERT(!IsConnected());
+  MOZ_ASSERT(!mDescriptors);
+
+  mDescriptors = new CSSFontFaceDescriptors;
+
+  // Parse all of the mDescriptors in aInitializer, which are the values
+  // we got from the JS constructor.
+  if (!ParseDescriptor(eCSSFontDesc_Family,
+                       aFamily,
+                       mDescriptors->mFamily) ||
+      *mDescriptors->mFamily.GetStringBufferValue() == 0 ||
+      !ParseDescriptor(eCSSFontDesc_Style,
+                       aDescriptors.mStyle,
+                       mDescriptors->mStyle) ||
+      !ParseDescriptor(eCSSFontDesc_Weight,
+                       aDescriptors.mWeight,
+                       mDescriptors->mWeight) ||
+      !ParseDescriptor(eCSSFontDesc_Stretch,
+                       aDescriptors.mStretch,
+                       mDescriptors->mStretch) ||
+      !ParseDescriptor(eCSSFontDesc_UnicodeRange,
+                       aDescriptors.mUnicodeRange,
+                       mDescriptors->mUnicodeRange) ||
+      !ParseDescriptor(eCSSFontDesc_FontFeatureSettings,
+                       aDescriptors.mFeatureSettings,
+                       mDescriptors->mFontFeatureSettings)) {
+    // 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);
+    }
+
+    SetStatus(FontFaceLoadStatus::Error);
+    return false;
+  }
+
+  return true;
+}
+
+void
+FontFace::OnInitialized()
+{
+  MOZ_ASSERT(!mInitialized);
+
+  mInitialized = true;
+
+  if (mInFontFaceSet) {
+    mFontFaceSet->OnFontFaceInitialized(this);
+  }
+}
+
 void
 FontFace::GetDesc(nsCSSFontDesc aDescID, nsCSSValue& aResult) const
 {
-  if (mRule) {
+  if (IsConnected()) {
+    MOZ_ASSERT(mRule);
     MOZ_ASSERT(!mDescriptors);
     mRule->GetDesc(aDescID, aResult);
   } else {
     aResult = mDescriptors->Get(aDescID);
   }
 }
 
 void
@@ -482,8 +741,11 @@ FontFace::DisconnectFromRule()
 FontFace::Entry::SetLoadState(UserFontLoadState aLoadState)
 {
   gfxUserFontEntry::SetLoadState(aLoadState);
 
   for (size_t i = 0; i < mFontFaces.Length(); i++) {
     mFontFaces[i]->SetStatus(LoadStateToStatus(aLoadState));
   }
 }
+
+} // namespace dom
+} // namespace mozilla
--- a/layout/style/FontFace.h
+++ b/layout/style/FontFace.h
@@ -15,27 +15,31 @@
 class nsCSSFontFaceRule;
 class nsPresContext;
 
 namespace mozilla {
 struct CSSFontFaceDescriptors;
 namespace dom {
 struct FontFaceDescriptors;
 class FontFaceSet;
+class FontFaceInitializer;
+class FontFaceStatusSetter;
 class Promise;
 class StringOrArrayBufferOrArrayBufferView;
 }
 }
 
 namespace mozilla {
 namespace dom {
 
 class FontFace MOZ_FINAL : public nsISupports,
                            public nsWrapperCache
 {
+  friend class mozilla::dom::FontFaceInitializer;
+  friend class mozilla::dom::FontFaceStatusSetter;
   friend class Entry;
 
 public:
   class Entry MOZ_FINAL : public gfxUserFontEntry {
     friend class FontFace;
 
   public:
     Entry(gfxUserFontSet* aFontSet,
@@ -74,19 +78,32 @@ public:
 
   nsCSSFontFaceRule* GetRule() { return mRule; }
 
   void GetDesc(nsCSSFontDesc aDescID, nsCSSValue& aResult) const;
 
   gfxUserFontEntry* GetUserFontEntry() const { return mUserFontEntry; }
   void SetUserFontEntry(gfxUserFontEntry* aEntry);
 
+  /**
+   * Returns whether this object is in a FontFaceSet.
+   */
   bool IsInFontFaceSet() { return mInFontFaceSet; }
 
   /**
+   * Returns whether this FontFace is initialized.  A CSS-connected
+   * FontFace is considered initialized at construction time.  For
+   * FontFace objects created using the FontFace JS constructor, it
+   * is once all the descriptors have been parsed.
+   */
+  bool IsInitialized() const { return mInitialized; }
+
+  FontFaceSet* GetFontFaceSet() const { return mFontFaceSet; }
+
+  /**
    * Gets the family name of the FontFace as a raw string (such as 'Times', as
    * opposed to GetFamily, which returns a CSS-escaped string, such as
    * '"Times"').  Returns whether a valid family name was available.
    */
   bool GetFamilyName(nsString& aResult);
 
   /**
    * Returns whether this object is CSS-connected, i.e. reflecting an
@@ -126,28 +143,49 @@ public:
   mozilla::dom::Promise* Load(mozilla::ErrorResult& aRv);
   mozilla::dom::Promise* GetLoaded(mozilla::ErrorResult& aRv);
 
 private:
   FontFace(nsISupports* aParent, nsPresContext* aPresContext);
   ~FontFace();
 
   /**
+   * Initializes the source and descriptors on this object based on values that
+   * were passed in to the JS constructor.  If the source was specified as
+   * an ArrayBuffer or ArrayBufferView, parsing of the font data in there
+   * will be started.
+   */
+  void Initialize(FontFaceInitializer* aInitializer);
+
+  /**
    * Parses a @font-face descriptor value, storing the result in aResult.
    * Returns whether the parsing was successful.
    */
   bool ParseDescriptor(nsCSSFontDesc aDescID, const nsAString& aString,
                        nsCSSValue& aResult);
 
   // Helper function for the descriptor setter methods.
   void SetDescriptor(nsCSSFontDesc aFontDesc,
                      const nsAString& aValue,
                      mozilla::ErrorResult& aRv);
 
   /**
+   * Sets all of the descriptor values in mDescriptors using values passed
+   * to the JS constructor.
+   */
+  bool SetDescriptors(const nsAString& aFamily,
+                      const FontFaceDescriptors& aDescriptors);
+
+  /**
+   * Marks the FontFace as initialized and informs the FontFaceSet it is in,
+   * if any.
+   */
+  void OnInitialized();
+
+  /**
    * Sets the current loading status.
    */
   void SetStatus(mozilla::dom::FontFaceLoadStatus aStatus);
 
   void GetDesc(nsCSSFontDesc aDescID,
                nsCSSProperty aPropID,
                nsString& aResult) const;
 
@@ -167,25 +205,50 @@ private:
   nsRefPtr<Entry> mUserFontEntry;
 
   // The current load status of the font represented by this FontFace.
   // Note that we can't just reflect the value of the gfxUserFontEntry's
   // status, since the spec sometimes requires us to go through the event
   // loop before updating the status, rather than doing it immediately.
   mozilla::dom::FontFaceLoadStatus mStatus;
 
+  // Represents where a FontFace's data is coming from.
+  enum SourceType {
+    eSourceType_FontFaceRule = 1,
+    eSourceType_URLs,
+    eSourceType_Buffer
+  };
+
+  // Where the font data for this FontFace is coming from.
+  SourceType mSourceType;
+
+  // If the FontFace was constructed with an ArrayBuffer(View), this is a
+  // copy of the data from it.
+  uint8_t* mSourceBuffer;
+  uint32_t mSourceBufferLength;
+
   // The values corresponding to the font face descriptors, if we are not
   // a CSS-connected FontFace object.  For CSS-connected objects, we use
   // the descriptors stored in mRule.
   nsAutoPtr<mozilla::CSSFontFaceDescriptors> mDescriptors;
 
   // The FontFaceSet this FontFace is associated with, regardless of whether
   // it is currently "in" the set.
   nsRefPtr<FontFaceSet> mFontFaceSet;
 
   // Whether this FontFace appears in the FontFaceSet.
   bool mInFontFaceSet;
+
+  // Whether the FontFace has been fully initialized.  This takes at least one
+  // run around the event loop, as the parsing of the src descriptor is done
+  // off an event queue task.
+  bool mInitialized;
+
+  // Records whether Load() was called on this FontFace before it was
+  // initialized.  When the FontFace eventually does become initialized,
+  // mLoadPending is checked and Load() is called if needed.
+  bool mLoadWhenInitialized;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // !defined(mozilla_dom_FontFace_h)
--- a/layout/style/FontFaceSet.cpp
+++ b/layout/style/FontFaceSet.cpp
@@ -166,16 +166,23 @@ FontFaceSet::Clear()
 
 bool
 FontFaceSet::Delete(FontFace& aFontFace, ErrorResult& aRv)
 {
   return false;
 }
 
 bool
+FontFaceSet::HasAvailableFontFace(FontFace* aFontFace)
+{
+  return aFontFace->GetFontFaceSet() == this &&
+         aFontFace->IsInFontFaceSet();
+}
+
+bool
 FontFaceSet::Has(FontFace& aFontFace)
 {
   return false;
 }
 
 FontFace*
 FontFaceSet::IndexedGetter(uint32_t aIndex, bool& aFound)
 {
@@ -477,16 +484,24 @@ FontFaceSet::IncrementGeneration(bool aI
   MOZ_ASSERT(mUserFontSet);
   mUserFontSet->IncrementGeneration(aIsRebuild);
 }
 
 void
 FontFaceSet::InsertUnconnectedFontFace(FontFace* aFontFace,
                                        bool& aFontSetModified)
 {
+  if (!aFontFace->IsInitialized()) {
+    // The FontFace is still waiting to be initialized, so don't create a
+    // user font entry for it yet.  Once it has been initialized, it will
+    // call OnFontFaceInitialized on us, which will end rebuild the user
+    // font set and end up back in here.
+    return;
+  }
+
   nsAutoString fontfamily;
   if (!aFontFace->GetFamilyName(fontfamily)) {
     // If there is no family name, this rule cannot contribute a
     // usable font, so there is no point in processing it further.
     return;
   }
 
   // Just create a new font entry if we haven't got one already.
@@ -1096,16 +1111,17 @@ FontFaceSet::FontFaceForRule(nsCSSFontFa
     FontFace::CreateForRule(GetParentObject(), mPresContext, aRule, entry);
   aRule->SetFontFace(newFontFace);
   return newFontFace;
 }
 
 void
 FontFaceSet::AddUnavailableFontFace(FontFace* aFontFace)
 {
+  MOZ_ASSERT(!aFontFace->IsConnected());
   MOZ_ASSERT(!aFontFace->IsInFontFaceSet());
   MOZ_ASSERT(!mUnavailableFaces.Contains(aFontFace));
 
   mUnavailableFaces.AppendElement(aFontFace);
 }
 
 void
 FontFaceSet::RemoveUnavailableFontFace(FontFace* aFontFace)
@@ -1116,16 +1132,26 @@ FontFaceSet::RemoveUnavailableFontFace(F
   // We might not actually find the FontFace in mUnavailableFaces, since we
   // might be shutting down the document and had DestroyUserFontSet called
   // on us, which clears out mUnavailableFaces.
   mUnavailableFaces.RemoveElement(aFontFace);
 
   MOZ_ASSERT(!mUnavailableFaces.Contains(aFontFace));
 }
 
+void
+FontFaceSet::OnFontFaceInitialized(FontFace* aFontFace)
+{
+  MOZ_ASSERT(HasAvailableFontFace(aFontFace));
+  MOZ_ASSERT(!aFontFace->IsConnected());
+  MOZ_ASSERT(aFontFace->IsInitialized());
+
+  mPresContext->RebuildUserFontSet();
+}
+
 // -- FontFaceSet::UserFontSet ------------------------------------------------
 
 /* virtual */ nsresult
 FontFaceSet::UserFontSet::CheckFontLoad(const gfxFontFaceSrc* aFontFaceSrc,
                                         nsIPrincipal** aPrincipal,
                                         bool* aBypassCache)
 {
   if (!mFontFaceSet) {
--- a/layout/style/FontFaceSet.h
+++ b/layout/style/FontFaceSet.h
@@ -119,16 +119,25 @@ public:
   void AddUnavailableFontFace(FontFace* aFontFace);
 
   /**
    * Removes the specified FontFace from the mUnavailableFaces array.  This
    * is called when a FontFace object is about be destroyed.
    */
   void RemoveUnavailableFontFace(FontFace* aFontFace);
 
+  /**
+   * Notification method called by a FontFace once it has been initialized.
+   *
+   * This is needed for the FontFaceSet to handle a FontFace that was created
+   * and inserted into the set immediately, before the event loop has spun and
+   * the FontFace's initialization tasks have run.
+   */
+  void OnFontFaceInitialized(FontFace* aFontFace);
+
   // -- Web IDL --------------------------------------------------------------
 
   IMPL_EVENT_HANDLER(loading)
   IMPL_EVENT_HANDLER(loadingdone)
   IMPL_EVENT_HANDLER(loadingerror)
   already_AddRefed<mozilla::dom::Promise> Load(const nsAString& aFont,
                                                const nsAString& aText,
                                                mozilla::ErrorResult& aRv);
@@ -143,16 +152,21 @@ public:
   bool Delete(FontFace& aFontFace, mozilla::ErrorResult& aRv);
   bool Has(FontFace& aFontFace);
   FontFace* IndexedGetter(uint32_t aIndex, bool& aFound);
   uint32_t Length();
 
 private:
   ~FontFaceSet();
 
+  /**
+   * Returns whether the given FontFace is currently "in" the FontFaceSet.
+   */
+  bool HasAvailableFontFace(FontFace* aFontFace);
+
   // Note: if you add new cycle collected objects to FontFaceRecord,
   // make sure to update FontFaceSet's cycle collection macros
   // accordingly.
   struct FontFaceRecord {
     nsRefPtr<FontFace> mFontFace;
     uint8_t mSheetType;
   };