Bug 1072102 - Part 1: Implement FontFaceSet load and check. r=jdaggett,bzbarsky
authorCameron McCormack <cam@mcc.id.au>
Sat, 27 Jun 2015 11:41:10 +1000
changeset 268568 c0af03c6f87794e20d8e065a689bae4c20d633ab
parent 268567 4a19d3a12a0b6fd0905df8b09783436e954f24fc
child 268569 b6379bec3c17319bb43f7f7ec7d69cbe8051c3c6
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-esr52@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdaggett, bzbarsky
bugs1072102
milestone41.0a1
Bug 1072102 - Part 1: Implement FontFaceSet load and check. r=jdaggett,bzbarsky
dom/bindings/Bindings.conf
dom/webidl/FontFaceSet.webidl
gfx/thebes/gfxFontConstants.h
layout/style/FontFace.cpp
layout/style/FontFace.h
layout/style/FontFaceSet.cpp
layout/style/FontFaceSet.h
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -490,16 +490,20 @@ DOMInterfaces = {
     'implicitJSContext': [ 'readAsArrayBuffer' ],
 },
 
 'FileReaderSync': {
     'workers': True,
     'wrapperCache': False,
 },
 
+'FontFaceSet': {
+    'implicitJSContext': [ 'load' ],
+},
+
 'FontFaceSetIterator': {
     'wrapperCache': False,
 },
 
 'FormData': {
     'nativeType': 'nsFormData'
 },
 
--- a/dom/webidl/FontFaceSet.webidl
+++ b/dom/webidl/FontFaceSet.webidl
@@ -45,22 +45,20 @@ interface FontFaceSet : EventTarget {
 
   // -- events for when loading state changes
   attribute EventHandler onloading;
   attribute EventHandler onloadingdone;
   attribute EventHandler onloadingerror;
 
   // check and start loads if appropriate
   // and fulfill promise when all loads complete
-  // Not implemented yet: bug 1072102.
-  // [Throws] Promise<sequence<FontFace>> load(DOMString font, optional DOMString text = " ");
+  [NewObject] Promise<sequence<FontFace>> load(DOMString font, optional DOMString text = " ");
 
   // return whether all fonts in the fontlist are loaded
   // (does not initiate load if not available)
-  // Not implemented yet: bug 1072102.
-  // [Throws] boolean check(DOMString font, optional DOMString text = " ");
+  [Throws] boolean check(DOMString font, optional DOMString text = " ");
 
   // async notification that font loading and layout operations are done
   [Throws] readonly attribute Promise<void> ready;
 
   // loading state, "loading" while one or more fonts loading, "loaded" otherwise
   readonly attribute FontFaceSetLoadStatus status;
 };
--- a/gfx/thebes/gfxFontConstants.h
+++ b/gfx/thebes/gfxFontConstants.h
@@ -14,16 +14,17 @@
  */
 
 #define NS_FONT_STYLE_NORMAL            0
 #define NS_FONT_STYLE_ITALIC            1
 #define NS_FONT_STYLE_OBLIQUE           2
 
 #define NS_FONT_WEIGHT_NORMAL           400
 #define NS_FONT_WEIGHT_BOLD             700
+#define NS_FONT_WEIGHT_THIN             100
 
 #define NS_FONT_STRETCH_ULTRA_CONDENSED (-4)
 #define NS_FONT_STRETCH_EXTRA_CONDENSED (-3)
 #define NS_FONT_STRETCH_CONDENSED       (-2)
 #define NS_FONT_STRETCH_SEMI_CONDENSED  (-1)
 #define NS_FONT_STRETCH_NORMAL          0
 #define NS_FONT_STRETCH_SEMI_EXPANDED   1
 #define NS_FONT_STRETCH_EXPANDED        2
--- a/layout/style/FontFace.cpp
+++ b/layout/style/FontFace.cpp
@@ -381,33 +381,40 @@ FontFace::Load(ErrorResult& aRv)
   // here.
   SetStatus(FontFaceLoadStatus::Loading);
 
   DoLoad();
 
   return mLoaded;
 }
 
-void
-FontFace::DoLoad()
+gfxUserFontEntry*
+FontFace::CreateUserFontEntry()
 {
   if (!mUserFontEntry) {
     MOZ_ASSERT(!HasRule(),
                "Rule backed FontFace objects should already have a user font "
                "entry by the time Load() can be called on them");
 
     nsRefPtr<gfxUserFontEntry> newEntry =
       mFontFaceSet->FindOrCreateUserFontEntryFromFontFace(this);
-    if (!newEntry) {
-      return;
+    if (newEntry) {
+      SetUserFontEntry(newEntry);
     }
-
-    SetUserFontEntry(newEntry);
   }
 
+  return mUserFontEntry;
+}
+
+void
+FontFace::DoLoad()
+{
+  if (!CreateUserFontEntry()) {
+    return;
+  }
   mUserFontEntry->Load();
 }
 
 Promise*
 FontFace::GetLoaded(ErrorResult& aRv)
 {
   mFontFaceSet->FlushUserFontSet();
 
--- a/layout/style/FontFace.h
+++ b/layout/style/FontFace.h
@@ -48,16 +48,17 @@ public:
           const nsTArray<gfxFontFeature>& aFeatureSettings,
           uint32_t aLanguageOverride,
           gfxSparseBitSet* aUnicodeRanges)
       : gfxUserFontEntry(aFontSet, aFontFaceSrcList, aWeight, aStretch,
                          aItalicStyle, aFeatureSettings, aLanguageOverride,
                          aUnicodeRanges) {}
 
     virtual void SetLoadState(UserFontLoadState aLoadState) 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
     // cache can return the same entry for different FontFaces that have
     // the same descriptor values and come from the same origin.
     nsAutoTArray<FontFace*,1> mFontFaces;
   };
@@ -71,16 +72,17 @@ public:
   static already_AddRefed<FontFace>
   CreateForRule(nsISupports* aGlobal, FontFaceSet* aFontFaceSet,
                 nsCSSFontFaceRule* aRule);
 
   nsCSSFontFaceRule* GetRule() { return mRule; }
 
   void GetDesc(nsCSSFontDesc aDescID, nsCSSValue& aResult) const;
 
+  gfxUserFontEntry* CreateUserFontEntry();
   gfxUserFontEntry* GetUserFontEntry() const { return mUserFontEntry; }
   void SetUserFontEntry(gfxUserFontEntry* aEntry);
 
   /**
    * Returns whether this object is in a FontFaceSet.
    */
   bool IsInFontFaceSet() { return mInFontFaceSet; }
 
--- a/layout/style/FontFaceSet.cpp
+++ b/layout/style/FontFaceSet.cpp
@@ -1,45 +1,50 @@
 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* 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 "FontFaceSet.h"
 
-#include "mozilla/Logging.h"
-
+#include "gfxFontConstants.h"
+#include "mozilla/css/Declaration.h"
 #include "mozilla/css/Loader.h"
 #include "mozilla/dom/CSSFontFaceLoadEvent.h"
 #include "mozilla/dom/CSSFontFaceLoadEventBinding.h"
 #include "mozilla/dom/FontFaceSetBinding.h"
 #include "mozilla/dom/FontFaceSetIterator.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/Logging.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Snprintf.h"
 #include "nsCORSListenerProxy.h"
+#include "nsCSSParser.h"
+#include "nsDeviceContext.h"
 #include "nsFontFaceLoader.h"
 #include "nsIConsoleService.h"
 #include "nsIContentPolicy.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsIDocShell.h"
 #include "nsIDocument.h"
 #include "nsINetworkPredictor.h"
 #include "nsIPresShell.h"
 #include "nsIPrincipal.h"
 #include "nsISupportsPriority.h"
 #include "nsIWebNavigation.h"
 #include "nsNetUtil.h"
 #include "nsPresContext.h"
 #include "nsPrintfCString.h"
 #include "nsStyleSet.h"
+#include "nsUTF8Utils.h"
 
 using namespace mozilla;
+using namespace mozilla::css;
 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)
 
 #define FONT_LOADING_API_ENABLED_PREF "layout.css.font-loading-api.enabled"
 
@@ -156,32 +161,213 @@ void
 FontFaceSet::RemoveDOMContentLoadedListener()
 {
   if (mDocument) {
     mDocument->RemoveSystemEventListener(NS_LITERAL_STRING("DOMContentLoaded"),
                                          this, false);
   }
 }
 
+void
+FontFaceSet::ParseFontShorthandForMatching(
+                            const nsAString& aFont,
+                            nsRefPtr<FontFamilyListRefCnt>& aFamilyList,
+                            uint32_t& aWeight,
+                            int32_t& aStretch,
+                            uint32_t& aItalicStyle,
+                            ErrorResult& aRv)
+{
+  // Parse aFont as a 'font' property value.
+  Declaration declaration;
+  declaration.InitializeEmpty();
+
+  bool changed = false;
+  nsCSSParser parser;
+  parser.ParseProperty(eCSSProperty_font,
+                       aFont,
+                       mDocument->GetDocumentURI(),
+                       mDocument->GetDocumentURI(),
+                       mDocument->NodePrincipal(),
+                       &declaration,
+                       &changed,
+                       /* aIsImportant */ false);
+
+  // All of the properties we are interested in should have been set at once.
+  MOZ_ASSERT(changed == (declaration.HasProperty(eCSSProperty_font_family) &&
+                         declaration.HasProperty(eCSSProperty_font_style) &&
+                         declaration.HasProperty(eCSSProperty_font_weight) &&
+                         declaration.HasProperty(eCSSProperty_font_stretch)));
+
+  if (!changed) {
+    aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+    return;
+  }
+
+  nsCSSCompressedDataBlock* data = declaration.GetNormalBlock();
+  MOZ_ASSERT(!declaration.GetImportantBlock());
+
+  const nsCSSValue* family = data->ValueFor(eCSSProperty_font_family);
+  if (family->GetUnit() != eCSSUnit_FontFamilyList) {
+    // We got inherit, initial, unset, a system font, or a token stream.
+    aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+    return;
+  }
+
+  aFamilyList =
+    static_cast<FontFamilyListRefCnt*>(family->GetFontFamilyListValue());
+
+  int32_t weight = data->ValueFor(eCSSProperty_font_weight)->GetIntValue();
+
+  // Resolve relative font weights against the initial of font-weight
+  // (normal, which is equivalent to 400).
+  if (weight == NS_STYLE_FONT_WEIGHT_BOLDER) {
+    weight = NS_FONT_WEIGHT_BOLD;
+  } else if (weight == NS_STYLE_FONT_WEIGHT_LIGHTER) {
+    weight = NS_FONT_WEIGHT_THIN;
+  }
+
+  aWeight = weight;
+
+  aStretch = data->ValueFor(eCSSProperty_font_stretch)->GetIntValue();
+  aItalicStyle = data->ValueFor(eCSSProperty_font_style)->GetIntValue();
+}
+
+static bool
+HasAnyCharacterInUnicodeRange(gfxUserFontEntry* aEntry,
+                              const nsAString& aInput)
+{
+  const char16_t* p = aInput.Data();
+  const char16_t* end = p + aInput.Length();
+
+  while (p < end) {
+    uint32_t c = UTF16CharEnumerator::NextChar(&p, end);
+    if (aEntry->CharacterInUnicodeRange(c)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void
+FontFaceSet::FindMatchingFontFaces(const nsAString& aFont,
+                                   const nsAString& aText,
+                                   nsTArray<FontFace*>& aFontFaces,
+                                   ErrorResult& aRv)
+{
+  nsRefPtr<FontFamilyListRefCnt> familyList;
+  uint32_t weight;
+  int32_t stretch;
+  uint32_t italicStyle;
+  ParseFontShorthandForMatching(aFont, familyList, weight, stretch, italicStyle,
+                                aRv);
+  if (aRv.Failed()) {
+    return;
+  }
+
+  gfxFontStyle style;
+  style.style = italicStyle;
+  style.weight = weight;
+  style.stretch = stretch;
+
+  nsTArray<FontFaceRecord>* arrays[2];
+  arrays[0] = &mNonRuleFaces;
+  arrays[1] = &mRuleFaces;
+
+  // Set of FontFaces that we want to return.
+  nsTHashtable<nsPtrHashKey<FontFace>> matchingFaces;
+
+  for (const FontFamilyName& fontFamilyName : familyList->GetFontlist()) {
+    nsRefPtr<gfxFontFamily> family =
+      mUserFontSet->LookupFamily(fontFamilyName.mName);
+
+    if (!family) {
+      continue;
+    }
+
+    nsAutoTArray<gfxFontEntry*,4> entries;
+    bool needsBold;
+    family->FindAllFontsForStyle(style, entries, needsBold);
+
+    for (gfxFontEntry* e : entries) {
+      FontFace::Entry* entry = static_cast<FontFace::Entry*>(e);
+      if (HasAnyCharacterInUnicodeRange(entry, aText)) {
+        for (FontFace* f : entry->GetFontFaces()) {
+          matchingFaces.PutEntry(f);
+        }
+      }
+    }
+  }
+
+  // Add all FontFaces in matchingFaces to aFontFaces, in the order
+  // they appear in the FontFaceSet.
+  for (nsTArray<FontFaceRecord>* array : arrays) {
+    for (FontFaceRecord& record : *array) {
+      FontFace* f = record.mFontFace;
+      if (matchingFaces.Contains(f)) {
+        aFontFaces.AppendElement(f);
+      }
+    }
+  }
+}
+
 already_AddRefed<Promise>
-FontFaceSet::Load(const nsAString& aFont,
+FontFaceSet::Load(JSContext* aCx,
+                  const nsAString& aFont,
                   const nsAString& aText,
                   ErrorResult& aRv)
 {
-  aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
-  return nullptr;
+  FlushUserFontSet();
+
+  nsTArray<nsRefPtr<Promise>> promises;
+
+  nsTArray<FontFace*> faces;
+  FindMatchingFontFaces(aFont, aText, faces, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  for (FontFace* f : faces) {
+    nsRefPtr<Promise> promise = f->Load(aRv);
+    if (aRv.Failed()) {
+      return nullptr;
+    }
+    if (!promises.AppendElement(promise, fallible)) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return nullptr;
+    }
+  }
+
+  nsIGlobalObject* globalObject = GetParentObject();
+  JS::Rooted<JSObject*> jsGlobal(aCx, globalObject->GetGlobalJSObject());
+  GlobalObject global(aCx, jsGlobal);
+
+  nsRefPtr<Promise> result = Promise::All(global, promises, aRv);
+  return result.forget();
 }
 
 bool
 FontFaceSet::Check(const nsAString& aFont,
                    const nsAString& aText,
                    ErrorResult& aRv)
 {
-  aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
-  return false;
+  FlushUserFontSet();
+
+  nsTArray<FontFace*> faces;
+  FindMatchingFontFaces(aFont, aText, faces, aRv);
+  if (aRv.Failed()) {
+    return false;
+  }
+
+  for (FontFace* f : faces) {
+    if (f->Status() != FontFaceLoadStatus::Loaded) {
+      return false;
+    }
+  }
+
+  return true;
 }
 
 Promise*
 FontFaceSet::GetReady(ErrorResult& aRv)
 {
   if (!mReady) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
--- a/layout/style/FontFaceSet.h
+++ b/layout/style/FontFaceSet.h
@@ -16,16 +16,19 @@
 
 struct gfxFontFaceSrc;
 class gfxUserFontEntry;
 class nsFontFaceLoader;
 class nsIPrincipal;
 class nsPIDOMWindow;
 
 namespace mozilla {
+namespace css {
+class FontFamilyListRefCnt;
+}
 namespace dom {
 class FontFace;
 class Promise;
 }
 }
 
 namespace mozilla {
 namespace dom {
@@ -146,17 +149,18 @@ public:
 
   void FlushUserFontSet();
 
   // -- Web IDL --------------------------------------------------------------
 
   IMPL_EVENT_HANDLER(loading)
   IMPL_EVENT_HANDLER(loadingdone)
   IMPL_EVENT_HANDLER(loadingerror)
-  already_AddRefed<mozilla::dom::Promise> Load(const nsAString& aFont,
+  already_AddRefed<mozilla::dom::Promise> Load(JSContext* aCx,
+                                               const nsAString& aFont,
                                                const nsAString& aText,
                                                mozilla::ErrorResult& aRv);
   bool Check(const nsAString& aFont,
              const nsAString& aText,
              mozilla::ErrorResult& aRv);
   mozilla::dom::Promise* GetReady(mozilla::ErrorResult& aRv);
   mozilla::dom::FontFaceSetLoadStatus Status();
 
@@ -266,16 +270,28 @@ private:
   /**
    * Returns whether we have any loading FontFace objects in the FontFaceSet.
    */
   bool HasLoadingFontFaces();
 
   // Helper function for HasLoadingFontFaces.
   void UpdateHasLoadingFontFaces();
 
+  void ParseFontShorthandForMatching(
+              const nsAString& aFont,
+              nsRefPtr<mozilla::css::FontFamilyListRefCnt>& aFamilyList,
+              uint32_t& aWeight,
+              int32_t& aStretch,
+              uint32_t& aItalicStyle,
+              ErrorResult& aRv);
+  void FindMatchingFontFaces(const nsAString& aFont,
+                             const nsAString& aText,
+                             nsTArray<FontFace*>& aFontFaces,
+                             mozilla::ErrorResult& aRv);
+
   nsRefPtr<UserFontSet> mUserFontSet;
 
   // The document this is a FontFaceSet for.
   nsCOMPtr<nsIDocument> mDocument;
 
   // A Promise that is fulfilled once all of the FontFace objects
   // in mRuleFaces and mNonRuleFaces that started or were loading at the
   // time the Promise was created have finished loading.  It is rejected if