Bug 1321022 pt 4 - Implement CSS parsing of the font-variations-setting property, storing the value into nsFont. r=dholbert
authorJonathan Kew <jkew@mozilla.com>
Sat, 03 Dec 2016 12:18:36 +0000
changeset 325170 763cff42073a4abf77f8586edb289d91eb751cc7
parent 325169 289358891b5f2d7c1f8d2383a83a9bf86258d9ce
child 325171 180d4d40de6b0089b2ccf41f00c43afd93b5d874
push id84613
push userjkew@mozilla.com
push dateSat, 03 Dec 2016 12:25:40 +0000
treeherdermozilla-inbound@870d19bb4759 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert
bugs1321022
milestone53.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 1321022 pt 4 - Implement CSS parsing of the font-variations-setting property, storing the value into nsFont. r=dholbert
layout/style/nsCSSParser.cpp
layout/style/nsCSSPropList.h
layout/style/nsComputedDOMStyle.cpp
layout/style/nsComputedDOMStyle.h
layout/style/nsComputedDOMStylePropertyList.h
layout/style/nsRuleNode.cpp
layout/style/nsRuleNode.h
layout/style/nsStyleUtil.cpp
layout/style/nsStyleUtil.h
layout/style/test/property_database.js
modules/libpref/init/all.js
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -1051,16 +1051,17 @@ protected:
   bool ParseFontVariantEastAsian(nsCSSValue& aValue);
   bool ParseFontVariantLigatures(nsCSSValue& aValue);
   bool ParseFontVariantNumeric(nsCSSValue& aValue);
   bool ParseFontVariant();
   bool ParseFontWeight(nsCSSValue& aValue);
   bool ParseOneFamily(nsAString& aFamily, bool& aOneKeyword, bool& aQuoted);
   bool ParseFamily(nsCSSValue& aValue);
   bool ParseFontFeatureSettings(nsCSSValue& aValue);
+  bool ParseFontVariationSettings(nsCSSValue& aValue);
   bool ParseFontSrc(nsCSSValue& aValue);
   bool ParseFontSrcFormat(InfallibleTArray<nsCSSValue>& values);
   bool ParseFontRanges(nsCSSValue& aValue);
   bool ParseListStyle();
   bool ParseListStyleType(nsCSSValue& aValue);
   bool ParseMargin();
   bool ParseClipPath(nsCSSValue& aValue);
   bool ParseTransform(bool aIsPrefixed, bool aDisallowRelativeValues = false);
@@ -12094,16 +12095,18 @@ CSSParserImpl::ParseSingleValuePropertyB
     case eCSSProperty_font_variant_east_asian:
       return ParseFontVariantEastAsian(aValue);
     case eCSSProperty_font_variant_ligatures:
       return ParseFontVariantLigatures(aValue);
     case eCSSProperty_font_variant_numeric:
       return ParseFontVariantNumeric(aValue);
     case eCSSProperty_font_feature_settings:
       return ParseFontFeatureSettings(aValue);
+    case eCSSProperty_font_variation_settings:
+      return ParseFontVariationSettings(aValue);
     case eCSSProperty_font_weight:
       return ParseFontWeight(aValue);
     case eCSSProperty_image_orientation:
       return ParseImageOrientation(aValue);
     case eCSSProperty_list_style_type:
       return ParseListStyleType(aValue);
     case eCSSProperty_scroll_snap_points_x:
       return ParseScrollSnapPoints(aValue, eCSSProperty_scroll_snap_points_x);
@@ -15350,16 +15353,65 @@ CSSParserImpl::ParseFontFeatureSettings(
     cur->mNext = new nsCSSValuePairList;
     cur = cur->mNext;
   }
 
   return true;
 }
 
 bool
+CSSParserImpl::ParseFontVariationSettings(nsCSSValue& aValue)
+{
+  if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL,
+                              nullptr)) {
+    return true;
+  }
+
+  auto resultHead = MakeUnique<nsCSSValuePairList>();
+  nsCSSValuePairList* cur = resultHead.get();
+
+  for (;;) {
+    // variation tag
+    if (!GetToken(true)) {
+      return false;
+    }
+
+    // variation tags are subject to the same validation as feature tags
+    if (mToken.mType != eCSSToken_String ||
+        !ValidFontFeatureTag(mToken.mIdent)) {
+      UngetToken();
+      return false;
+    }
+    cur->mXValue.SetStringValue(mToken.mIdent, eCSSUnit_String);
+
+    if (!GetToken(true)) {
+      return false;
+    }
+
+    if (mToken.mType == eCSSToken_Number) {
+      cur->mYValue.SetFloatValue(mToken.mNumber, eCSSUnit_Number);
+    } else {
+      UngetToken();
+      return false;
+    }
+
+    if (!ExpectSymbol(',', true)) {
+      break;
+    }
+
+    cur->mNext = new nsCSSValuePairList;
+    cur = cur->mNext;
+  }
+
+  aValue.AdoptPairListValue(Move(resultHead));
+
+  return true;
+}
+
+bool
 CSSParserImpl::ParseListStyle()
 {
   // 'list-style' can accept 'none' for two different subproperties,
   // 'list-style-type' and 'list-style-image'.  In order to accept
   // 'none' as the value of either but still allow another value for
   // either, we need to ensure that the first 'none' we find gets
   // allocated to a dummy property instead. Since parse function for
   // 'list-style-type' could accept values for 'list-style-position',
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -2032,16 +2032,30 @@ CSS_PROP_FONT(
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
         CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
     "",
     VARIANT_HMK,
     kFontVariantPositionKTable,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_Discrete)
 CSS_PROP_FONT(
+    font-variation-settings,
+    font_variation_settings,
+    FontVariationSettings,
+    CSS_PROPERTY_PARSE_VALUE |
+        CSS_PROPERTY_VALUE_PARSER_FUNCTION |
+        CSS_PROPERTY_VALUE_LIST_USES_COMMAS |
+        CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+        CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+    "layout.css.font-variations.enabled",
+    0,
+    nullptr,
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Discrete)
+CSS_PROP_FONT(
     font-weight,
     font_weight,
     FontWeight,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_VALUE_PARSER_FUNCTION |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
         CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
         // NOTE: This property has range restrictions on interpolation!
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -1584,16 +1584,33 @@ nsComputedDOMStyle::DoGetFontFeatureSett
     nsStyleUtil::AppendFontFeatureSettings(font->mFont.fontFeatureSettings,
                                            result);
     val->SetString(result);
   }
   return val.forget();
 }
 
 already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetFontVariationSettings()
+{
+  RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+  const nsStyleFont* font = StyleFont();
+  if (font->mFont.fontVariationSettings.IsEmpty()) {
+    val->SetIdent(eCSSKeyword_normal);
+  } else {
+    nsAutoString result;
+    nsStyleUtil::AppendFontVariationSettings(font->mFont.fontVariationSettings,
+                                             result);
+    val->SetString(result);
+  }
+  return val.forget();
+}
+
+already_AddRefed<CSSValue>
 nsComputedDOMStyle::DoGetFontKerning()
 {
   RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
   val->SetIdent(
     nsCSSProps::ValueToKeywordEnum(StyleFont()->mFont.kerning,
                                    nsCSSProps::kFontKerningKTable));
   return val.forget();
 }
--- a/layout/style/nsComputedDOMStyle.h
+++ b/layout/style/nsComputedDOMStyle.h
@@ -251,16 +251,17 @@ private:
   already_AddRefed<CSSValue> DoGetRight();
   already_AddRefed<CSSValue> DoGetBottom();
   already_AddRefed<CSSValue> DoGetStackSizing();
 
   /* Font properties */
   already_AddRefed<CSSValue> DoGetColor();
   already_AddRefed<CSSValue> DoGetFontFamily();
   already_AddRefed<CSSValue> DoGetFontFeatureSettings();
+  already_AddRefed<CSSValue> DoGetFontVariationSettings();
   already_AddRefed<CSSValue> DoGetFontKerning();
   already_AddRefed<CSSValue> DoGetFontLanguageOverride();
   already_AddRefed<CSSValue> DoGetFontSize();
   already_AddRefed<CSSValue> DoGetFontSizeAdjust();
   already_AddRefed<CSSValue> DoGetOsxFontSmoothing();
   already_AddRefed<CSSValue> DoGetFontStretch();
   already_AddRefed<CSSValue> DoGetFontStyle();
   already_AddRefed<CSSValue> DoGetFontSynthesis();
--- a/layout/style/nsComputedDOMStylePropertyList.h
+++ b/layout/style/nsComputedDOMStylePropertyList.h
@@ -139,16 +139,17 @@ COMPUTED_STYLE_PROP(font_style,         
 COMPUTED_STYLE_PROP(font_synthesis,                FontSynthesis)
 COMPUTED_STYLE_PROP(font_variant,                  FontVariant)
 COMPUTED_STYLE_PROP(font_variant_alternates,       FontVariantAlternates)
 COMPUTED_STYLE_PROP(font_variant_caps,             FontVariantCaps)
 COMPUTED_STYLE_PROP(font_variant_east_asian,       FontVariantEastAsian)
 COMPUTED_STYLE_PROP(font_variant_ligatures,        FontVariantLigatures)
 COMPUTED_STYLE_PROP(font_variant_numeric,          FontVariantNumeric)
 COMPUTED_STYLE_PROP(font_variant_position,         FontVariantPosition)
+COMPUTED_STYLE_PROP(font_variation_settings,       FontVariationSettings)
 COMPUTED_STYLE_PROP(font_weight,                   FontWeight)
 COMPUTED_STYLE_PROP(grid_auto_columns,             GridAutoColumns)
 COMPUTED_STYLE_PROP(grid_auto_flow,                GridAutoFlow)
 COMPUTED_STYLE_PROP(grid_auto_rows,                GridAutoRows)
 COMPUTED_STYLE_PROP(grid_column_end,               GridColumnEnd)
 COMPUTED_STYLE_PROP(grid_column_gap,               GridColumnGap)
 COMPUTED_STYLE_PROP(grid_column_start,             GridColumnStart)
 COMPUTED_STYLE_PROP(grid_row_end,                  GridRowEnd)
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -3989,16 +3989,51 @@ nsRuleNode::SetFont(nsPresContext* aPres
                         aFont->mFont.fontFeatureSettings);
     break;
 
   default:
     MOZ_ASSERT(false, "unexpected value unit");
     break;
   }
 
+  // font-variation-settings
+  const nsCSSValue* variationSettingsValue =
+    aRuleData->ValueForFontVariationSettings();
+
+  switch (variationSettingsValue->GetUnit()) {
+    case eCSSUnit_Null:
+      break;
+
+    case eCSSUnit_Normal:
+    case eCSSUnit_Initial:
+      aFont->mFont.fontVariationSettings.Clear();
+      break;
+
+    case eCSSUnit_Inherit:
+    case eCSSUnit_Unset:
+      aConditions.SetUncacheable();
+      aFont->mFont.fontVariationSettings =
+        aParentFont->mFont.fontVariationSettings;
+      break;
+
+    case eCSSUnit_System_Font:
+      aFont->mFont.fontVariationSettings = systemFont.fontVariationSettings;
+      break;
+
+    case eCSSUnit_PairList:
+    case eCSSUnit_PairListDep:
+      ComputeFontVariations(variationSettingsValue->GetPairListValue(),
+                            aFont->mFont.fontVariationSettings);
+      break;
+
+    default:
+      MOZ_ASSERT(false, "unexpected value unit");
+      break;
+  }
+
   // font-language-override
   const nsCSSValue* languageOverrideValue =
     aRuleData->ValueForFontLanguageOverride();
   if (eCSSUnit_Inherit == languageOverrideValue->GetUnit() ||
       eCSSUnit_Unset == languageOverrideValue->GetUnit()) {
     aConditions.SetUncacheable();
     aFont->mFont.languageOverride = aParentFont->mFont.languageOverride;
   } else if (eCSSUnit_Normal == languageOverrideValue->GetUnit() ||
@@ -4121,16 +4156,28 @@ nsRuleNode::SetFont(nsPresContext* aPres
   if (eCSSUnit_System_Font == sizeAdjustValue->GetUnit()) {
     aFont->mFont.sizeAdjust = systemFont.sizeAdjust;
   } else
     SetFactor(*sizeAdjustValue, aFont->mFont.sizeAdjust,
               aConditions, aParentFont->mFont.sizeAdjust, -1.0f,
               SETFCT_NONE | SETFCT_UNSET_INHERIT);
 }
 
+static inline void
+AssertValidFontTag(const nsString& aString)
+{
+  // To be valid as a font feature tag, a string MUST be:
+  MOZ_ASSERT(aString.Length() == 4 &&              // (1) exactly 4 chars long
+             NS_IsAscii(aString.BeginReading()) && // (2) entirely ASCII
+             isprint(aString[0]) &&                // (3) all printable chars
+             isprint(aString[1]) &&
+             isprint(aString[2]) &&
+             isprint(aString[3]));
+}
+
 /* static */ void
 nsRuleNode::ComputeFontFeatures(const nsCSSValuePairList *aFeaturesList,
                                 nsTArray<gfxFontFeature>& aFeatureSettings)
 {
   aFeatureSettings.Clear();
   for (const nsCSSValuePairList* p = aFeaturesList; p; p = p->mNext) {
     gfxFontFeature feat = {0, 0};
 
@@ -4151,16 +4198,47 @@ nsRuleNode::ComputeFontFeatures(const ns
     NS_ASSERTION(p->mYValue.GetUnit() == eCSSUnit_Integer,
                  "should have found an integer unit");
     feat.mValue = p->mYValue.GetIntValue();
 
     aFeatureSettings.AppendElement(feat);
   }
 }
 
+/* static */ void
+nsRuleNode::ComputeFontVariations(const nsCSSValuePairList* aVariationsList,
+                                  nsTArray<gfxFontVariation>& aVariationSettings)
+{
+  aVariationSettings.Clear();
+  for (const nsCSSValuePairList* p = aVariationsList; p; p = p->mNext) {
+    gfxFontVariation var;
+
+    MOZ_ASSERT(aVariationsList->mXValue.GetUnit() == eCSSUnit_String,
+               "unexpected value unit");
+
+    // tag is a 4-byte ASCII sequence
+    nsAutoString tag;
+    p->mXValue.GetStringValue(tag);
+    AssertValidFontTag(tag);
+    if (tag.Length() != 4) {
+      continue;
+    }
+    // parsing validates that these are ASCII chars
+    // tags are always big-endian
+    var.mTag = (tag[0] << 24) | (tag[1] << 16) | (tag[2] << 8)  | tag[3];
+
+    // value
+    NS_ASSERTION(p->mYValue.GetUnit() == eCSSUnit_Number,
+                 "should have found a number unit");
+    var.mValue = p->mYValue.GetFloatValue();
+
+    aVariationSettings.AppendElement(var);
+  }
+}
+
 // This should die (bug 380915).
 //
 // SetGenericFont:
 //  - backtrack to an ancestor with the same generic font name (possibly
 //    up to the root where default values come from the presentation context)
 //  - re-apply cascading rules from there without caching intermediate values
 /* static */ void
 nsRuleNode::SetGenericFont(nsPresContext* aPresContext,
--- a/layout/style/nsRuleNode.h
+++ b/layout/style/nsRuleNode.h
@@ -1020,16 +1020,19 @@ public:
   // style structs.
   bool NodeHasCachedUnconditionalData(const nsStyleStructID aSID) {
     return !!mStyleData.GetStyleData(aSID);
   }
 
   static void ComputeFontFeatures(const nsCSSValuePairList *aFeaturesList,
                                   nsTArray<gfxFontFeature>& aFeatureSettings);
 
+  static void ComputeFontVariations(const nsCSSValuePairList* aVariationsList,
+                                    nsTArray<gfxFontVariation>& aVariationSettings);
+
   static nscoord CalcFontPointSize(int32_t aHTMLSize, int32_t aBasePointSize,
                                    nsPresContext* aPresContext,
                                    nsFontSizeType aFontSizeType = eFontSize_HTML);
 
   static nscoord FindNextSmallerFontSize(nscoord aFontSize, int32_t aBasePointSize,
                                          nsPresContext* aPresContext,
                                          nsFontSizeType aFontSizeType = eFontSize_HTML);
 
--- a/layout/style/nsStyleUtil.cpp
+++ b/layout/style/nsStyleUtil.cpp
@@ -386,16 +386,55 @@ nsStyleUtil::AppendFontFeatureSettings(c
                   "improper value unit for font-feature-settings:");
 
   nsTArray<gfxFontFeature> featureSettings;
   nsRuleNode::ComputeFontFeatures(aSrc.GetPairListValue(), featureSettings);
   AppendFontFeatureSettings(featureSettings, aResult);
 }
 
 /* static */ void
+nsStyleUtil::AppendFontVariationSettings(const nsTArray<gfxFontVariation>& aVariations,
+                                         nsAString& aResult)
+{
+  for (uint32_t i = 0, numVars = aVariations.Length(); i < numVars; i++) {
+    const gfxFontVariation& var = aVariations[i];
+
+    if (i != 0) {
+      aResult.AppendLiteral(", ");
+    }
+
+    // output tag
+    AppendFontTagAsString(var.mTag, aResult);
+
+    // output value
+    aResult.Append(' ');
+    aResult.AppendFloat(var.mValue);
+  }
+}
+
+/* static */ void
+nsStyleUtil::AppendFontVariationSettings(const nsCSSValue& aSrc,
+                                         nsAString& aResult)
+{
+  nsCSSUnit unit = aSrc.GetUnit();
+
+  if (unit == eCSSUnit_Normal) {
+    aResult.AppendLiteral("normal");
+    return;
+  }
+
+  NS_PRECONDITION(unit == eCSSUnit_PairList || unit == eCSSUnit_PairListDep,
+                  "improper value unit for font-variation-settings:");
+
+  nsTArray<gfxFontVariation> variationSettings;
+  nsRuleNode::ComputeFontVariations(aSrc.GetPairListValue(), variationSettings);
+  AppendFontVariationSettings(variationSettings, aResult);
+}
+
+/* static */ void
 nsStyleUtil::GetFunctionalAlternatesName(int32_t aFeature,
                                          nsAString& aFeatureName)
 {
   aFeatureName.Truncate();
   nsCSSKeyword key =
     nsCSSProps::ValueToKeywordEnum(aFeature,
                            nsCSSProps::kFontVariantAlternatesFuncsKTable);
 
--- a/layout/style/nsStyleUtil.h
+++ b/layout/style/nsStyleUtil.h
@@ -66,16 +66,22 @@ public:
   static void AppendFontTagAsString(uint32_t aTag, nsAString& aResult);
 
   static void AppendFontFeatureSettings(const nsTArray<gfxFontFeature>& aFeatures,
                                         nsAString& aResult);
 
   static void AppendFontFeatureSettings(const nsCSSValue& src,
                                         nsAString& aResult);
 
+  static void AppendFontVariationSettings(const nsTArray<gfxFontVariation>& aVariations,
+                                          nsAString& aResult);
+
+  static void AppendFontVariationSettings(const nsCSSValue& src,
+                                          nsAString& aResult);
+
   static void AppendUnicodeRange(const nsCSSValue& aValue, nsAString& aResult);
 
   static void AppendCSSNumber(float aNumber, nsAString& aResult)
   {
     aResult.AppendFloat(aNumber);
   }
 
   static void AppendStepsTimingFunction(nsTimingFunction::Type aType,
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -5682,16 +5682,41 @@ if (IsCSSPropertyPrefEnabled("layout.css
                       "digits 3 all", "digits foo", "digits all", "digits 3.0" ]
   };
   if (IsCSSPropertyPrefEnabled("layout.css.text-combine-upright-digits.enabled")) {
     gCSSProperties["text-combine-upright"].other_values.push(
       "digits", "digits 2", "digits 3", "digits 4", "digits     3");
   }
 }
 
+if (IsCSSPropertyPrefEnabled("layout.css.font-variations.enabled")) {
+  gCSSProperties["font-variation-settings"] = {
+    domProp: "fontVariationSettings",
+    inherited: true,
+    type: CSS_TYPE_LONGHAND,
+    initial_values: [ "normal" ],
+    other_values: [
+      "'wdth' 0", "'wdth' -.1", "\"wdth\" 1", "'wdth' 2, 'wght' 3", "\"XXXX\" 0"
+    ],
+    invalid_values: [
+      "wdth", "wdth 1", // unquoted tags
+      "'wdth'", "'wdth' 'wght'", "'wdth', 'wght'", // missing values
+      "'' 1", "'wid' 1", "'width' 1", // incorrect tag lengths
+      "'wd\th' 1", // non-graphic character in tag
+      "'wdth' 1 'wght' 2", // missing comma between pairs
+      "'wdth' 1,", // trailing comma
+      "'wdth' 1 , , 'wght' 2", // extra comma
+      "'wdth', 1" // comma within pair
+    ],
+    unbalanced_values: [
+      "'wdth\" 1", "\"wdth' 1" // mismatched quotes
+    ]
+  }
+}
+
 if (IsCSSPropertyPrefEnabled("svg.paint-order.enabled")) {
   gCSSProperties["paint-order"] = {
     domProp: "paintOrder",
     inherited: true,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "normal" ],
     other_values: [ "fill", "fill stroke", "fill stroke markers", "stroke markers fill" ],
     invalid_values: [ "fill stroke markers fill", "fill normal" ]
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2542,16 +2542,19 @@ pref("layout.css.float-logical-values.en
 #endif
 
 // Is support for the CSS4 image-orientation property enabled?
 pref("layout.css.image-orientation.enabled", true);
 
 // Is support for the font-display @font-face descriptor enabled?
 pref("layout.css.font-display.enabled", false);
 
+// Is support for variation fonts enabled?
+pref("layout.css.font-variations.enabled", false);
+
 // Are sets of prefixed properties supported?
 pref("layout.css.prefixes.border-image", true);
 pref("layout.css.prefixes.transforms", true);
 pref("layout.css.prefixes.transitions", true);
 pref("layout.css.prefixes.animations", true);
 pref("layout.css.prefixes.box-sizing", true);
 pref("layout.css.prefixes.font-features", true);
 pref("layout.css.prefixes.gradients", true);