servo: Merge #17894 - Implement parsing/serialization for @font-feature-values rule (from canaltinova:at-font-feature-values); r=SimonSapin,xidorn
authorNazım Can Altınova <canaltinova@gmail.com>
Thu, 27 Jul 2017 16:15:02 -0500
changeset 420208 4538ef25253032a5624b4610e402f4e85c1fecc7
parent 420207 8816877de730339d9355892a0cd175d99cd4a6c2
child 420209 b5451544fbeef393e4db58da5a4b7a1e5a3746c8
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersSimonSapin, xidorn
bugs17894, 1365900
milestone56.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
servo: Merge #17894 - Implement parsing/serialization for @font-feature-values rule (from canaltinova:at-font-feature-values); r=SimonSapin,xidorn <!-- Please describe your changes on the following line: --> This is reviewed by SimonSapin and xidorn in [Bug 1365900](https://bugzilla.mozilla.org/show_bug.cgi?id=1365900). --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix [Bug 1365900](https://bugzilla.mozilla.org/show_bug.cgi?id=1365900) Source-Repo: https://github.com/servo/servo Source-Revision: 00e55dd278f46560e61959a1fd5aa9e1846cb557
servo/components/script/dom/cssrule.rs
servo/components/style/error_reporting.rs
servo/components/style/gecko/arc_types.rs
servo/components/style/gecko/generated/bindings.rs
servo/components/style/invalidation/stylesheets.rs
servo/components/style/stylesheets/font_feature_values_rule.rs
servo/components/style/stylesheets/mod.rs
servo/components/style/stylesheets/rule_parser.rs
servo/components/style/stylesheets/rules_iterator.rs
servo/components/style/stylesheets/stylesheet.rs
servo/components/style/stylist.rs
servo/ports/geckolib/error_reporter.rs
servo/ports/geckolib/glue.rs
servo/tests/unit/style/stylesheets.rs
--- a/servo/components/script/dom/cssrule.rs
+++ b/servo/components/script/dom/cssrule.rs
@@ -73,16 +73,17 @@ impl CSSRule {
     // CSSRule based on which rule it is
     pub fn new_specific(window: &Window, parent_stylesheet: &CSSStyleSheet,
                         rule: StyleCssRule) -> Root<CSSRule> {
         // be sure to update the match in as_specific when this is updated
         match rule {
             StyleCssRule::Import(s) => Root::upcast(CSSImportRule::new(window, parent_stylesheet, s)),
             StyleCssRule::Style(s) => Root::upcast(CSSStyleRule::new(window, parent_stylesheet, s)),
             StyleCssRule::FontFace(s) => Root::upcast(CSSFontFaceRule::new(window, parent_stylesheet, s)),
+            StyleCssRule::FontFeatureValues(_) => unimplemented!(),
             StyleCssRule::CounterStyle(_) => unimplemented!(),
             StyleCssRule::Keyframes(s) => Root::upcast(CSSKeyframesRule::new(window, parent_stylesheet, s)),
             StyleCssRule::Media(s) => Root::upcast(CSSMediaRule::new(window, parent_stylesheet, s)),
             StyleCssRule::Namespace(s) => Root::upcast(CSSNamespaceRule::new(window, parent_stylesheet, s)),
             StyleCssRule::Viewport(s) => Root::upcast(CSSViewportRule::new(window, parent_stylesheet, s)),
             StyleCssRule::Supports(s) => Root::upcast(CSSSupportsRule::new(window, parent_stylesheet, s)),
             StyleCssRule::Page(_) => unreachable!(),
             StyleCssRule::Document(_) => unimplemented!(), // TODO
--- a/servo/components/style/error_reporting.rs
+++ b/servo/components/style/error_reporting.rs
@@ -13,18 +13,22 @@ use style_traits::ParseError;
 use stylesheets::UrlExtraData;
 
 /// Errors that can be encountered while parsing CSS.
 pub enum ContextualParseError<'a> {
     /// A property declaration was not recognized.
     UnsupportedPropertyDeclaration(&'a str, ParseError<'a>),
     /// A font face descriptor was not recognized.
     UnsupportedFontFaceDescriptor(&'a str, ParseError<'a>),
+    /// A font feature values descroptor was not recognized.
+    UnsupportedFontFeatureValuesDescriptor(&'a str, ParseError<'a>),
     /// A keyframe rule was not valid.
     InvalidKeyframeRule(&'a str, ParseError<'a>),
+    /// A font feature values rule was not valid.
+    InvalidFontFeatureValuesRule(&'a str, ParseError<'a>),
     /// A keyframe property declaration was not recognized.
     UnsupportedKeyframePropertyDeclaration(&'a str, ParseError<'a>),
     /// A rule was invalid for some reason.
     InvalidRule(&'a str, ParseError<'a>),
     /// A rule was not recognized.
     UnsupportedRule(&'a str, ParseError<'a>),
     /// A viewport descriptor declaration was not recognized.
     UnsupportedViewportDescriptorDeclaration(&'a str, ParseError<'a>),
@@ -103,19 +107,25 @@ impl<'a> ContextualParseError<'a> {
 
         match *self {
             ContextualParseError::UnsupportedPropertyDeclaration(decl, ref err) =>
                 format!("Unsupported property declaration: '{}', {}", decl,
                         parse_error_to_str(err)),
             ContextualParseError::UnsupportedFontFaceDescriptor(decl, ref err) =>
                 format!("Unsupported @font-face descriptor declaration: '{}', {}", decl,
                         parse_error_to_str(err)),
+            ContextualParseError::UnsupportedFontFeatureValuesDescriptor(decl, ref err) =>
+            format!("Unsupported @font-feature-values descriptor declaration: '{}', {}", decl,
+                    parse_error_to_str(err)),
             ContextualParseError::InvalidKeyframeRule(rule, ref err) =>
                 format!("Invalid keyframe rule: '{}', {}", rule,
                         parse_error_to_str(err)),
+            ContextualParseError::InvalidFontFeatureValuesRule(rule, ref err) =>
+            format!("Invalid font feature value rule: '{}', {}", rule,
+                    parse_error_to_str(err)),
             ContextualParseError::UnsupportedKeyframePropertyDeclaration(decl, ref err) =>
                 format!("Unsupported keyframe property declaration: '{}', {}", decl,
                         parse_error_to_str(err)),
             ContextualParseError::InvalidRule(rule, ref err) =>
                 format!("Invalid rule: '{}', {}", rule, parse_error_to_str(err)),
             ContextualParseError::UnsupportedRule(rule, ref err) =>
                 format!("Unsupported rule: '{}', {}", rule, parse_error_to_str(err)),
             ContextualParseError::UnsupportedViewportDescriptorDeclaration(decl, ref err) =>
--- a/servo/components/style/gecko/arc_types.rs
+++ b/servo/components/style/gecko/arc_types.rs
@@ -3,33 +3,33 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! This file lists all arc FFI types and defines corresponding addref
 //! and release functions. This list corresponds to ServoArcTypeList.h
 //! file in Gecko.
 
 #![allow(non_snake_case, missing_docs)]
 
-use gecko_bindings::bindings::{RawServoImportRule, RawServoSupportsRule};
+use gecko_bindings::bindings::{RawServoFontFeatureValuesRule, RawServoImportRule, RawServoSupportsRule};
 use gecko_bindings::bindings::{RawServoKeyframe, RawServoKeyframesRule};
 use gecko_bindings::bindings::{RawServoMediaRule, RawServoNamespaceRule, RawServoPageRule};
 use gecko_bindings::bindings::{RawServoRuleNode, RawServoRuleNodeStrong, RawServoDocumentRule};
 use gecko_bindings::bindings::ServoCssRules;
 use gecko_bindings::structs::{RawServoAnimationValue, RawServoDeclarationBlock, RawServoStyleRule};
 use gecko_bindings::structs::{RawServoMediaList, RawServoStyleSheetContents};
 use gecko_bindings::sugar::ownership::{HasArcFFI, HasFFI, Strong};
 use media_queries::MediaList;
 use properties::{ComputedValues, PropertyDeclarationBlock};
 use properties::animated_properties::AnimationValue;
 use rule_tree::StrongRuleNode;
 use servo_arc::{Arc, ArcBorrow};
 use shared_lock::Locked;
 use std::{mem, ptr};
 use stylesheets::{CssRules, StylesheetContents, StyleRule, ImportRule, KeyframesRule, MediaRule};
-use stylesheets::{NamespaceRule, PageRule, SupportsRule, DocumentRule};
+use stylesheets::{FontFeatureValuesRule, NamespaceRule, PageRule, SupportsRule, DocumentRule};
 use stylesheets::keyframes_rule::Keyframe;
 
 macro_rules! impl_arc_ffi {
     ($servo_type:ty => $gecko_type:ty [$addref:ident, $release:ident]) => {
         unsafe impl HasFFI for $servo_type {
             type FFIType = $gecko_type;
         }
         unsafe impl HasArcFFI for $servo_type {}
@@ -83,16 +83,19 @@ impl_arc_ffi!(Locked<PageRule> => RawSer
               [Servo_PageRule_AddRef, Servo_PageRule_Release]);
 
 impl_arc_ffi!(Locked<SupportsRule> => RawServoSupportsRule
               [Servo_SupportsRule_AddRef, Servo_SupportsRule_Release]);
 
 impl_arc_ffi!(Locked<DocumentRule> => RawServoDocumentRule
               [Servo_DocumentRule_AddRef, Servo_DocumentRule_Release]);
 
+impl_arc_ffi!(Locked<FontFeatureValuesRule> => RawServoFontFeatureValuesRule
+              [Servo_FontFeatureValuesRule_AddRef, Servo_FontFeatureValuesRule_Release]);
+
 // RuleNode is a Arc-like type but it does not use Arc.
 
 impl StrongRuleNode {
     pub fn into_strong(self) -> RawServoRuleNodeStrong {
         let ptr = self.ptr();
         mem::forget(self);
         unsafe { mem::transmute(ptr) }
     }
--- a/servo/components/style/gecko/generated/bindings.rs
+++ b/servo/components/style/gecko/generated/bindings.rs
@@ -373,16 +373,21 @@ pub type RawServoSupportsRuleBorrowed<'a
 pub type RawServoSupportsRuleBorrowedOrNull<'a> = Option<&'a RawServoSupportsRule>;
 enum RawServoSupportsRuleVoid { }
 pub struct RawServoSupportsRule(RawServoSupportsRuleVoid);
 pub type RawServoDocumentRuleStrong = ::gecko_bindings::sugar::ownership::Strong<RawServoDocumentRule>;
 pub type RawServoDocumentRuleBorrowed<'a> = &'a RawServoDocumentRule;
 pub type RawServoDocumentRuleBorrowedOrNull<'a> = Option<&'a RawServoDocumentRule>;
 enum RawServoDocumentRuleVoid { }
 pub struct RawServoDocumentRule(RawServoDocumentRuleVoid);
+pub type RawServoFontFeatureValuesRuleStrong = ::gecko_bindings::sugar::ownership::Strong<RawServoFontFeatureValuesRule>;
+pub type RawServoFontFeatureValuesRuleBorrowed<'a> = &'a RawServoFontFeatureValuesRule;
+pub type RawServoFontFeatureValuesRuleBorrowedOrNull<'a> = Option<&'a RawServoFontFeatureValuesRule>;
+enum RawServoFontFeatureValuesRuleVoid { }
+pub struct RawServoFontFeatureValuesRule(RawServoFontFeatureValuesRuleVoid);
 pub type RawServoRuleNodeStrong = ::gecko_bindings::sugar::ownership::Strong<RawServoRuleNode>;
 pub type RawServoRuleNodeBorrowed<'a> = &'a RawServoRuleNode;
 pub type RawServoRuleNodeBorrowedOrNull<'a> = Option<&'a RawServoRuleNode>;
 enum RawServoRuleNodeVoid { }
 pub struct RawServoRuleNode(RawServoRuleNodeVoid);
 
 extern "C" {
     pub fn Gecko_EnsureTArrayCapacity(aArray: *mut ::std::os::raw::c_void,
@@ -476,16 +481,24 @@ extern "C" {
 }
 extern "C" {
     pub fn Servo_DocumentRule_AddRef(ptr: RawServoDocumentRuleBorrowed);
 }
 extern "C" {
     pub fn Servo_DocumentRule_Release(ptr: RawServoDocumentRuleBorrowed);
 }
 extern "C" {
+    pub fn Servo_FontFeatureValuesRule_AddRef(ptr:
+                                                  RawServoFontFeatureValuesRuleBorrowed);
+}
+extern "C" {
+    pub fn Servo_FontFeatureValuesRule_Release(ptr:
+                                                   RawServoFontFeatureValuesRuleBorrowed);
+}
+extern "C" {
     pub fn Servo_RuleNode_AddRef(ptr: RawServoRuleNodeBorrowed);
 }
 extern "C" {
     pub fn Servo_RuleNode_Release(ptr: RawServoRuleNodeBorrowed);
 }
 extern "C" {
     pub fn Servo_StyleSet_Drop(ptr: RawServoStyleSetOwned);
 }
@@ -2155,16 +2168,34 @@ extern "C" {
     pub fn Servo_DocumentRule_GetCssText(rule: RawServoDocumentRuleBorrowed,
                                          result: *mut nsAString);
 }
 extern "C" {
     pub fn Servo_DocumentRule_GetRules(rule: RawServoDocumentRuleBorrowed)
      -> ServoCssRulesStrong;
 }
 extern "C" {
+    pub fn Servo_CssRules_GetFontFeatureValuesRuleAt(rules:
+                                                         ServoCssRulesBorrowed,
+                                                     index: u32,
+                                                     line: *mut u32,
+                                                     column: *mut u32)
+     -> RawServoFontFeatureValuesRuleStrong;
+}
+extern "C" {
+    pub fn Servo_FontFeatureValuesRule_Debug(rule:
+                                                 RawServoFontFeatureValuesRuleBorrowed,
+                                             result: *mut nsACString);
+}
+extern "C" {
+    pub fn Servo_FontFeatureValuesRule_GetCssText(rule:
+                                                      RawServoFontFeatureValuesRuleBorrowed,
+                                                  result: *mut nsAString);
+}
+extern "C" {
     pub fn Servo_CssRules_GetFontFaceRuleAt(rules: ServoCssRulesBorrowed,
                                             index: u32)
      -> *mut nsCSSFontFaceRule;
 }
 extern "C" {
     pub fn Servo_CssRules_GetCounterStyleRuleAt(rules: ServoCssRulesBorrowed,
                                                 index: u32)
      -> *mut nsCSSCounterStyleRule;
@@ -2292,16 +2323,26 @@ extern "C" {
                                                result: *mut nsAString);
 }
 extern "C" {
     pub fn Servo_DocumentRule_GetConditionText(rule:
                                                    RawServoDocumentRuleBorrowed,
                                                result: *mut nsAString);
 }
 extern "C" {
+    pub fn Servo_FontFeatureValuesRule_GetFontFamily(rule:
+                                                         RawServoFontFeatureValuesRuleBorrowed,
+                                                     result: *mut nsAString);
+}
+extern "C" {
+    pub fn Servo_FontFeatureValuesRule_GetValueText(rule:
+                                                        RawServoFontFeatureValuesRuleBorrowed,
+                                                    result: *mut nsAString);
+}
+extern "C" {
     pub fn Servo_ParseProperty(property: nsCSSPropertyID,
                                value: *const nsACString,
                                data: *mut RawGeckoURLExtraData,
                                parsing_mode: ParsingMode,
                                quirks_mode: nsCompatibility,
                                loader: *mut Loader)
      -> RawServoDeclarationBlockStrong;
 }
--- a/servo/components/style/invalidation/stylesheets.rs
+++ b/servo/components/style/invalidation/stylesheets.rs
@@ -296,17 +296,18 @@ impl StylesheetInvalidationSet {
             Supports(..) => {
                 // Do nothing, relevant nested rules are visited as part of the
                 // iteration.
             }
             FontFace(..) |
             CounterStyle(..) |
             Keyframes(..) |
             Page(..) |
-            Viewport(..) => {
+            Viewport(..) |
+            FontFeatureValues(..) => {
                 debug!(" > Found unsupported rule, marking the whole subtree \
                        invalid.");
 
                 // TODO(emilio): Can we do better here?
                 //
                 // At least in `@page`, we could check the relevant media, I
                 // guess.
                 self.fully_invalid = true;
new file mode 100644
--- /dev/null
+++ b/servo/components/style/stylesheets/font_feature_values_rule.rs
@@ -0,0 +1,389 @@
+/* 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/. */
+
+//! The [`@font-feature-values`][font-feature-values] at-rule.
+//!
+//! [font-feature-values]: https://drafts.csswg.org/css-fonts-3/#at-font-feature-values-rule
+
+use Atom;
+use computed_values::font_family::FamilyName;
+use cssparser::{AtRuleParser, AtRuleType, BasicParseError, DeclarationListParser, DeclarationParser, Parser};
+use cssparser::{CowRcStr, RuleListParser, SourceLocation, QualifiedRuleParser, Token, serialize_identifier};
+use error_reporting::ContextualParseError;
+use parser::{ParserContext, log_css_error, Parse};
+use selectors::parser::SelectorParseError;
+use shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
+use std::fmt;
+use style_traits::{ParseError, StyleParseError, ToCss};
+use stylesheets::CssRuleType;
+
+/// A @font-feature-values block declaration.
+/// It is `<ident>: <integer>+`.
+/// This struct can take 3 value types.
+/// - `SingleValue` is to keep just one unsigned integer value.
+/// - `PairValues` is to keep one or two unsigned integer values.
+/// - `VectorValues` is to keep a list of unsigned integer values.
+#[derive(Clone, Debug, PartialEq)]
+pub struct FFVDeclaration<T> {
+    /// An `<ident>` for declaration name.
+    pub name: Atom,
+    /// An `<integer>+` for declaration value.
+    pub value: T,
+}
+
+impl<T: ToCss> ToCss for FFVDeclaration<T> {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        serialize_identifier(&self.name.to_string(), dest)?;
+        dest.write_str(": ")?;
+        self.value.to_css(dest)?;
+        dest.write_str(";")
+    }
+}
+
+/// A @font-feature-values block declaration value that keeps one value.
+#[derive(Clone, Debug, PartialEq)]
+pub struct SingleValue(pub u32);
+
+impl Parse for SingleValue {
+    fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                     -> Result<SingleValue, ParseError<'i>> {
+        match *input.next()? {
+            Token::Number { int_value: Some(v), .. } if v >= 0 => Ok(SingleValue(v as u32)),
+            ref t => Err(BasicParseError::UnexpectedToken(t.clone()).into()),
+        }
+    }
+}
+
+impl ToCss for SingleValue {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        write!(dest, "{}", self.0)
+    }
+}
+
+/// A @font-feature-values block declaration value that keeps one or two values.
+#[derive(Clone, Debug, PartialEq)]
+pub struct PairValues(pub u32, pub Option<u32>);
+
+impl Parse for PairValues {
+    fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                     -> Result<PairValues, ParseError<'i>> {
+        let first = match *input.next()? {
+            Token::Number { int_value: Some(a), .. } if a >= 0 => a as u32,
+            ref t => return Err(BasicParseError::UnexpectedToken(t.clone()).into()),
+        };
+        match input.next() {
+            Ok(&Token::Number { int_value: Some(b), .. }) if b >= 0 => {
+                Ok(PairValues(first, Some(b as u32)))
+            }
+            // It can't be anything other than number.
+            Ok(t) => Err(BasicParseError::UnexpectedToken(t.clone()).into()),
+            // It can be just one value.
+            Err(_) => Ok(PairValues(first, None))
+        }
+    }
+}
+
+impl ToCss for PairValues {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        write!(dest, "{}", self.0)?;
+        if let Some(second) = self.1 {
+            write!(dest, " {}", second)?;
+        }
+        Ok(())
+    }
+}
+
+/// A @font-feature-values block declaration value that keeps a list of values.
+#[derive(Clone, Debug, PartialEq)]
+pub struct VectorValues(pub Vec<u32>);
+
+impl Parse for VectorValues {
+    fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                     -> Result<VectorValues, ParseError<'i>> {
+        let mut vec = vec![];
+        loop {
+            match input.next() {
+                Ok(&Token::Number { int_value: Some(a), .. }) if a >= 0 => {
+                    vec.push(a as u32);
+                },
+                // It can't be anything other than number.
+                Ok(t) => return Err(BasicParseError::UnexpectedToken(t.clone()).into()),
+                Err(_) => break,
+            }
+        }
+
+        if vec.len() == 0 {
+            return Err(BasicParseError::EndOfInput.into());
+        }
+
+        Ok(VectorValues(vec))
+    }
+}
+
+impl ToCss for VectorValues {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        let mut iter = self.0.iter();
+        let first = iter.next();
+        if let Some(first) = first {
+            write!(dest, "{}", first)?;
+            for value in iter {
+                dest.write_str(" ")?;
+                write!(dest, "{}", value)?;
+            }
+        }
+        Ok(())
+    }
+}
+
+/// Parses a list of `FamilyName`s.
+pub fn parse_family_name_list<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                                  -> Result<Vec<FamilyName>, ParseError<'i>> {
+    input.parse_comma_separated(|i| FamilyName::parse(context, i)).map_err(|e| e.into())
+}
+
+/// @font-feature-values inside block parser. Parses a list of `FFVDeclaration`.
+/// (`<ident>: <integer>+`)
+struct FFVDeclarationsParser<'a, 'b: 'a, T: 'a> {
+    context: &'a ParserContext<'b>,
+    declarations: &'a mut Vec<FFVDeclaration<T>>,
+}
+
+/// Default methods reject all at rules.
+impl<'a, 'b, 'i, T> AtRuleParser<'i> for FFVDeclarationsParser<'a, 'b, T> {
+    type Prelude = ();
+    type AtRule = ();
+    type Error = SelectorParseError<'i, StyleParseError<'i>>;
+}
+
+impl<'a, 'b, 'i, T> DeclarationParser<'i> for FFVDeclarationsParser<'a, 'b, T>
+    where T: Parse
+{
+    type Declaration = ();
+    type Error = SelectorParseError<'i, StyleParseError<'i>>;
+
+    fn parse_value<'t>(&mut self, name: CowRcStr<'i>, input: &mut Parser<'i, 't>)
+                       -> Result<(), ParseError<'i>> {
+        let value = input.parse_entirely(|i| T::parse(self.context, i))?;
+        let new = FFVDeclaration {
+            name: Atom::from(&*name),
+            value: value,
+        };
+        update_or_push(&mut self.declarations, new);
+        Ok(())
+    }
+}
+
+macro_rules! font_feature_values_blocks {
+    (
+        blocks = [
+            $( #[$doc: meta] $name: tt $ident: ident / $ident_camel: ident: $ty: ty, )*
+        ]
+    ) => {
+        /// The [`@font-feature-values`][font-feature-values] at-rule.
+        ///
+        /// [font-feature-values]: https://drafts.csswg.org/css-fonts-3/#at-font-feature-values-rule
+        #[derive(Clone, Debug, PartialEq)]
+        pub struct FontFeatureValuesRule {
+            /// Font family list for @font-feature-values rule.
+            /// Family names cannot contain generic families. FamilyName
+            /// also accepts only non-generic names.
+            pub family_names: Vec<FamilyName>,
+            $(
+                #[$doc]
+                pub $ident: Vec<FFVDeclaration<$ty>>,
+            )*
+            /// The line and column of the rule's source code.
+            pub source_location: SourceLocation,
+        }
+
+        impl FontFeatureValuesRule {
+            /// Creates an empty FontFeatureValuesRule with given location and family name list.
+            fn new(family_names: Vec<FamilyName>, location: SourceLocation) -> Self {
+                FontFeatureValuesRule {
+                    family_names: family_names,
+                    $(
+                        $ident: vec![],
+                    )*
+                    source_location: location,
+                }
+            }
+
+            /// Parses a `FontFeatureValuesRule`.
+            pub fn parse(context: &ParserContext, input: &mut Parser,
+                         family_names: Vec<FamilyName>, location: SourceLocation)
+                         -> FontFeatureValuesRule {
+                let mut rule = FontFeatureValuesRule::new(family_names, location);
+
+                {
+                    let mut iter = RuleListParser::new_for_nested_rule(input, FontFeatureValuesRuleParser {
+                        context: context,
+                        rule: &mut rule,
+                    });
+                    while let Some(result) = iter.next() {
+                        if let Err(err) = result {
+                            let pos = err.span.start;
+                            let error = ContextualParseError::UnsupportedRule(
+                                iter.input.slice(err.span), err.error);
+                            log_css_error(iter.input, pos, error, context);
+                        }
+                    }
+                }
+                rule
+            }
+
+            /// Prints font family names.
+            pub fn font_family_to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+                let mut iter = self.family_names.iter();
+                iter.next().unwrap().to_css(dest)?;
+                for val in iter {
+                    dest.write_str(", ")?;
+                    val.to_css(dest)?;
+                }
+                Ok(())
+            }
+
+            /// Prints inside of `@font-feature-values` block.
+            pub fn value_to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+                $(
+                    if self.$ident.len() > 0 {
+                        dest.write_str(concat!("@", $name, " {\n"))?;
+                        let iter = self.$ident.iter();
+                        for val in iter {
+                            val.to_css(dest)?;
+                            dest.write_str("\n")?
+                        }
+                        dest.write_str("}\n")?
+                    }
+                )*
+                Ok(())
+            }
+        }
+
+        impl ToCssWithGuard for FontFeatureValuesRule {
+            fn to_css<W>(&self, _guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
+                where W: fmt::Write
+            {
+                dest.write_str("@font-feature-values ")?;
+                self.font_family_to_css(dest)?;
+                dest.write_str(" {\n")?;
+                self.value_to_css(dest)?;
+                dest.write_str("}")
+            }
+        }
+
+        /// Updates with new value if same `ident` exists, otherwise pushes to the vector.
+        fn update_or_push<T>(vec: &mut Vec<FFVDeclaration<T>>, element: FFVDeclaration<T>) {
+            let position = vec.iter().position(|ref val| val.name == element.name);
+            if let Some(index) = position {
+                vec[index].value = element.value;
+            } else {
+                vec.push(element);
+            }
+        }
+
+        /// Keeps the information about block type like @swash, @styleset etc.
+        enum BlockType {
+            $(
+                $ident_camel,
+            )*
+        }
+
+        /// Parser for `FontFeatureValuesRule`. Parses all blocks
+        /// <feature-type> {
+        ///   <feature-value-declaration-list>
+        /// }
+        /// <feature-type> = @stylistic | @historical-forms | @styleset |
+        /// @character-variant | @swash | @ornaments | @annotation
+        struct FontFeatureValuesRuleParser<'a> {
+            context: &'a ParserContext<'a>,
+            rule: &'a mut FontFeatureValuesRule,
+        }
+
+        /// Default methods reject all qualified rules.
+        impl<'a, 'i> QualifiedRuleParser<'i> for FontFeatureValuesRuleParser<'a> {
+            type Prelude = ();
+            type QualifiedRule = ();
+            type Error = SelectorParseError<'i, StyleParseError<'i>>;
+        }
+
+        impl<'a, 'i> AtRuleParser<'i> for FontFeatureValuesRuleParser<'a> {
+            type Prelude = BlockType;
+            type AtRule = ();
+            type Error = SelectorParseError<'i, StyleParseError<'i>>;
+
+            fn parse_prelude<'t>(&mut self,
+                                 name: CowRcStr<'i>,
+                                 _input: &mut Parser<'i, 't>)
+                                 -> Result<AtRuleType<Self::Prelude, Self::AtRule>, ParseError<'i>> {
+                match_ignore_ascii_case! { &*name,
+                    $(
+                        $name => Ok(AtRuleType::WithBlock(BlockType::$ident_camel)),
+                    )*
+                    _ => Err(BasicParseError::AtRuleBodyInvalid.into()),
+                }
+            }
+
+            fn parse_block<'t>(&mut self, prelude: Self::Prelude, input: &mut Parser<'i, 't>)
+                               -> Result<Self::AtRule, ParseError<'i>> {
+                let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::FontFeatureValues));
+                match prelude {
+                    $(
+                        BlockType::$ident_camel => {
+                            let parser = FFVDeclarationsParser {
+                                context: &context,
+                                declarations: &mut self.rule.$ident,
+                            };
+
+                            let mut iter = DeclarationListParser::new(input, parser);
+                            while let Some(declaration) = iter.next() {
+                                if let Err(err) = declaration {
+                                    let pos = err.span.start;
+                                    let error = ContextualParseError::UnsupportedKeyframePropertyDeclaration(
+                                        iter.input.slice(err.span), err.error);
+                                    log_css_error(iter.input, pos, error, &context);
+                                }
+                            }
+                        },
+                    )*
+                }
+
+                Ok(())
+            }
+        }
+    }
+}
+
+font_feature_values_blocks! {
+    blocks = [
+        #[doc = "A @swash blocksck. \
+                 Specifies a feature name that will work with the swash() \
+                 functional notation of font-variant-alternates."]
+        "swash" swash / Swash: SingleValue,
+
+        #[doc = "A @stylistic block. \
+                 Specifies a feature name that will work with the annotation() \
+                 functional notation of font-variant-alternates."]
+        "stylistic" stylistic / Stylistic: SingleValue,
+
+        #[doc = "A @ornaments block. \
+                 Specifies a feature name that will work with the ornaments() ] \
+                 functional notation of font-variant-alternates."]
+        "ornaments" ornaments / Ornaments: SingleValue,
+
+        #[doc = "A @annotation block. \
+                 Specifies a feature name that will work with the stylistic() \
+                 functional notation of font-variant-alternates."]
+        "annotation" annotation / Annotation: SingleValue,
+
+        #[doc = "A @character-variant block. \
+                 Specifies a feature name that will work with the styleset() \
+                 functional notation of font-variant-alternates. The value can be a pair."]
+        "character-variant" character_variant / CharacterVariant: PairValues,
+
+        #[doc = "A @styleset block. \
+                 Specifies a feature name that will work with the character-variant() \
+                 functional notation of font-variant-alternates. The value can be a list."]
+        "styleset" styleset / Styleset: VectorValues,
+    ]
+}
--- a/servo/components/style/stylesheets/mod.rs
+++ b/servo/components/style/stylesheets/mod.rs
@@ -2,16 +2,17 @@
  * 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/. */
 
 //! Style sheets and their CSS rules.
 
 mod counter_style_rule;
 mod document_rule;
 mod font_face_rule;
+pub mod font_feature_values_rule;
 pub mod import_rule;
 pub mod keyframes_rule;
 mod loader;
 mod media_rule;
 mod memory;
 mod namespace_rule;
 mod page_rule;
 mod rule_list;
@@ -28,16 +29,17 @@ use parser::ParserContext;
 use servo_arc::Arc;
 use shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
 use std::fmt;
 use style_traits::PARSING_MODE_DEFAULT;
 
 pub use self::counter_style_rule::CounterStyleRule;
 pub use self::document_rule::DocumentRule;
 pub use self::font_face_rule::FontFaceRule;
+pub use self::font_feature_values_rule::FontFeatureValuesRule;
 pub use self::import_rule::ImportRule;
 pub use self::keyframes_rule::KeyframesRule;
 pub use self::loader::StylesheetLoader;
 pub use self::media_rule::MediaRule;
 pub use self::memory::{MallocSizeOf, MallocSizeOfFn, MallocSizeOfWithGuard};
 pub use self::namespace_rule::NamespaceRule;
 pub use self::page_rule::PageRule;
 pub use self::rule_parser::{State, TopLevelRuleParser};
@@ -98,16 +100,17 @@ pub enum CssRule {
     // No Charset here, CSSCharsetRule has been removed from CSSOM
     // https://drafts.csswg.org/cssom/#changes-from-5-december-2013
 
     Namespace(Arc<Locked<NamespaceRule>>),
     Import(Arc<Locked<ImportRule>>),
     Style(Arc<Locked<StyleRule>>),
     Media(Arc<Locked<MediaRule>>),
     FontFace(Arc<Locked<FontFaceRule>>),
+    FontFeatureValues(Arc<Locked<FontFeatureValuesRule>>),
     CounterStyle(Arc<Locked<CounterStyleRule>>),
     Viewport(Arc<Locked<ViewportRule>>),
     Keyframes(Arc<Locked<KeyframesRule>>),
     Supports(Arc<Locked<SupportsRule>>),
     Page(Arc<Locked<PageRule>>),
     Document(Arc<Locked<DocumentRule>>),
 }
 
@@ -120,16 +123,17 @@ impl MallocSizeOfWithGuard for CssRule {
         match *self {
             CssRule::Style(ref lock) => {
                 lock.read_with(guard).malloc_size_of_children(guard, malloc_size_of)
             },
             // Measurement of these fields may be added later.
             CssRule::Import(_) => 0,
             CssRule::Media(_) => 0,
             CssRule::FontFace(_) => 0,
+            CssRule::FontFeatureValues(_) => 0,
             CssRule::CounterStyle(_) => 0,
             CssRule::Keyframes(_) => 0,
             CssRule::Namespace(_) => 0,
             CssRule::Viewport(_) => 0,
             CssRule::Supports(_) => 0,
             CssRule::Page(_) => 0,
             CssRule::Document(_)  => 0,
         }
@@ -190,16 +194,17 @@ impl From<SingleRuleParseError> for Rule
 impl CssRule {
     /// Returns the CSSOM rule type of this rule.
     pub fn rule_type(&self) -> CssRuleType {
         match *self {
             CssRule::Style(_) => CssRuleType::Style,
             CssRule::Import(_) => CssRuleType::Import,
             CssRule::Media(_) => CssRuleType::Media,
             CssRule::FontFace(_) => CssRuleType::FontFace,
+            CssRule::FontFeatureValues(_) => CssRuleType::FontFeatureValues,
             CssRule::CounterStyle(_) => CssRuleType::CounterStyle,
             CssRule::Keyframes(_) => CssRuleType::Keyframes,
             CssRule::Namespace(_) => CssRuleType::Namespace,
             CssRule::Viewport(_) => CssRuleType::Viewport,
             CssRule::Supports(_) => CssRuleType::Supports,
             CssRule::Page(_) => CssRuleType::Page,
             CssRule::Document(_)  => CssRuleType::Document,
         }
@@ -293,16 +298,20 @@ impl DeepCloneWithLock for CssRule {
                 CssRule::Media(Arc::new(
                     lock.wrap(rule.deep_clone_with_lock(lock, guard, params))))
             },
             CssRule::FontFace(ref arc) => {
                 let rule = arc.read_with(guard);
                 CssRule::FontFace(Arc::new(lock.wrap(
                     rule.clone_conditionally_gecko_or_servo())))
             },
+            CssRule::FontFeatureValues(ref arc) => {
+                let rule = arc.read_with(guard);
+                CssRule::FontFeatureValues(Arc::new(lock.wrap(rule.clone())))
+            },
             CssRule::CounterStyle(ref arc) => {
                 let rule = arc.read_with(guard);
                 CssRule::CounterStyle(Arc::new(lock.wrap(
                     rule.clone_conditionally_gecko_or_servo())))
             },
             CssRule::Viewport(ref arc) => {
                 let rule = arc.read_with(guard);
                 CssRule::Viewport(Arc::new(lock.wrap(rule.clone())))
@@ -335,16 +344,17 @@ impl ToCssWithGuard for CssRule {
     // https://drafts.csswg.org/cssom/#serialize-a-css-rule
     fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
     where W: fmt::Write {
         match *self {
             CssRule::Namespace(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::Import(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::Style(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::FontFace(ref lock) => lock.read_with(guard).to_css(guard, dest),
+            CssRule::FontFeatureValues(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::CounterStyle(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::Viewport(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::Keyframes(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::Media(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::Supports(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::Page(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::Document(ref lock) => lock.read_with(guard).to_css(guard, dest),
         }
--- a/servo/components/style/stylesheets/rule_parser.rs
+++ b/servo/components/style/stylesheets/rule_parser.rs
@@ -1,34 +1,36 @@
 /* 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/. */
 
 //! Parsing of the stylesheet contents.
 
 use {Namespace, Prefix};
+use computed_values::font_family::FamilyName;
 use counter_style::{parse_counter_style_body, parse_counter_style_name};
 use cssparser::{AtRuleParser, AtRuleType, Parser, QualifiedRuleParser, RuleListParser};
 use cssparser::{CowRcStr, SourceLocation, BasicParseError};
 use error_reporting::ContextualParseError;
 use font_face::parse_font_face_block;
 use media_queries::{parse_media_query_list, MediaList};
 use parser::{Parse, ParserContext, log_css_error};
 use properties::parse_property_declaration_list;
 use selector_parser::{SelectorImpl, SelectorParser};
 use selectors::SelectorList;
 use selectors::parser::SelectorParseError;
 use servo_arc::Arc;
 use shared_lock::{Locked, SharedRwLock};
 use str::starts_with_ignore_ascii_case;
 use style_traits::{StyleParseError, ParseError};
 use stylesheets::{CssRule, CssRules, CssRuleType, Origin, StylesheetLoader};
-use stylesheets::{DocumentRule, KeyframesRule, MediaRule, NamespaceRule, PageRule};
-use stylesheets::{StyleRule, SupportsRule, ViewportRule};
+use stylesheets::{DocumentRule, FontFeatureValuesRule, KeyframesRule, MediaRule};
+use stylesheets::{NamespaceRule, PageRule, StyleRule, SupportsRule, ViewportRule};
 use stylesheets::document_rule::DocumentCondition;
+use stylesheets::font_feature_values_rule::parse_family_name_list;
 use stylesheets::keyframes_rule::parse_keyframe_list;
 use stylesheets::stylesheet::Namespaces;
 use stylesheets::supports_rule::SupportsCondition;
 use stylesheets::viewport_rule;
 use values::CustomIdent;
 use values::KeyframesName;
 use values::specified::url::SpecifiedUrl;
 
@@ -96,16 +98,18 @@ pub enum VendorPrefix {
     /// -webkit prefix.
     WebKit,
 }
 
 /// A rule prelude for a given at-rule.
 pub enum AtRulePrelude {
     /// A @font-face rule prelude.
     FontFace(SourceLocation),
+    /// A @font-feature-values rule prelude, with its FamilyName list.
+    FontFeatureValues(Vec<FamilyName>, SourceLocation),
     /// A @counter-style rule prelude, with its counter style name.
     CounterStyle(CustomIdent),
     /// A @media rule prelude, with its media queries.
     Media(Arc<Locked<MediaList>>, SourceLocation),
     /// An @supports rule, with its conditional
     Supports(SupportsCondition, SourceLocation),
     /// A @viewport rule prelude.
     Viewport,
@@ -340,16 +344,24 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for Ne
             },
             "supports" => {
                 let cond = SupportsCondition::parse(input)?;
                 Ok(AtRuleType::WithBlock(AtRulePrelude::Supports(cond, location)))
             },
             "font-face" => {
                 Ok(AtRuleType::WithBlock(AtRulePrelude::FontFace(location)))
             },
+            "font-feature-values" => {
+                if !cfg!(feature = "gecko") && !cfg!(feature = "testing") {
+                    // Support for this rule is not fully implemented in Servo yet.
+                    return Err(StyleParseError::UnsupportedAtRule(name.clone()).into())
+                }
+                let family_names = parse_family_name_list(self.context, input)?;
+                Ok(AtRuleType::WithBlock(AtRulePrelude::FontFeatureValues(family_names, location)))
+            },
             "counter-style" => {
                 if !cfg!(feature = "gecko") {
                     // Support for this rule is not fully implemented in Servo yet.
                     return Err(StyleParseError::UnsupportedAtRule(name.clone()).into())
                 }
                 let name = parse_counter_style_name(input)?;
                 // ASCII-case-insensitive matches for "decimal" and "disc".
                 // The name is already lower-cased by `parse_counter_style_name`
@@ -408,16 +420,21 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for Ne
         input: &mut Parser<'i, 't>
     ) -> Result<CssRule, ParseError<'i>> {
         match prelude {
             AtRulePrelude::FontFace(location) => {
                 let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::FontFace));
                 Ok(CssRule::FontFace(Arc::new(self.shared_lock.wrap(
                    parse_font_face_block(&context, input, location).into()))))
             }
+            AtRulePrelude::FontFeatureValues(family_names, location) => {
+                let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::FontFeatureValues));
+                Ok(CssRule::FontFeatureValues(Arc::new(self.shared_lock.wrap(
+                    FontFeatureValuesRule::parse(&context, input, family_names, location)))))
+            }
             AtRulePrelude::CounterStyle(name) => {
                 let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::CounterStyle));
                 Ok(CssRule::CounterStyle(Arc::new(self.shared_lock.wrap(
                    parse_counter_style_body(name, &context, input)?.into()))))
             }
             AtRulePrelude::Media(media_queries, location) => {
                 Ok(CssRule::Media(Arc::new(self.shared_lock.wrap(MediaRule {
                     media_queries: media_queries,
--- a/servo/components/style/stylesheets/rules_iterator.rs
+++ b/servo/components/style/stylesheets/rules_iterator.rs
@@ -81,17 +81,18 @@ impl<'a, 'b, C> Iterator for RulesIterat
 
                 match *rule {
                     CssRule::Namespace(_) |
                     CssRule::Style(_) |
                     CssRule::FontFace(_) |
                     CssRule::CounterStyle(_) |
                     CssRule::Viewport(_) |
                     CssRule::Keyframes(_) |
-                    CssRule::Page(_) => {
+                    CssRule::Page(_) |
+                    CssRule::FontFeatureValues(_) => {
                         return Some(rule)
                     },
                     CssRule::Import(ref import_rule) => {
                         let import_rule = import_rule.read_with(self.guard);
                         if !C::process_import(self.guard,
                                               self.device,
                                               self.quirks_mode,
                                               import_rule) {
--- a/servo/components/style/stylesheets/stylesheet.rs
+++ b/servo/components/style/stylesheets/stylesheet.rs
@@ -242,16 +242,17 @@ pub trait StylesheetInDocument {
     ) -> EffectiveRulesIterator<'a, 'b> {
         self.iter_rules::<EffectiveRules>(device, guard)
     }
 
     rule_filter! {
         effective_style_rules(Style => StyleRule),
         effective_media_rules(Media => MediaRule),
         effective_font_face_rules(FontFace => FontFaceRule),
+        effective_font_face_feature_values_rules(FontFeatureValues => FontFeatureValuesRule),
         effective_counter_style_rules(CounterStyle => CounterStyleRule),
         effective_viewport_rules(Viewport => ViewportRule),
         effective_keyframes_rules(Keyframes => KeyframesRule),
         effective_supports_rules(Supports => SupportsRule),
         effective_page_rules(Page => PageRule),
         effective_document_rules(Document => DocumentRule),
     }
 }
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -990,17 +990,18 @@ impl Stylist {
                     CssRule::Style(..) |
                     CssRule::Namespace(..) |
                     CssRule::FontFace(..) |
                     CssRule::CounterStyle(..) |
                     CssRule::Supports(..) |
                     CssRule::Keyframes(..) |
                     CssRule::Page(..) |
                     CssRule::Viewport(..) |
-                    CssRule::Document(..) => {
+                    CssRule::Document(..) |
+                    CssRule::FontFeatureValues(..) => {
                         // Not affected by device changes.
                         continue;
                     }
                     CssRule::Import(ref lock) => {
                         let import_rule = lock.read_with(guard);
                         let effective_now =
                             import_rule.stylesheet
                                 .is_effective_for_device(&self.device, guard);
--- a/servo/ports/geckolib/error_reporter.rs
+++ b/servo/ports/geckolib/error_reporter.rs
@@ -198,17 +198,19 @@ trait ErrorHelpers<'a> {
     fn to_gecko_message(&self) -> (&'static [u8], Action);
 }
 
 impl<'a> ErrorHelpers<'a> for ContextualParseError<'a> {
     fn error_data(self) -> (CowRcStr<'a>, ParseError<'a>) {
         match self {
             ContextualParseError::UnsupportedPropertyDeclaration(s, err) |
             ContextualParseError::UnsupportedFontFaceDescriptor(s, err) |
+            ContextualParseError::UnsupportedFontFeatureValuesDescriptor(s, err) |
             ContextualParseError::InvalidKeyframeRule(s, err) |
+            ContextualParseError::InvalidFontFeatureValuesRule(s, err) |
             ContextualParseError::UnsupportedKeyframePropertyDeclaration(s, err) |
             ContextualParseError::InvalidRule(s, err) |
             ContextualParseError::UnsupportedRule(s, err) |
             ContextualParseError::UnsupportedViewportDescriptorDeclaration(s, err) |
             ContextualParseError::UnsupportedCounterStyleDescriptorDeclaration(s, err) =>
                 (s.into(), err),
             ContextualParseError::InvalidCounterStyleWithoutSymbols(s) |
             ContextualParseError::InvalidCounterStyleNotEnoughSymbols(s) =>
@@ -284,17 +286,19 @@ impl<'a> ErrorHelpers<'a> for Contextual
             ContextualParseError::UnsupportedRule(..) =>
                 (b"PEDeclDropped\0", Action::Nothing),
             ContextualParseError::UnsupportedViewportDescriptorDeclaration(..) |
             ContextualParseError::UnsupportedCounterStyleDescriptorDeclaration(..) |
             ContextualParseError::InvalidCounterStyleWithoutSymbols(..) |
             ContextualParseError::InvalidCounterStyleNotEnoughSymbols(..) |
             ContextualParseError::InvalidCounterStyleWithoutAdditiveSymbols |
             ContextualParseError::InvalidCounterStyleExtendsWithSymbols |
-            ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols =>
+            ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols |
+            ContextualParseError::UnsupportedFontFeatureValuesDescriptor(..) |
+            ContextualParseError::InvalidFontFeatureValuesRule(..) =>
                 (b"PEUnknownAtRule\0", Action::Skip),
         }
     }
 }
 
 impl ParseErrorReporter for ErrorReporter {
     fn report_error<'a>(&self,
                         input: &mut Parser,
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -26,16 +26,17 @@ use style::gecko::restyle_damage::GeckoR
 use style::gecko::selector_parser::PseudoElement;
 use style::gecko::traversal::RecalcStyleOnly;
 use style::gecko::wrapper::GeckoElement;
 use style::gecko_bindings::bindings;
 use style::gecko_bindings::bindings::{RawGeckoElementBorrowed, RawGeckoElementBorrowedOrNull};
 use style::gecko_bindings::bindings::{RawGeckoKeyframeListBorrowed, RawGeckoKeyframeListBorrowedMut};
 use style::gecko_bindings::bindings::{RawServoDeclarationBlockBorrowed, RawServoDeclarationBlockStrong};
 use style::gecko_bindings::bindings::{RawServoDocumentRule, RawServoDocumentRuleBorrowed};
+use style::gecko_bindings::bindings::{RawServoFontFeatureValuesRule, RawServoFontFeatureValuesRuleBorrowed};
 use style::gecko_bindings::bindings::{RawServoImportRule, RawServoImportRuleBorrowed};
 use style::gecko_bindings::bindings::{RawServoKeyframe, RawServoKeyframeBorrowed, RawServoKeyframeStrong};
 use style::gecko_bindings::bindings::{RawServoKeyframesRule, RawServoKeyframesRuleBorrowed};
 use style::gecko_bindings::bindings::{RawServoMediaListBorrowed, RawServoMediaListStrong};
 use style::gecko_bindings::bindings::{RawServoMediaRule, RawServoMediaRuleBorrowed};
 use style::gecko_bindings::bindings::{RawServoNamespaceRule, RawServoNamespaceRuleBorrowed};
 use style::gecko_bindings::bindings::{RawServoPageRule, RawServoPageRuleBorrowed};
 use style::gecko_bindings::bindings::{RawServoStyleSetBorrowed, RawServoStyleSetOwned};
@@ -102,17 +103,17 @@ use style::properties::animated_properti
 use style::properties::parse_one_declaration_into;
 use style::rule_tree::StyleSource;
 use style::selector_parser::PseudoElementCascadeType;
 use style::sequential;
 use style::shared_lock::{SharedRwLockReadGuard, StylesheetGuards, ToCssWithGuard, Locked};
 use style::string_cache::Atom;
 use style::style_adjuster::StyleAdjuster;
 use style::stylesheets::{CssRule, CssRules, CssRuleType, CssRulesHelpers, DocumentRule};
-use style::stylesheets::{ImportRule, KeyframesRule, MallocSizeOfWithGuard, MediaRule};
+use style::stylesheets::{FontFeatureValuesRule, ImportRule, KeyframesRule, MallocSizeOfWithGuard, MediaRule};
 use style::stylesheets::{NamespaceRule, Origin, PageRule, StyleRule, SupportsRule};
 use style::stylesheets::StylesheetContents;
 use style::stylesheets::StylesheetLoader as StyleStylesheetLoader;
 use style::stylesheets::keyframes_rule::{Keyframe, KeyframeSelector, KeyframesStepValue};
 use style::stylesheets::supports_rule::parse_condition_or_declaration;
 use style::stylist::RuleInclusion;
 use style::thread_state;
 use style::timer::Timer;
@@ -1228,16 +1229,22 @@ impl_group_rule_funcs! { (Supports, Supp
 
 impl_group_rule_funcs! { (Document, DocumentRule, RawServoDocumentRule),
     get_rules: Servo_DocumentRule_GetRules,
     getter: Servo_CssRules_GetDocumentRuleAt,
     debug: Servo_DocumentRule_Debug,
     to_css: Servo_DocumentRule_GetCssText,
 }
 
+impl_basic_rule_funcs! { (FontFeatureValues, FontFeatureValuesRule, RawServoFontFeatureValuesRule),
+    getter: Servo_CssRules_GetFontFeatureValuesRuleAt,
+    debug: Servo_FontFeatureValuesRule_Debug,
+    to_css: Servo_FontFeatureValuesRule_GetCssText,
+}
+
 macro_rules! impl_getter_for_embedded_rule {
     ($getter:ident: $name:ident -> $ty:ty) => {
         #[no_mangle]
         pub extern "C" fn $getter(rules: ServoCssRulesBorrowed, index: u32) -> *mut $ty
         {
             let global_style_data = &*GLOBAL_STYLE_DATA;
             let guard = global_style_data.shared_lock.read();
             let rules = Locked::<CssRules>::as_arc(&rules).read_with(&guard);
@@ -1519,16 +1526,32 @@ pub extern "C" fn Servo_SupportsRule_Get
 pub extern "C" fn Servo_DocumentRule_GetConditionText(rule: RawServoDocumentRuleBorrowed,
                                                       result: *mut nsAString) {
     read_locked_arc(rule, |rule: &DocumentRule| {
         rule.condition.to_css(unsafe { result.as_mut().unwrap() }).unwrap();
     })
 }
 
 #[no_mangle]
+pub extern "C" fn Servo_FontFeatureValuesRule_GetFontFamily(rule: RawServoFontFeatureValuesRuleBorrowed,
+                                                            result: *mut nsAString) {
+    read_locked_arc(rule, |rule: &FontFeatureValuesRule| {
+        rule.font_family_to_css(unsafe { result.as_mut().unwrap() }).unwrap();
+    })
+}
+
+#[no_mangle]
+pub extern "C" fn Servo_FontFeatureValuesRule_GetValueText(rule: RawServoFontFeatureValuesRuleBorrowed,
+                                                           result: *mut nsAString) {
+    read_locked_arc(rule, |rule: &FontFeatureValuesRule| {
+        rule.value_to_css(unsafe { result.as_mut().unwrap() }).unwrap();
+    })
+}
+
+#[no_mangle]
 pub extern "C" fn Servo_ComputedValues_GetForAnonymousBox(parent_style_or_null: ServoStyleContextBorrowedOrNull,
                                                           pseudo_tag: *mut nsIAtom,
                                                           raw_data: RawServoStyleSetBorrowed)
      -> ServoStyleContextStrong {
     let global_style_data = &*GLOBAL_STYLE_DATA;
     let guard = global_style_data.shared_lock.read();
     let guards = StylesheetGuards::same(&guard);
     let data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
--- a/servo/tests/unit/style/stylesheets.rs
+++ b/servo/tests/unit/style/stylesheets.rs
@@ -9,26 +9,29 @@ use parking_lot::RwLock;
 use selectors::attr::*;
 use selectors::parser::*;
 use servo_arc::Arc;
 use servo_atoms::Atom;
 use servo_url::ServoUrl;
 use std::borrow::ToOwned;
 use std::sync::Mutex;
 use std::sync::atomic::AtomicBool;
+use style::computed_values::font_family::FamilyName;
 use style::context::QuirksMode;
 use style::error_reporting::{ParseErrorReporter, ContextualParseError};
 use style::media_queries::MediaList;
 use style::properties::Importance;
 use style::properties::{CSSWideKeyword, DeclaredValueOwned, PropertyDeclaration, PropertyDeclarationBlock};
 use style::properties::longhands;
 use style::properties::longhands::animation_timing_function;
 use style::shared_lock::SharedRwLock;
 use style::stylesheets::{Origin, Namespaces};
 use style::stylesheets::{Stylesheet, StylesheetContents, NamespaceRule, CssRule, CssRules, StyleRule, KeyframesRule};
+use style::stylesheets::font_feature_values_rule::{FFVDeclaration, FontFeatureValuesRule};
+use style::stylesheets::font_feature_values_rule::{SingleValue, PairValues, VectorValues};
 use style::stylesheets::keyframes_rule::{Keyframe, KeyframeSelector, KeyframePercentage};
 use style::values::{KeyframesName, CustomIdent};
 use style::values::computed::Percentage;
 use style::values::specified::{LengthOrPercentageOrAuto, PositionComponent};
 use style::values::specified::transform::TimingFunction;
 
 pub fn block_from<I>(iterable: I) -> PropertyDeclarationBlock
 where I: IntoIterator<Item=(PropertyDeclaration, Importance)> {
@@ -60,16 +63,24 @@ fn test_parse_stylesheet() {
         @keyframes foo {
             from { width: 0% }
             to {
                 width: 100%;
                 width: 50% !important; /* !important not allowed here */
                 animation-name: 'foo'; /* animation properties not allowed here */
                 animation-timing-function: ease; /* … except animation-timing-function */
             }
+        }
+        @font-feature-values test {
+            @swash { foo: 12; bar: 24; }
+            @swash { bar: 36; baz: 48; }
+            @stylistic { fooo: 14; }
+            @rubbish { shouldnt-parse: 1; }
+            @styleset { hello: 10 11 12; }
+            @character-variant { ok: 78 2; }
         }";
     let url = ServoUrl::parse("about::test").unwrap();
     let lock = SharedRwLock::new();
     let media = Arc::new(lock.wrap(MediaList::empty()));
     let stylesheet = Stylesheet::from_str(css, url.clone(), Origin::UserAgent, media, lock,
                                           None, &CSSErrorReporterTest, QuirksMode::NoQuirks, 0u64);
     let mut namespaces = Namespaces::default();
     namespaces.default = Some((ns!(html), ()));
@@ -234,16 +245,60 @@ fn test_parse_stylesheet() {
                             ]))),
                         })),
                     ],
                     vendor_prefix: None,
                     source_location: SourceLocation {
                         line: 16,
                         column: 19,
                     },
+                }))),
+                CssRule::FontFeatureValues(Arc::new(stylesheet.shared_lock.wrap(FontFeatureValuesRule {
+                    family_names: vec![FamilyName {
+                        name: Atom::from("test"),
+                        quoted: false,
+                    }],
+                    swash: vec![
+                        FFVDeclaration {
+                            name: "foo".into(),
+                            value: SingleValue(12 as u32),
+                        },
+                        FFVDeclaration {
+                            name: "bar".into(),
+                            value: SingleValue(36 as u32),
+                        },
+                        FFVDeclaration {
+                            name: "baz".into(),
+                            value: SingleValue(48 as u32),
+                        }
+                    ],
+                    stylistic: vec![
+                        FFVDeclaration {
+                            name: "fooo".into(),
+                            value: SingleValue(14 as u32),
+                        }
+                    ],
+                    ornaments: vec![],
+                    annotation: vec![],
+                    character_variant: vec![
+                        FFVDeclaration {
+                            name: "ok".into(),
+                            value: PairValues(78 as u32, Some(2 as u32)),
+                        },
+                    ],
+                    styleset: vec![
+                        FFVDeclaration {
+                            name: "hello".into(),
+                            value: VectorValues(vec![10 as u32, 11 as u32, 12 as u32]),
+                        },
+                    ],
+                    source_location: SourceLocation {
+                        line: 25,
+                        column: 29,
+                    },
                 })))
 
             ], &stylesheet.shared_lock),
         },
         media: Arc::new(stylesheet.shared_lock.wrap(MediaList::empty())),
         shared_lock: stylesheet.shared_lock.clone(),
         disabled: AtomicBool::new(false),
     };