Bug 549861. Implement rendering support for simple font-variant properties. r=jkew
authorJohn Daggett <jdaggett@mozilla.com>
Mon, 13 May 2013 18:45:36 +0900
changeset 144110 dea9df4a487db3ccafb3332946a2480aeb22f5a5
parent 144109 ddff3c5c6f05b9b3aefcc6441240cd5940074bff
child 144111 d97d070db34414267f1fbf5379eb896d4ff47c0d
push id368
push userbbajaj@mozilla.com
push dateMon, 09 Sep 2013 22:57:58 +0000
treeherdermozilla-release@5a4f47ae1217 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjkew
bugs549861
milestone23.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 549861. Implement rendering support for simple font-variant properties. r=jkew
gfx/src/nsFont.cpp
gfx/tests/gfxFontSelectionTests.h
gfx/tests/gfxTextRunPerfTest.cpp
gfx/tests/gfxWordCacheTest.cpp
gfx/thebes/gfxFont.cpp
gfx/thebes/gfxFont.h
gfx/thebes/gfxGraphiteShaper.cpp
gfx/thebes/gfxHarfBuzzShaper.cpp
layout/reftests/font-features/font-features-order-3.html
layout/reftests/font-features/font-features-order-4.html
layout/reftests/font-features/font-features-order-5.html
layout/reftests/font-features/font-kerning-1.html
layout/reftests/font-features/font-kerning-2.html
layout/reftests/font-features/font-kerning-3.html
layout/reftests/font-features/font-kerning-auto.html
layout/reftests/font-features/font-kerning-kern.html
layout/reftests/font-features/font-kerning-nokern.html
layout/reftests/font-features/font-kerning-none.html
layout/reftests/font-features/font-kerning-normal.html
layout/reftests/font-features/font-kerning-table-none.html
layout/reftests/font-features/font-kerning-table-normal.html
layout/reftests/font-features/font-variant-caps-ref.html
layout/reftests/font-features/font-variant-caps.html
layout/reftests/font-features/font-variant-debug.html
layout/reftests/font-features/font-variant-east-asian-ref.html
layout/reftests/font-features/font-variant-east-asian.html
layout/reftests/font-features/font-variant-features.css
layout/reftests/font-features/font-variant-features.js
layout/reftests/font-features/font-variant-ligatures-ref.html
layout/reftests/font-features/font-variant-ligatures.html
layout/reftests/font-features/font-variant-numeric-ref.html
layout/reftests/font-features/font-variant-numeric.html
layout/reftests/font-features/font-variant-position-ref.html
layout/reftests/font-features/font-variant-position.html
layout/reftests/font-features/reftest.list
--- a/gfx/src/nsFont.cpp
+++ b/gfx/src/nsFont.cpp
@@ -143,23 +143,16 @@ nsFont& nsFont::operator=(const nsFont& 
   variantCaps = aOther.variantCaps;
   variantEastAsian = aOther.variantEastAsian;
   variantLigatures = aOther.variantLigatures;
   variantNumeric = aOther.variantNumeric;
   variantPosition = aOther.variantPosition;
   return *this;
 }
 
-void
-nsFont::AddFontFeaturesToStyle(gfxFontStyle *aStyle) const
-{
-  // simple copy for now, font-variant implementation will expand
-  aStyle->featureSettings.AppendElements(fontFeatureSettings);
-}
-
 static bool IsGenericFontFamily(const nsString& aFamily)
 {
   uint8_t generic;
   nsFont::GetGenericID(aFamily, &generic);
   return generic != kGenericFont_NONE;
 }
 
 const PRUnichar kNullCh       = PRUnichar('\0');
@@ -213,16 +206,198 @@ bool nsFont::EnumerateFamilies(nsFontFam
       return false;
 
     ++p; // may advance past p_end
   }
 
   return true;
 }
 
+// mapping from bitflag to font feature tag/value pair
+//
+// these need to be kept in sync with the constants listed
+// in gfxFontConstants.h (e.g. NS_FONT_VARIANT_EAST_ASIAN_JIS78)
+
+// NS_FONT_VARIANT_EAST_ASIAN_xxx values
+const gfxFontFeature eastAsianDefaults[] = {
+  { TRUETYPE_TAG('j','p','7','8'), 1 },
+  { TRUETYPE_TAG('j','p','8','3'), 1 },
+  { TRUETYPE_TAG('j','p','9','0'), 1 },
+  { TRUETYPE_TAG('j','p','0','4'), 1 },
+  { TRUETYPE_TAG('s','m','p','l'), 1 },
+  { TRUETYPE_TAG('t','r','a','d'), 1 },
+  { TRUETYPE_TAG('f','w','i','d'), 1 },
+  { TRUETYPE_TAG('p','w','i','d'), 1 },
+  { TRUETYPE_TAG('r','u','b','y'), 1 }
+};
+
+PR_STATIC_ASSERT(NS_ARRAY_LENGTH(eastAsianDefaults) ==
+                 eFeatureEastAsian_numFeatures);
+
+// NS_FONT_VARIANT_LIGATURES_xxx values
+const gfxFontFeature ligDefaults[] = {
+  { TRUETYPE_TAG('l','i','g','a'), 1 },
+  { TRUETYPE_TAG('l','i','g','a'), 0 },
+  { TRUETYPE_TAG('d','l','i','g'), 1 },
+  { TRUETYPE_TAG('d','l','i','g'), 0 },
+  { TRUETYPE_TAG('h','l','i','g'), 1 },
+  { TRUETYPE_TAG('h','l','i','g'), 0 },
+  { TRUETYPE_TAG('c','a','l','t'), 1 },
+  { TRUETYPE_TAG('c','a','l','t'), 0 }
+};
+
+PR_STATIC_ASSERT(NS_ARRAY_LENGTH(ligDefaults) ==
+                 eFeatureLigatures_numFeatures);
+
+// NS_FONT_VARIANT_NUMERIC_xxx values
+const gfxFontFeature numericDefaults[] = {
+  { TRUETYPE_TAG('l','n','u','m'), 1 },
+  { TRUETYPE_TAG('o','n','u','m'), 1 },
+  { TRUETYPE_TAG('p','n','u','m'), 1 },
+  { TRUETYPE_TAG('t','n','u','m'), 1 },
+  { TRUETYPE_TAG('f','r','a','c'), 1 },
+  { TRUETYPE_TAG('a','f','r','c'), 1 },
+  { TRUETYPE_TAG('z','e','r','o'), 1 },
+  { TRUETYPE_TAG('o','r','d','n'), 1 }
+};
+
+PR_STATIC_ASSERT(NS_ARRAY_LENGTH(numericDefaults) ==
+                 eFeatureNumeric_numFeatures);
+
+static void
+AddFontFeaturesBitmask(uint32_t aValue, uint32_t aMin, uint32_t aMax,
+                      const gfxFontFeature aFeatureDefaults[],
+                      nsTArray<gfxFontFeature>& aFeaturesOut)
+
+{
+  uint32_t i, m;
+
+  for (i = 0, m = aMin; m <= aMax; i++, m <<= 1) {
+    if (m & aValue) {
+      const gfxFontFeature& feature = aFeatureDefaults[i];
+      aFeaturesOut.AppendElement(feature);
+    }
+  }
+}
+
+void nsFont::AddFontFeaturesToStyle(gfxFontStyle *aStyle) const
+{
+  // add in font-variant features
+  gfxFontFeature setting;
+
+  // -- kerning
+  setting.mTag = TRUETYPE_TAG('k','e','r','n');
+  switch (kerning) {
+    case NS_FONT_KERNING_NONE:
+      setting.mValue = 0;
+      aStyle->kerning = false;
+      aStyle->featureSettings.AppendElement(setting);
+      break;
+    case NS_FONT_KERNING_NORMAL:
+      setting.mValue = 1;
+      aStyle->kerning = true;
+      aStyle->featureSettings.AppendElement(setting);
+      break;
+    default:
+      // auto case implies use user agent default
+      break;
+  }
+
+  // -- caps
+  setting.mValue = 1;
+  switch (variantCaps) {
+    case NS_FONT_VARIANT_CAPS_ALLSMALL:
+      setting.mTag = TRUETYPE_TAG('c','2','s','c');
+      aStyle->featureSettings.AppendElement(setting);
+      // fall through to the small-caps case
+    case NS_FONT_VARIANT_CAPS_SMALLCAPS:
+      setting.mTag = TRUETYPE_TAG('s','m','c','p');
+      aStyle->featureSettings.AppendElement(setting);
+      break;
+
+    case NS_FONT_VARIANT_CAPS_ALLPETITE:
+      setting.mTag = TRUETYPE_TAG('c','2','p','c');
+      aStyle->featureSettings.AppendElement(setting);
+      // fall through to the petite-caps case
+    case NS_FONT_VARIANT_CAPS_PETITECAPS:
+      setting.mTag = TRUETYPE_TAG('p','c','a','p');
+      aStyle->featureSettings.AppendElement(setting);
+      break;
+
+    case NS_FONT_VARIANT_CAPS_TITLING:
+      setting.mTag = TRUETYPE_TAG('t','i','t','l');
+      aStyle->featureSettings.AppendElement(setting);
+      break;
+
+    case NS_FONT_VARIANT_CAPS_UNICASE:
+      setting.mTag = TRUETYPE_TAG('u','n','i','c');
+      aStyle->featureSettings.AppendElement(setting);
+      break;
+
+    default:
+      break;
+  }
+
+  // -- east-asian
+  if (variantEastAsian) {
+    AddFontFeaturesBitmask(variantEastAsian,
+                           NS_FONT_VARIANT_EAST_ASIAN_JIS78,
+                           NS_FONT_VARIANT_EAST_ASIAN_RUBY,
+                           eastAsianDefaults, aStyle->featureSettings);
+  }
+
+  // -- ligatures
+  if (variantLigatures) {
+    AddFontFeaturesBitmask(variantLigatures,
+                           NS_FONT_VARIANT_LIGATURES_COMMON,
+                           NS_FONT_VARIANT_LIGATURES_NO_CONTEXTUAL,
+                           ligDefaults, aStyle->featureSettings);
+
+    // special case common ligs, which also enable/disable clig
+    if (variantLigatures & NS_FONT_VARIANT_LIGATURES_COMMON) {
+      setting.mTag = TRUETYPE_TAG('c','l','i','g');
+      setting.mValue = 1;
+      aStyle->featureSettings.AppendElement(setting);
+    } else if (variantLigatures & NS_FONT_VARIANT_LIGATURES_NO_COMMON) {
+      setting.mTag = TRUETYPE_TAG('c','l','i','g');
+      setting.mValue = 0;
+      aStyle->featureSettings.AppendElement(setting);
+    }
+  }
+
+  // -- numeric
+  if (variantNumeric) {
+    AddFontFeaturesBitmask(variantNumeric,
+                           NS_FONT_VARIANT_NUMERIC_LINING,
+                           NS_FONT_VARIANT_NUMERIC_ORDINAL,
+                           numericDefaults, aStyle->featureSettings);
+  }
+
+  // -- position
+  setting.mTag = 0;
+  setting.mValue = 1;
+  switch (variantPosition) {
+    case NS_FONT_VARIANT_POSITION_SUPER:
+      setting.mTag = TRUETYPE_TAG('s','u','p','s');
+      aStyle->featureSettings.AppendElement(setting);
+      break;
+
+    case NS_FONT_VARIANT_POSITION_SUB:
+      setting.mTag = TRUETYPE_TAG('s','u','b','s');
+      aStyle->featureSettings.AppendElement(setting);
+      break;
+
+    default:
+      break;
+  }
+
+  // add in features from font-feature-settings
+  aStyle->featureSettings.AppendElements(fontFeatureSettings);
+}
+
 static bool FontEnumCallback(const nsString& aFamily, bool aGeneric, void *aData)
 {
   *((nsString*)aData) = aFamily;
   return false;
 }
 
 void nsFont::GetFirstFamily(nsString& aFamily) const
 {
--- a/gfx/tests/gfxFontSelectionTests.h
+++ b/gfx/tests/gfxFontSelectionTests.h
@@ -75,27 +75,25 @@ SetupTests()
     /* some common styles */
     gfxFontStyle style_western_normal_16 (FONT_STYLE_NORMAL,
                                           NS_FONT_STRETCH_NORMAL,
                                           400,
                                           16.0,
                                           NS_NewPermanentAtom(NS_LITERAL_STRING("en")),
                                           0.0,
                                           false, false, false,
-                                          NS_LITERAL_STRING(""),
                                           NS_LITERAL_STRING(""));
 
     gfxFontStyle style_western_bold_16 (FONT_STYLE_NORMAL,
                                         NS_FONT_STRETCH_NORMAL,
                                         700,
                                         16.0,
                                         NS_NewPermanentAtom(NS_LITERAL_STRING("en")),
                                         0.0,
                                         false, false, false,
-                                        NS_LITERAL_STRING(""),
                                         NS_LITERAL_STRING(""));
 
     /* Test 0 */
     t = AddTest ("sans-serif",
                  style_western_normal_16,
                  S_ASCII,
                  "ABCD");
 
--- a/gfx/tests/gfxTextRunPerfTest.cpp
+++ b/gfx/tests/gfxTextRunPerfTest.cpp
@@ -61,17 +61,16 @@ RunTest (TestEntry *test, gfxContext *ct
     if (!lastFamilies || strcmp(lastFamilies, test->mFamilies)) {
         gfxFontStyle style_western_normal_16 (FONT_STYLE_NORMAL,
                                               NS_FONT_STRETCH_NORMAL,
                                               400,
                                               16.0,
                                               NS_NewPermanentAtom(NS_LITERAL_STRING("en")),
                                               0.0,
                                               false, false, false,
-                                              NS_LITERAL_STRING(""),
                                               NS_LITERAL_STRING(""));
 
         fontGroup = gfxPlatform::GetPlatform()->CreateFontGroup(NS_ConvertUTF8toUTF16(test->mFamilies), &style_western_normal_16, nullptr);
     }
 
     nsAutoPtr<gfxTextRun> textRun;
     uint32_t i;
     bool isASCII = true;
--- a/gfx/tests/gfxWordCacheTest.cpp
+++ b/gfx/tests/gfxWordCacheTest.cpp
@@ -119,17 +119,16 @@ main (int argc, char **argv) {
    {
        gfxFontStyle style (FONT_STYLE_NORMAL,
                            NS_FONT_STRETCH_NORMAL,
                            139,
                            10.0,
                            NS_NewPermanentAtom(NS_LITERAL_STRING("en")),
                            0.0,
                            false, false, false,
-                           NS_LITERAL_STRING(""),
                            NS_LITERAL_STRING(""));
 
        nsRefPtr<gfxFontGroup> fontGroup =
            gfxPlatform::GetPlatform()->CreateFontGroup(NS_LITERAL_STRING("Geneva, MS Sans Serif, Helvetica,serif"), &style, nullptr);
 
        gfxTextRunFactory::Parameters params = {
            ctx, nullptr, nullptr, nullptr, 0, 60
        };
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -1369,23 +1369,26 @@ gfxFontCache::SizeOfIncludingThis(nsMall
                                   FontCacheSizes*   aSizes) const
 {
     aSizes->mFontInstances += aMallocSizeOf(this);
     SizeOfExcludingThis(aMallocSizeOf, aSizes);
 }
 
 /* static */ bool
 gfxFontShaper::MergeFontFeatures(
-    const nsTArray<gfxFontFeature>& aStyleRuleFeatures,
+    const gfxFontStyle *aStyle,
     const nsTArray<gfxFontFeature>& aFontFeatures,
     bool aDisableLigatures,
     nsDataHashtable<nsUint32HashKey,uint32_t>& aMergedFeatures)
 {
+    const nsTArray<gfxFontFeature>& styleRuleFeatures =
+        aStyle->featureSettings;
+
     // bail immediately if nothing to do
-    if (aStyleRuleFeatures.IsEmpty() &&
+    if (styleRuleFeatures.IsEmpty() &&
         aFontFeatures.IsEmpty() &&
         !aDisableLigatures) {
         return false;
     }
 
     aMergedFeatures.Init();
 
     // Ligature features are enabled by default in the generic shaper,
@@ -1400,19 +1403,19 @@ gfxFontShaper::MergeFontFeatures(
 
     count = aFontFeatures.Length();
     for (i = 0; i < count; i++) {
         const gfxFontFeature& feature = aFontFeatures.ElementAt(i);
         aMergedFeatures.Put(feature.mTag, feature.mValue);
     }
 
     // add feature values from style rules
-    count = aStyleRuleFeatures.Length();
+    count = styleRuleFeatures.Length();
     for (i = 0; i < count; i++) {
-        const gfxFontFeature& feature = aStyleRuleFeatures.ElementAt(i);
+        const gfxFontFeature& feature = styleRuleFeatures.ElementAt(i);
         aMergedFeatures.Put(feature.mTag, feature.mValue);
     }
 
     return aMergedFeatures.Count() != 0;
 }
 
 void
 gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther, bool aOtherIsOnLeft)
@@ -4751,31 +4754,31 @@ gfxFontStyle::ParseFontLanguageOverride(
 }
 
 gfxFontStyle::gfxFontStyle() :
     language(nsGkAtoms::x_western),
     size(DEFAULT_PIXEL_FONT_SIZE), sizeAdjust(0.0f),
     languageOverride(NO_FONT_LANGUAGE_OVERRIDE),
     weight(NS_FONT_WEIGHT_NORMAL), stretch(NS_FONT_STRETCH_NORMAL),
     systemFont(true), printerFont(false), 
-    style(NS_FONT_STYLE_NORMAL)
+    kerning(true), style(NS_FONT_STYLE_NORMAL)
 {
 }
 
 gfxFontStyle::gfxFontStyle(uint8_t aStyle, uint16_t aWeight, int16_t aStretch,
                            gfxFloat aSize, nsIAtom *aLanguage,
                            float aSizeAdjust, bool aSystemFont,
                            bool aPrinterFont,
                            const nsString& aLanguageOverride):
     language(aLanguage),
     size(aSize), sizeAdjust(aSizeAdjust),
     languageOverride(ParseFontLanguageOverride(aLanguageOverride)),
     weight(aWeight), stretch(aStretch),
     systemFont(aSystemFont), printerFont(aPrinterFont),
-    style(aStyle)
+    kerning(true), style(aStyle)
 {
     MOZ_ASSERT(!mozilla::IsNaN(size));
     MOZ_ASSERT(!mozilla::IsNaN(sizeAdjust));
 
     if (weight > 900)
         weight = 900;
     if (weight < 100)
         weight = 100;
@@ -4795,17 +4798,17 @@ gfxFontStyle::gfxFontStyle(uint8_t aStyl
 }
 
 gfxFontStyle::gfxFontStyle(const gfxFontStyle& aStyle) :
     language(aStyle.language),
     size(aStyle.size), sizeAdjust(aStyle.sizeAdjust),
     languageOverride(aStyle.languageOverride),
     weight(aStyle.weight), stretch(aStyle.stretch),
     systemFont(aStyle.systemFont), printerFont(aStyle.printerFont),
-    style(aStyle.style)
+    kerning(aStyle.kerning), style(aStyle.style)
 {
     featureSettings.AppendElements(aStyle.featureSettings);
 }
 
 int8_t
 gfxFontStyle::ComputeWeight() const
 {
     int8_t baseWeight = (weight + 50) / 100;
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -68,16 +68,20 @@ struct THEBES_API gfxFontStyle {
                  const nsString& aLanguageOverride);
     gfxFontStyle(const gfxFontStyle& aStyle);
 
     // the language (may be an internal langGroup code rather than an actual
     // language code) specified in the document or element's lang property,
     // or inferred from the charset
     nsRefPtr<nsIAtom> language;
 
+    // Features are composed of (1) features from style rules (2) features
+    // from feature setttings rules and (3) family-specific features.  (1) and
+    // (3) are guaranteed to be mutually exclusive
+
     // custom opentype feature settings
     nsTArray<gfxFontFeature> featureSettings;
 
     // The logical size of the font, in pixels
     gfxFloat size;
 
     // The aspect-value (ie., the ratio actualsize:actualxheight) that any
     // actual physical font created from this font structure must have when
@@ -107,16 +111,19 @@ struct THEBES_API gfxFontStyle {
     // Say that this font is a system font and therefore does not
     // require certain fixup that we do for fonts from untrusted
     // sources.
     bool systemFont : 1;
 
     // Say that this font is used for print or print preview.
     bool printerFont : 1;
 
+    // whether kerning is enabled or not (true by default, can be disabled)
+    bool kerning : 1;
+
     // The style of font (normal, italic, oblique)
     uint8_t style : 2;
 
     // Return the final adjusted font size for the given aspect ratio.
     // Not meant to be called when sizeAdjust = 0.
     gfxFloat GetAdjustedSize(gfxFloat aspect) const {
         NS_ASSERTION(sizeAdjust != 0.0, "Not meant to be called when sizeAdjust = 0");
         gfxFloat adjustedSize = std::max(NS_round(size*(sizeAdjust/aspect)), 1.0);
@@ -138,16 +145,17 @@ struct THEBES_API gfxFontStyle {
             (style == other.style) &&
             (systemFont == other.systemFont) &&
             (printerFont == other.printerFont) &&
             (weight == other.weight) &&
             (stretch == other.stretch) &&
             (language == other.language) &&
             (*reinterpret_cast<const uint32_t*>(&sizeAdjust) ==
              *reinterpret_cast<const uint32_t*>(&other.sizeAdjust)) &&
+            (kerning == other.kerning) &&
             (featureSettings == other.featureSettings) &&
             (languageOverride == other.languageOverride);
     }
 
     static void ParseFontFeatureSettings(const nsString& aFeatureString,
                                          nsTArray<gfxFontFeature>& aFeatures);
 
     static uint32_t ParseFontLanguageOverride(const nsString& aLangTag);
@@ -1142,17 +1150,17 @@ public:
                            uint32_t         aLength,
                            int32_t          aScript,
                            gfxShapedText   *aShapedText) = 0;
 
     gfxFont *GetFont() const { return mFont; }
 
     // returns true if features exist in output, false otherwise
     static bool
-    MergeFontFeatures(const nsTArray<gfxFontFeature>& aStyleRuleFeatures,
+    MergeFontFeatures(const gfxFontStyle *aStyle,
                       const nsTArray<gfxFontFeature>& aFontFeatures,
                       bool aDisableLigatures,
                       nsDataHashtable<nsUint32HashKey,uint32_t>& aMergedFeatures);
 
 protected:
     // the font this shaper is working with
     gfxFont * mFont;
 };
--- a/gfx/thebes/gfxGraphiteShaper.cpp
+++ b/gfx/thebes/gfxGraphiteShaper.cpp
@@ -187,18 +187,21 @@ gfxGraphiteShaper::ShapeText(gfxContext 
         nsAutoCString langString;
         style->language->ToUTF8String(langString);
         grLang = GetGraphiteTagForLang(langString);
     }
     gr_feature_val *grFeatures = gr_face_featureval_for_lang(mGrFace, grLang);
 
     nsDataHashtable<nsUint32HashKey,uint32_t> mergedFeatures;
 
-    if (MergeFontFeatures(style->featureSettings, entry->mFeatureSettings,
-                          aShapedText->DisableLigatures(), mergedFeatures)) {
+    if (MergeFontFeatures(style,
+                          mFont->GetFontEntry()->mFeatureSettings,
+                          aShapedText->DisableLigatures(),
+                          mergedFeatures))
+    {
         // enumerate result and insert into Graphite feature list
         GrFontFeatures f = {mGrFace, grFeatures};
         mergedFeatures.Enumerate(AddFeature, &f);
     }
 
     size_t numChars = gr_count_unicode_characters(gr_utf16,
                                                   aText, aText + aLength,
                                                   nullptr);
--- a/gfx/thebes/gfxHarfBuzzShaper.cpp
+++ b/gfx/thebes/gfxHarfBuzzShaper.cpp
@@ -94,22 +94,24 @@ HBGetTable(hb_face_t *face, hb_tag_t aTa
 }
 
 /*
  * HarfBuzz font callback functions; font_data is a ptr to a
  * FontCallbackData struct
  */
 
 struct FontCallbackData {
-    FontCallbackData(gfxHarfBuzzShaper *aShaper, gfxContext *aContext)
-        : mShaper(aShaper), mContext(aContext)
+    FontCallbackData(gfxHarfBuzzShaper *aShaper, gfxContext *aContext,
+                     bool aKerning)
+        : mShaper(aShaper), mContext(aContext), mKerning(aKerning)
     { }
 
     gfxHarfBuzzShaper *mShaper;
     gfxContext        *mContext;
+    bool               mKerning;
 };
 
 #define UNICODE_BMP_LIMIT 0x10000
 
 hb_codepoint_t
 gfxHarfBuzzShaper::GetGlyph(hb_codepoint_t unicode,
                             hb_codepoint_t variation_selector) const
 {
@@ -639,17 +641,23 @@ gfxHarfBuzzShaper::GetHKerning(uint16_t 
 
 static hb_position_t
 HBGetHKerning(hb_font_t *font, void *font_data,
               hb_codepoint_t first_glyph, hb_codepoint_t second_glyph,
               void *user_data)
 {
     const FontCallbackData *fcd =
         static_cast<const FontCallbackData*>(font_data);
-    return fcd->mShaper->GetHKerning(first_glyph, second_glyph);
+
+    // return 0 if kerning is explicitly disabled
+    if (fcd->mKerning) {
+        return fcd->mShaper->GetHKerning(first_glyph, second_glyph);
+    } else {
+        return 0.0;
+    }
 }
 
 /*
  * HarfBuzz unicode property callbacks
  */
 
 static hb_codepoint_t
 HBGetMirroring(hb_unicode_funcs_t *ufuncs, hb_codepoint_t aCh,
@@ -951,33 +959,35 @@ gfxHarfBuzzShaper::ShapeText(gfxContext 
     }
 
     if ((!mUseFontGetGlyph && mCmapFormat <= 0) ||
         (!mUseFontGlyphWidths && !mHmtxTable)) {
         // unable to shape with this font
         return false;
     }
 
-    FontCallbackData fcd(this, aContext);
+    const gfxFontStyle *style = mFont->GetStyle();
+    FontCallbackData fcd(this, aContext, style->kerning);
     hb_font_t *font = hb_font_create(mHBFace);
     hb_font_set_funcs(font, sHBFontFuncs, &fcd, nullptr);
     hb_font_set_ppem(font, mFont->GetAdjustedSize(), mFont->GetAdjustedSize());
     uint32_t scale = FloatToFixed(mFont->GetAdjustedSize()); // 16.16 fixed-point
     hb_font_set_scale(font, scale, scale);
 
     nsAutoTArray<hb_feature_t,20> features;
 
     gfxFontEntry *entry = mFont->GetFontEntry();
-    const gfxFontStyle *style = mFont->GetStyle();
 
     nsDataHashtable<nsUint32HashKey,uint32_t> mergedFeatures;
 
-    if (MergeFontFeatures(style->featureSettings,
-                      mFont->GetFontEntry()->mFeatureSettings,
-                      aShapedText->DisableLigatures(), mergedFeatures)) {
+    if (MergeFontFeatures(style,
+                          mFont->GetFontEntry()->mFeatureSettings,
+                          aShapedText->DisableLigatures(),
+                          mergedFeatures))
+    {
         // enumerate result and insert into hb_feature array
         mergedFeatures.Enumerate(AddFeature, &features);
     }
 
     bool isRightToLeft = aShapedText->IsRightToLeft();
     hb_buffer_t *buffer = hb_buffer_create();
     hb_buffer_set_unicode_funcs(buffer, sHBUnicodeFuncs);
     hb_buffer_set_direction(buffer, isRightToLeft ? HB_DIRECTION_RTL :
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-features-order-3.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+@font-face {
+  font-family: libertine;
+  src: url(../fonts/LinLibertine_Re-4.7.5.woff) format("woff");
+  -moz-font-feature-settings: "liga" on, "hlig" on;
+}
+body {
+  font-family: libertine, sans-serif;
+  font-size: 400%;
+  line-height: 2em;
+  /* font feature settings property should override font setting */
+  -moz-font-feature-settings: "liga" off, "hlig" off;
+}
+</style>
+</head>
+<body lang="en">
+fastest firefox
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-features-order-4.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+@font-face {
+  font-family: libertine;
+  src: url(../fonts/LinLibertine_Re-4.7.5.woff) format("woff");
+  font-feature-settings: "liga" on, "hlig" on;
+}
+body {
+  font-family: libertine, sans-serif;
+  font-size: 400%;
+  line-height: 2em;
+  /* font variant property should override font setting */
+  font-variant-ligatures: no-common-ligatures no-historical-ligatures;
+}
+</style>
+</head>
+<body lang="en">
+fastest firefox
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-features-order-5.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+@font-face {
+  font-family: libertine;
+  src: url(../fonts/LinLibertine_Re-4.7.5.woff) format("woff");
+  font-feature-settings: "liga" on, "hlig" on;
+}
+body {
+  font-family: libertine, sans-serif;
+  font-size: 400%;
+  line-height: 2em;
+  /* font variant property should override font setting but font feature
+     settings property should override that */
+  font-variant-ligatures: no-common-ligatures no-historical-ligatures;
+  font-feature-settings: "liga" on, "hlig" on;
+}
+</style>
+</head>
+<body lang="en">
+fastest firefox
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-kerning-1.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style type="text/css">
+@font-face {
+  font-family: libertine;
+  src: url(../fonts/LinLibertine_Re-4.7.5.woff) format("woff");
+}
+body {
+  margin: 10px;
+  font-family: libertine, sans-serif;
+  font-size: 600%;
+  line-height: 1.2em;
+  /* font-feature-settings should take precedence over font-kerning,
+     so kerning should be DISabled here */
+  font-feature-settings: "kern" off;
+  font-kerning: normal;
+}
+</style>
+</head>
+<body lang="en">
+<div>Ta To</div>
+<div>AVA</div>
+<div>AWAY</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-kerning-2.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style type="text/css">
+@font-face {
+  font-family: libertine;
+  src: url(../fonts/LinLibertine_Re-4.7.5.woff) format("woff");
+}
+body {
+  margin: 10px;
+  font-family: libertine, sans-serif;
+  font-size: 600%;
+  line-height: 1.2em;
+  /* font-feature-settings should take precedence over font-kerning,
+     so kerning should be ENabled here. */
+  font-feature-settings: "kern" on;
+  font-kerning: none;
+}
+</style>
+</head>
+<body lang="en">
+<div>Ta To</div>
+<div>AVA</div>
+<div>AWAY</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-kerning-3.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style type="text/css">
+@font-face {
+  font-family: libertine;
+  src: url(../fonts/LinLibertine_Re-4.7.5.woff) format("woff");
+  font-feature-settings: "kern" off;
+}
+body {
+  margin: 10px;
+  font-family: libertine, sans-serif;
+  font-size: 600%;
+  line-height: 1.2em;
+}
+</style>
+</head>
+<body lang="en">
+<div>Ta To</div>
+<div>AVA</div>
+<div>AWAY</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-kerning-auto.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style type="text/css">
+@font-face {
+  font-family: libertine;
+  src: url(../fonts/LinLibertine_Re-4.7.5.woff) format("woff");
+}
+body {
+  margin: 10px;
+  font-family: libertine, sans-serif;
+  font-size: 600%;
+  line-height: 1.2em;
+  font-kerning: auto;
+}
+</style>
+</head>
+<body lang="en">
+<div>Ta To</div>
+<div>AVA</div>
+<div>AWAY</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-kerning-kern.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style type="text/css">
+@font-face {
+  font-family: libertine;
+  src: url(../fonts/LinLibertine_Re-4.7.5.woff) format("woff");
+}
+body {
+  margin: 10px;
+  font-family: libertine, sans-serif;
+  font-size: 600%;
+  line-height: 1.2em;
+  font-feature-settings: "kern" on;
+}
+</style>
+</head>
+<body lang="en">
+<div>Ta To</div>
+<div>AVA</div>
+<div>AWAY</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-kerning-nokern.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style type="text/css">
+@font-face {
+  font-family: libertine;
+  src: url(../fonts/LinLibertine_Re-4.7.5.woff) format("woff");
+}
+body {
+  margin: 10px;
+  font-family: libertine, sans-serif;
+  font-size: 600%;
+  line-height: 1.2em;
+  font-feature-settings: "kern" off;
+}
+</style>
+</head>
+<body lang="en">
+<div>Ta To</div>
+<div>AVA</div>
+<div>AWAY</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-kerning-none.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style type="text/css">
+@font-face {
+  font-family: libertine;
+  src: url(../fonts/LinLibertine_Re-4.7.5.woff) format("woff");
+}
+body {
+  margin: 10px;
+  font-family: libertine, sans-serif;
+  font-size: 600%;
+  line-height: 1.2em;
+  font-kerning: none;
+}
+</style>
+</head>
+<body lang="en">
+<div>Ta To</div>
+<div>AVA</div>
+<div>AWAY</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-kerning-normal.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style type="text/css">
+@font-face {
+  font-family: libertine;
+  src: url(../fonts/LinLibertine_Re-4.7.5.woff) format("woff");
+}
+body {
+  margin: 10px;
+  font-family: libertine, sans-serif;
+  font-size: 600%;
+  line-height: 1.2em;
+  font-kerning: normal;
+}
+</style>
+</head>
+<body lang="en">
+<div>Ta To</div>
+<div>AVA</div>
+<div>AWAY</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-kerning-table-none.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style type="text/css">
+@font-face {
+  font-family: gentium;
+  /* font that has an old-style 'kern' table rather than GPOS 'kern' feature */
+  src: url(../fonts/sil/GenR102.ttf);
+}
+body {
+  margin: 10px;
+  font-family: gentium;
+  font-size: 600%;
+  line-height: 1.2em;
+  font-kerning: none;
+}
+</style>
+</head>
+<body lang="en">
+<div>Ta To</div>
+<div>AVA</div>
+<div>AWAY</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-kerning-table-normal.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style type="text/css">
+@font-face {
+  font-family: gentium;
+  src: url(../fonts/sil/GenR102.ttf);
+}
+body {
+  margin: 10px;
+  font-family: gentium;
+  font-size: 600%;
+  line-height: 1.2em;
+  font-kerning: normal;
+}
+</style>
+</head>
+<body lang="en">
+<div>Ta To</div>
+<div>AVA</div>
+<div>AWAY</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-variant-caps-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+<title>font-variant-caps test</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<script type="text/javascript" src="../fonts/gsubtest/gsubtest-features.js"></script>
+<script type="text/javascript" src="font-variant-features.js"></script>
+<link rel="stylesheet" href="font-variant-features.css" type="text/css"/>
+</head>
+<body>
+<div id="content"></div>
+<script type="text/javascript">
+  document.getElementById("content").appendChild(createFeatureTestTable(gPropertyData, "font-variant-caps", true, false));
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-variant-caps.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+<title>font-variant-caps test</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<script type="text/javascript" src="../fonts/gsubtest/gsubtest-features.js"></script>
+<script type="text/javascript" src="font-variant-features.js"></script>
+<link rel="stylesheet" href="font-variant-features.css" type="text/css"/>
+</head>
+<body>
+<div id="content"></div>
+<script type="text/javascript">
+  document.getElementById("content").appendChild(createFeatureTestTable(gPropertyData, "font-variant-caps", false, false));
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-variant-debug.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+<title>font-variant test</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<script type="text/javascript" src="../fonts/gsubtest/gsubtest-features.js"></script>
+<script type="text/javascript" src="font-variant-features.js"></script>
+<link rel="stylesheet" href="font-variant-features.css" type="text/css"/>
+</head>
+<body>
+<div id="content"></div>
+<script type="text/javascript">
+  document.getElementById("content").appendChild(createFeatureTestTable(gPropertyData, "all", false, true));
+</script>
+</body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-variant-east-asian-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+<title>font-variant-east-asian test</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<script type="text/javascript" src="../fonts/gsubtest/gsubtest-features.js"></script>
+<script type="text/javascript" src="font-variant-features.js"></script>
+<link rel="stylesheet" href="font-variant-features.css" type="text/css"/>
+</head>
+<body>
+<div id="content"></div>
+<script type="text/javascript">
+  document.getElementById("content").appendChild(createFeatureTestTable(gPropertyData, "font-variant-east-asian", true, false));
+</script>
+</body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-variant-east-asian.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+<title>font-variant-east-asian test</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<script type="text/javascript" src="../fonts/gsubtest/gsubtest-features.js"></script>
+<script type="text/javascript" src="font-variant-features.js"></script>
+<link rel="stylesheet" href="font-variant-features.css" type="text/css"/>
+</head>
+<body>
+<div id="content"></div>
+<script type="text/javascript">
+  document.getElementById("content").appendChild(createFeatureTestTable(gPropertyData, "font-variant-east-asian", false, false));
+</script>
+</body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-variant-features.css
@@ -0,0 +1,21 @@
+body { margin: 10px; }
+
+@font-face {
+  font-family: gsub-test;
+  src: url(../fonts/gsubtest/gsubtest-lookup3.otf);
+}
+
+td.prop {
+  font-family: Menlo, monospace;
+  font-weight: normal;
+  text-align: left;
+  font-size: 80%;
+}
+
+td.features {
+  font-family: gsub-test;
+}
+
+.invalid {
+  color: red;
+}
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-variant-features.js
@@ -0,0 +1,220 @@
+
+// data associated with gsubtest test font for testing font features
+
+// prefix
+gPrefix = "";
+
+// equivalent properties
+// setting prop: value should match the specific feature settings listed
+//
+// each of these tests evaluate whether a given feature is enabled as required
+// and also whether features that shouldn't be enabled are or not.
+var gPropertyData = [
+  // font-variant-caps
+  // valid values
+  { prop: "font-variant-caps", value: "normal", features: {"smcp": 0} },
+  { prop: "font-variant-caps", value: "small-caps", features: {"smcp": 1, "c2sc": 0} },
+  { prop: "font-variant-caps", value: "all-small-caps", features: {"smcp": 1, "c2sc": 1, "pcap": 0} },
+  { prop: "font-variant-caps", value: "petite-caps", features: {"pcap": 1, "smcp": 0} },
+  { prop: "font-variant-caps", value: "all-petite-caps", features: {"c2pc": 1, "pcap": 1, "smcp": 0} },
+  { prop: "font-variant-caps", value: "titling-caps", features: {"titl": 1, "smcp": 0} },
+  { prop: "font-variant-caps", value: "unicase", features: {"unic": 1, "titl": 0} },
+
+  // invalid values
+  { prop: "font-variant-caps", value: "normal small-caps", features: {"smcp": 0}, invalid: true },
+  { prop: "font-variant-caps", value: "small-caps potato", features: {"smcp": 0}, invalid: true },
+  { prop: "font-variant-caps", value: "small-caps petite-caps", features: {"smcp": 0, "pcap": 0}, invalid: true },
+  { prop: "font-variant-caps", value: "small-caps all-small-caps", features: {"smcp": 0, "c2sc": 0}, invalid: true },
+  { prop: "font-variant-caps", value: "small-cap", features: {"smcp": 0}, invalid: true },
+
+  // font-variant-east-asian
+  // valid values
+  { prop: "font-variant-east-asian", value: "jis78", features: {"jp78": 1, "jp04": 0} },
+  { prop: "font-variant-east-asian", value: "jis83", features: {"jp83": 1, "jp04": 0} },
+  { prop: "font-variant-east-asian", value: "jis90", features: {"jp90": 1, "jp04": 0} },
+  { prop: "font-variant-east-asian", value: "jis04", features: {"jp04": 1, "jp78": 0} },
+  { prop: "font-variant-east-asian", value: "simplified", features: {"smpl": 1, "jp04": 0} },
+  { prop: "font-variant-east-asian", value: "traditional", features: {"trad": 1, "jp04": 0} },
+  { prop: "font-variant-east-asian", value: "full-width", features: {"fwid": 1, "jp04": 0} },
+  { prop: "font-variant-east-asian", value: "proportional-width", features: {"pwid": 1, "jp04": 0} },
+  { prop: "font-variant-east-asian", value: "ruby", features: {"ruby": 1, "jp04": 0} },
+  { prop: "font-variant-east-asian", value: "jis78 full-width", features: {"jp78": 1, "fwid": 1, "jp83": 0} },
+  { prop: "font-variant-east-asian", value: "jis78 full-width ruby", features: {"jp78": 1, "fwid": 1, "jp83": 0, "ruby": 1} },
+  { prop: "font-variant-east-asian", value: "simplified proportional-width", features: {"smpl": 1, "pwid": 1, "jp83": 0} },
+  { prop: "font-variant-east-asian", value: "ruby simplified", features: {"ruby": 1, "smpl": 1, "trad": 0} },
+
+  // invalid values
+  { prop: "font-variant-east-asian", value: "ruby normal", features: {"ruby": 0}, invalid: true },
+  { prop: "font-variant-east-asian", value: "jis90 jis04", features: {"jp90": 0, "jp04": 0}, invalid: true },
+  { prop: "font-variant-east-asian", value: "simplified traditional", features: {"smpl": 0, "trad": 0}, invalid: true },
+  { prop: "font-variant-east-asian", value: "full-width proportional-width", features: {"fwid": 0, "pwid": 0}, invalid: true },
+  { prop: "font-variant-east-asian", value: "ruby simplified ruby", features: {"ruby": 0, "smpl": 0, "jp04": 0}, invalid: true },
+  { prop: "font-variant-east-asian", value: "jis78 ruby simplified", features: {"ruby": 0, "smpl": 0, "jp78": 0}, invalid: true },
+
+  // font-variant-ligatures
+  // valid values
+  { prop: "font-variant-ligatures", value: "normal", features: {"liga": 1, "dlig": 0} },
+  { prop: "font-variant-ligatures", value: "common-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0, "calt": 1} },
+  { prop: "font-variant-ligatures", value: "no-common-ligatures", features: {"liga": 0, "clig": 0, "dlig": 0, "hlig": 0, "calt": 1} },
+  { prop: "font-variant-ligatures", value: "discretionary-ligatures", features: {"liga": 1, "clig": 1, "dlig": 1, "hlig": 0, "calt": 1} },
+  { prop: "font-variant-ligatures", value: "no-discretionary-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0, "calt": 1} },
+  { prop: "font-variant-ligatures", value: "historical-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 1, "calt": 1} },
+  { prop: "font-variant-ligatures", value: "no-historical-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0, "calt": 1} },
+  { prop: "font-variant-ligatures", value: "contextual", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0, "calt": 1} },
+  { prop: "font-variant-ligatures", value: "no-contextual", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0, "calt": 0} },
+  { prop: "font-variant-ligatures", value: "common-ligatures no-discretionary-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0, "calt": 1} },
+  { prop: "font-variant-ligatures", value: "historical-ligatures no-common-ligatures", features: {"clig": 0, "liga": 0, "dlig": 0, "hlig": 1, "calt": 1} },
+  { prop: "font-variant-ligatures", value: "no-historical-ligatures discretionary-ligatures", features: {"liga": 1, "clig": 1, "dlig": 1, "hlig": 0, "calt": 1} },
+  { prop: "font-variant-ligatures", value: "common-ligatures no-discretionary-ligatures historical-ligatures no-contextual", features: {"clig": 1, "dlig": 0, "hlig": 1, "liga": 1, "calt": 0} },
+
+  // invalid values
+  { prop: "font-variant-ligatures", value: "common-ligatures normal", features: {"liga": 1, "clig": 1, "dlig": 0}, invalid: true },
+  { prop: "font-variant-ligatures", value: "common-ligatures no-common-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0}, invalid: true },
+  { prop: "font-variant-ligatures", value: "common-ligatures common-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0}, invalid: true },
+  { prop: "font-variant-ligatures", value: "no-historical-ligatures historical-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0}, invalid: true },
+  { prop: "font-variant-ligatures", value: "no-contextual contextual", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0}, invalid: true },
+  { prop: "font-variant-ligatures", value: "no-discretionary-ligatures discretionary-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0}, invalid: true },
+  { prop: "font-variant-ligatures", value: "common-ligatures no-discretionary-ligatures no-common-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0}, invalid: true },
+
+  // font-variant-numeric
+  // valid values
+  { prop: "font-variant-numeric", value: "normal", features: {"lnum": 0, "tnum": 0, "pnum": 0, "onum": 0} },
+  { prop: "font-variant-numeric", value: "lining-nums", features: {"lnum": 1, "onum": 0, "pnum": 0} },
+  { prop: "font-variant-numeric", value: "oldstyle-nums", features: {"lnum": 0, "onum": 1, "pnum": 0} },
+  { prop: "font-variant-numeric", value: "proportional-nums", features: {"lnum": 0, "onum": 0, "pnum": 1, "tnum": 0} },
+  { prop: "font-variant-numeric", value: "proportional-nums oldstyle-nums", features: {"lnum": 0, "onum": 1, "pnum": 1, "tnum": 0} },
+  { prop: "font-variant-numeric", value: "tabular-nums", features: {"tnum": 1, "onum": 0, "pnum": 0} },
+  { prop: "font-variant-numeric", value: "diagonal-fractions", features: {"frac": 1, "afrc": 0, "pnum": 0} },
+  { prop: "font-variant-numeric", value: "stacked-fractions", features: {"frac": 0, "afrc": 1, "pnum": 0} },
+  { prop: "font-variant-numeric", value: "slashed-zero", features: {"zero": 1, "pnum": 0} },
+  { prop: "font-variant-numeric", value: "ordinal", features: {"ordn": 1, "pnum": 0} },
+  { prop: "font-variant-numeric", value: "lining-nums diagonal-fractions", features: {"frac": 1, "afrc": 0, "lnum": 1} },
+  { prop: "font-variant-numeric", value: "tabular-nums stacked-fractions", features: {"frac": 0, "afrc": 1, "tnum": 1} },
+  { prop: "font-variant-numeric", value: "tabular-nums slashed-zero stacked-fractions", features: {"frac": 0, "afrc": 1, "tnum": 1, "zero": 1} },
+  { prop: "font-variant-numeric", value: "proportional-nums slashed-zero diagonal-fractions oldstyle-nums ordinal", features: {"frac": 1, "afrc": 0, "tnum": 0, "pnum": 1, "onum": 1, "ordn": 1, "zero": 1} },
+
+  // invalid values
+  { prop: "font-variant-numeric", value: "lining-nums normal", features: {"lnum": 0, "onum": 0}, invalid: true },
+  { prop: "font-variant-numeric", value: "lining-nums oldstyle-nums", features: {"lnum": 0, "onum": 0}, invalid: true },
+  { prop: "font-variant-numeric", value: "lining-nums normal slashed-zero ordinal", features: {"lnum": 0, "onum": 0, "zero": 0}, invalid: true },
+  { prop: "font-variant-numeric", value: "proportional-nums tabular-nums", features: {"pnum": 0, "tnum": 0}, invalid: true },
+  { prop: "font-variant-numeric", value: "diagonal-fractions stacked-fractions", features: {"frac": 0, "afrc": 0}, invalid: true },
+  { prop: "font-variant-numeric", value: "slashed-zero diagonal-fractions slashed-zero", features: {"frac": 0, "afrc": 0, "zero": 0}, invalid: true },
+  { prop: "font-variant-numeric", value: "lining-nums slashed-zero diagonal-fractions oldstyle-nums", features: {"frac": 0, "afrc": 0, "zero": 0, "onum": 0}, invalid: true },
+
+  // font-variant-position
+  // valid values
+  { prop: "font-variant-position", value: "normal", features: {"subs": 0, "sups": 0} },
+  { prop: "font-variant-position", value: "super", features: {"subs": 0, "sups": 1} },
+  { prop: "font-variant-position", value: "sub", features: {"subs": 1, "sups": 0} },
+
+  // invalid values
+  { prop: "font-variant-position", value: "super sub", features: {"subs": 0, "sups": 0}, invalid: true },
+];
+
+// note: the code below requires an array "gFeatures" from :
+//   layout/reftests/fonts/gsubtest/gsubtest-features.js
+
+// The font defines feature lookups for all OpenType features for a
+// specific set of PUA codepoints, as listed in the gFeatures array.
+// Using these codepoints and feature combinations, tests can be
+// constructed to detect when certain features are enabled or not.
+
+// return a created table containing tests for a given property
+//
+// Ex: { prop: "font-variant-ligatures", value: "common-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0} }
+//
+// This means that for the property 'font-variant-ligatures' with the value 'common-ligatures', the features listed should
+// either be explicitly enabled or disabled.
+
+// propData is the prop/value list with corresponding feature assertions
+// whichProp is either "all" or a specific subproperty (i.e. "font-variant-position")
+// isRef is true when this is the reference
+// debug outputs the prop/value pair along with the tests
+
+function createFeatureTestTable(propData, whichProp, isRef, debug)
+{
+  var table = document.createElement("table");
+
+  if (typeof(isRef) == "undefined") {
+    isRef = false;
+  }
+
+  if (typeof(debug) == "undefined") {
+    debug = false;
+  }
+
+  var doAll = (whichProp == "all");
+  for (var i in propData) {
+    var data = propData[i];
+
+    if (!doAll && data.prop != whichProp) continue;
+
+    var row = document.createElement("tr");
+    var invalid = false;
+    if ("invalid" in data) {
+      invalid = true;
+      row.className = "invalid";
+    }
+
+    var cell = document.createElement("td");
+    cell.className = "prop";
+    var styledecl = gPrefix + data.prop + ": " + data.value + ";";
+    cell.innerHTML = styledecl;
+    row.appendChild(cell);
+    if (debug) {
+      table.appendChild(row);
+    }
+
+    row = document.createElement("tr");
+    if (invalid) {
+      row.className = "invalid";
+    }
+
+    cell = document.createElement("td");
+    cell.className = "features";
+    if (!isRef) {
+      cell.style.cssText = styledecl;
+    }
+
+    for (var f in data.features) {
+      var feature = data.features[f];
+
+      var cp, unsupported = "F".charCodeAt(0);
+      var basecp = gFeatures[f];
+
+      if (typeof(basecp) == "undefined") {
+        cp = unsupported;
+      } else {
+        switch(feature) {
+        case 0:
+          cp = basecp;
+          break;
+        case 1:
+          cp = basecp + 1;
+          break;
+        case 2:
+          cp = basecp + 2;
+          break;
+        case 3:
+          cp = basecp + 3;
+          break;
+        default:
+          cp = basecp + 1;
+          break;
+        }
+      }
+
+      var span = document.createElement("span");
+      span.innerHTML = (isRef ? "P " : "&#x" + cp.toString(16) + "; ");
+      span.title = f + "=" + feature;
+      cell.appendChild(span);
+    }
+    row.appendChild(cell);
+    table.appendChild(row);
+  }
+
+  return table;
+}
+
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-variant-ligatures-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+<title>font-variant-ligatures test</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<script type="text/javascript" src="../fonts/gsubtest/gsubtest-features.js"></script>
+<script type="text/javascript" src="font-variant-features.js"></script>
+<link rel="stylesheet" href="font-variant-features.css" type="text/css"/>
+</head>
+<body>
+<div id="content"></div>
+<script type="text/javascript">
+  document.getElementById("content").appendChild(createFeatureTestTable(gPropertyData, "font-variant-ligatures", true, false));
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-variant-ligatures.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+<title>font-variant-ligatures test</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<script type="text/javascript" src="../fonts/gsubtest/gsubtest-features.js"></script>
+<script type="text/javascript" src="font-variant-features.js"></script>
+<link rel="stylesheet" href="font-variant-features.css" type="text/css"/>
+</head>
+<body>
+<div id="content"></div>
+<script type="text/javascript">
+  document.getElementById("content").appendChild(createFeatureTestTable(gPropertyData, "font-variant-ligatures", false, false));
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-variant-numeric-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+<title>font-variant-numeric test</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<script type="text/javascript" src="../fonts/gsubtest/gsubtest-features.js"></script>
+<script type="text/javascript" src="font-variant-features.js"></script>
+<link rel="stylesheet" href="font-variant-features.css" type="text/css"/>
+</head>
+<body>
+<div id="content"></div>
+<script type="text/javascript">
+  document.getElementById("content").appendChild(createFeatureTestTable(gPropertyData, "font-variant-numeric", true, false));
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-variant-numeric.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+<title>font-variant-numeric test</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<script type="text/javascript" src="../fonts/gsubtest/gsubtest-features.js"></script>
+<script type="text/javascript" src="font-variant-features.js"></script>
+<link rel="stylesheet" href="font-variant-features.css" type="text/css"/>
+</head>
+<body>
+<div id="content"></div>
+<script type="text/javascript">
+  document.getElementById("content").appendChild(createFeatureTestTable(gPropertyData, "font-variant-numeric", false, false));
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-variant-position-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+<title>font-variant-position test</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<script type="text/javascript" src="../fonts/gsubtest/gsubtest-features.js"></script>
+<script type="text/javascript" src="font-variant-features.js"></script>
+<link rel="stylesheet" href="font-variant-features.css" type="text/css"/>
+</head>
+<body>
+<div id="content"></div>
+<script type="text/javascript">
+  document.getElementById("content").appendChild(createFeatureTestTable(gPropertyData, "font-variant-position", true, false));
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-variant-position.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+<title>font-variant-position test</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<script type="text/javascript" src="../fonts/gsubtest/gsubtest-features.js"></script>
+<script type="text/javascript" src="font-variant-features.js"></script>
+<link rel="stylesheet" href="font-variant-features.css" type="text/css"/>
+</head>
+<body>
+<div id="content"></div>
+<script type="text/javascript">
+  document.getElementById("content").appendChild(createFeatureTestTable(gPropertyData, "font-variant-position", false, false));
+</script>
+</body>
+</html>
--- a/layout/reftests/font-features/reftest.list
+++ b/layout/reftests/font-features/reftest.list
@@ -46,16 +46,41 @@ HTTP(..) == font-features-turkish-overri
 HTTP(..) == font-features-turkish-override-3.html font-features-ref.html
 HTTP(..) == font-features-turkish-override-4.html font-features-ref.html
 skip-if(B2G) HTTP(..) == font-features-turkish-override-5.html font-features-turkish.html # bug 773482
 
 # check that last value wins if a feature is repeated
 HTTP(..) == font-features-order-1.html font-features-ref.html
 HTTP(..) == font-features-order-2.html font-features-noliga.html
 
+# check priority of feature settings vs. font-variant subproperty
+HTTP(..) == font-features-order-3.html font-features-noliga.html
+pref(layout.css.font-features.enabled,true) HTTP(..) == font-features-order-4.html font-features-noliga.html
+pref(layout.css.font-features.enabled,true) HTTP(..) == font-features-order-5.html font-features-hlig.html
+
+# font-variant subproperties
+# test for specific features being on and others off, based on prop values
+# (debug problems with font-variant-debug.html which displays all props)
+pref(layout.css.font-features.enabled,true) HTTP(..) == font-variant-caps.html font-variant-caps-ref.html
+pref(layout.css.font-features.enabled,true) HTTP(..) == font-variant-east-asian.html font-variant-east-asian-ref.html
+pref(layout.css.font-features.enabled,true) HTTP(..) == font-variant-ligatures.html font-variant-ligatures-ref.html
+pref(layout.css.font-features.enabled,true) HTTP(..) == font-variant-numeric.html font-variant-numeric-ref.html
+pref(layout.css.font-features.enabled,true) HTTP(..) == font-variant-position.html font-variant-position-ref.html
+
+# font-kerning
+pref(layout.css.font-features.enabled,true) HTTP(..) != font-kerning-normal.html font-kerning-none.html
+pref(layout.css.font-features.enabled,true) HTTP(..) != font-kerning-auto.html font-kerning-none.html
+pref(layout.css.font-features.enabled,true) HTTP(..) == font-kerning-auto.html font-kerning-normal.html
+pref(layout.css.font-features.enabled,true) HTTP(..) == font-kerning-normal.html font-kerning-kern.html
+pref(layout.css.font-features.enabled,true) HTTP(..) == font-kerning-none.html font-kerning-nokern.html
+pref(layout.css.font-features.enabled,true) HTTP(..) == font-kerning-1.html font-kerning-none.html
+pref(layout.css.font-features.enabled,true) HTTP(..) == font-kerning-2.html font-kerning-normal.html
+pref(layout.css.font-features.enabled,true) HTTP(..) == font-kerning-3.html font-kerning-none.html
+pref(layout.css.font-features.enabled,true) HTTP(..) != font-kerning-table-none.html font-kerning-table-normal.html
+
 # sanity check for kerning - with no spaces, kerning should occur
 HTTP(..) == kerning-sanity-check-kern.html kerning-sanity-check-default.html
 HTTP(..) != kerning-sanity-check-nokern.html kerning-sanity-check-default.html
 
 # OpenType features should work across inter-word spaces
 HTTP(..) == font-features-across-space-1.html font-features-across-space-1-ref.html
 # requires Japanese font with feature support, WinXP lacks one
 random-if(!winWidget&&!cocoaWidget) fails-if(/^Windows\x20NT\x205\.1/.test(http.oscpu)) HTTP(..) == fwid-spaces.html fwid-spaces-ref.html