Bug 1365900 - Implement parsing/serialization for @font-feature-values rule r?SimonSapin draft
authorNazım Can Altınova <canaltinova@gmail.com>
Sun, 23 Jul 2017 23:49:44 -0700
changeset 616522 a31434c4c19de32dbfb010506d76370fff1f7780
parent 614015 5928d905c0bc0b28f5488b236444c7d7991cf8d4
child 616523 bb723558bf88e7a8318356f98127336b0bd4b928
push id70715
push userbmo:canaltinova@gmail.com
push dateThu, 27 Jul 2017 04:39:30 +0000
reviewersSimonSapin
bugs1365900
milestone56.0a1
Bug 1365900 - Implement parsing/serialization for @font-feature-values rule r?SimonSapin MozReview-Commit-ID: EgHIgzPWYjb
servo/components/script/dom/cssrule.rs
servo/components/style/error_reporting.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/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/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,391 @@
+/* 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::{CompactCowStr, 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)),
+            t => Err(BasicParseError::UnexpectedToken(t).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>> {
+        match input.next()? {
+            Token::Number { int_value: Some(a), .. } if a >= 0 => {
+                match input.next() {
+                    Ok(Token::Number { int_value: Some(b), .. }) if b >= 0 => {
+                        Ok(PairValues(a as u32, Some(b as u32)))
+                    }
+                    // It can't be anything other than number.
+                    Ok(t) => Err(BasicParseError::UnexpectedToken(t).into()),
+                    // It can be just one value.
+                    Err(_) => Ok(PairValues(a as u32, None))
+                }
+            },
+            t => Err(BasicParseError::UnexpectedToken(t).into())
+        }
+    }
+}
+
+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).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: CompactCowStr<'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: CompactCowStr<'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,15 +1,16 @@
 /* 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::{CompactCowStr, 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;
@@ -17,19 +18,20 @@ use selector_parser::{SelectorImpl, Sele
 use selectors::SelectorList;
 use selectors::parser::SelectorParseError;
 use servo_arc::Arc;
 use shared_lock::{Locked, SharedRwLock};
 use std::borrow::Cow;
 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;
 
@@ -97,16 +99,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,
@@ -341,16 +345,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`
@@ -409,16 +421,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
@@ -987,17 +987,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
@@ -191,17 +191,19 @@ trait ErrorHelpers<'a> {
     fn to_gecko_message(&self) -> &'static [u8];
 }
 
 impl<'a> ErrorHelpers<'a> for ContextualParseError<'a> {
     fn error_data(self) -> (CompactCowStr<'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) =>
@@ -277,17 +279,19 @@ impl<'a> ErrorHelpers<'a> for Contextual
             ContextualParseError::UnsupportedRule(..) =>
                 b"PEDeclDropped\0",
             ContextualParseError::UnsupportedViewportDescriptorDeclaration(..) |
             ContextualParseError::UnsupportedCounterStyleDescriptorDeclaration(..) |
             ContextualParseError::InvalidCounterStyleWithoutSymbols(..) |
             ContextualParseError::InvalidCounterStyleNotEnoughSymbols(..) |
             ContextualParseError::InvalidCounterStyleWithoutAdditiveSymbols |
             ContextualParseError::InvalidCounterStyleExtendsWithSymbols |
-            ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols =>
+            ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols |
+            ContextualParseError::UnsupportedFontFeatureValuesDescriptor(..) |
+            ContextualParseError::InvalidFontFeatureValuesRule(..) =>
                 b"PEUnknownAtRule\0",
         }
     }
 }
 
 impl ParseErrorReporter for ErrorReporter {
     fn report_error<'a>(&self,
                         input: &mut Parser,
--- 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_play_state;
 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};
 
 pub fn block_from<I>(iterable: I) -> PropertyDeclarationBlock
 where I: IntoIterator<Item=(PropertyDeclaration, Importance)> {
     let mut block = PropertyDeclarationBlock::new();
@@ -59,16 +62,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-play-state: running; /* … except animation-play-state */
             }
+        }
+        @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), ()));
@@ -233,16 +244,60 @@ fn test_parse_stylesheet() {
                             ]))),
                         })),
                     ],
                     vendor_prefix: None,
                     source_location: SourceLocation {
                         line: 16,
                         column: 18,
                     },
+                }))),
+                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: 28,
+                    },
                 })))
 
             ], &stylesheet.shared_lock),
         },
         media: Arc::new(stylesheet.shared_lock.wrap(MediaList::empty())),
         shared_lock: stylesheet.shared_lock.clone(),
         disabled: AtomicBool::new(false),
     };