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 281246 c0af03c6f87794e20d8e065a689bae4c20d633ab
parent 281245 4a19d3a12a0b6fd0905df8b09783436e954f24fc
child 281247 b6379bec3c17319bb43f7f7ec7d69cbe8051c3c6
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdaggett, bzbarsky
bugs1072102
milestone41.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 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