Bug 1499386 - Implement @supports selector() syntax. r=heycam
authorEmilio Cobos Álvarez <emilio@crisal.io>
Wed, 17 Oct 2018 12:08:14 +0000
changeset 490067 631545ef79251ea54347ebcb76420b7c1c9ba333
parent 490066 55c7172f12a49ad69572ab2bad0d0ea76fa860ac
child 490068 bfb92e2d55e43b25ee6256c7a037ead4022c3f8d
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersheycam
bugs1499386
milestone64.0a1
Bug 1499386 - Implement @supports selector() syntax. r=heycam This implements the selector(<complex-selector>) syntax for @supports. See https://github.com/w3c/csswg-drafts/issues/3207 for explainer and discussion. Probably would should wait for that to be sorted out to land this, or maybe we should put it behind a pref to get the code landed and change our implementation if the discussion there leads to a change. Differential Revision: https://phabricator.services.mozilla.com/D8864
layout/reftests/bugs/1499386-ref.html
layout/reftests/bugs/1499386.html
layout/reftests/bugs/reftest.list
modules/libpref/init/StaticPrefList.h
servo/components/selectors/parser.rs
servo/components/style/stylesheets/mod.rs
servo/components/style/stylesheets/rule_parser.rs
servo/components/style/stylesheets/stylesheet.rs
servo/components/style/stylesheets/supports_rule.rs
servo/ports/geckolib/glue.rs
testing/web-platform/tests/css/css-conditional/at-supports-040.html
testing/web-platform/tests/css/css-conditional/at-supports-041.html
testing/web-platform/tests/css/css-conditional/at-supports-042.html
testing/web-platform/tests/css/cssom/CSS.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1499386-ref.html
@@ -0,0 +1,3 @@
+<style>
+  :root { background: green }
+</style>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1499386.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<style>
+  @supports selector(div) {
+    :root { background: green }
+  }
+</style>
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -2082,8 +2082,10 @@ test-pref(layout.css.prefixes.gradients,
 test-pref(layout.css.contain.enabled,false) == 1466008.html 1466008-ref.html
 fuzzy(0-1,0-625) == 1466638-1.html 1466638-1-ref.html
 == bug1472465-1.html bug1472465-1-ref.html
 == 1475971-1.html 1475971-1-ref.html
 == 1483649-1.xul 1483649-1-ref.xul
 test-pref(layout.css.contain.enabled,true) == 1483946.html 1483946-ref.html
 test-pref(layout.css.visited_links_enabled,false) == 1488155.html 1488155-ref.html
 == 1492660-1.html 1492660-1-ref.html
+pref(layout.css.supports-selector.enabled,true) == 1499386.html 1499386-ref.html
+pref(layout.css.supports-selector.enabled,false) != 1499386.html 1499386-ref.html
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -707,16 +707,28 @@ VARCACHE_PREF(
 
 // Pref to control whether @-moz-document rules are enabled in content pages.
 VARCACHE_PREF(
   "layout.css.moz-document.content.enabled",
    layout_css_moz_document_content_enabled,
   bool, false
 )
 
+#ifdef NIGHTLY_BUILD
+# define PREF_VALUE true
+#else
+# define PREF_VALUE false
+#endif
+VARCACHE_PREF(
+  "layout.css.supports-selector.enabled",
+   layout_css_supports_selector_enabled,
+  bool, PREF_VALUE
+)
+#undef PREF_VALUE
+
 // Pref to control whether @-moz-document url-prefix() is parsed in content
 // pages. Only effective when layout.css.moz-document.content.enabled is false.
 #ifdef EARLY_BETA_OR_EARLIER
 #define PREF_VALUE false
 #else
 #define PREF_VALUE true
 #endif
 VARCACHE_PREF(
--- a/servo/components/selectors/parser.rs
+++ b/servo/components/selectors/parser.rs
@@ -254,18 +254,23 @@ fn parse_inner_compound_selector<'i, 't,
     parser: &P,
     input: &mut CssParser<'i, 't>,
 ) -> Result<Selector<Impl>, ParseError<'i, P::Error>>
 where
     P: Parser<'i, Impl = Impl>,
     Impl: SelectorImpl,
 {
     let location = input.current_source_location();
-    let selector = Selector::parse(parser, input)?;
-    // Ensure they're actually all compound selectors.
+    let selector = parse_selector(parser, input)?;
+
+    // Ensure they're actually all compound selectors without pseudo-elements.
+    if selector.has_pseudo_element() {
+        return Err(location.new_custom_error(SelectorParseErrorKind::PseudoElementInComplexSelector));
+    }
+
     if selector.iter_raw_match_order().any(|s| s.is_combinator()) {
         return Err(location.new_custom_error(SelectorParseErrorKind::NonCompoundSelector));
     }
 
     Ok(selector)
 }
 
 /// Parse a comma separated list of compound selectors.
@@ -1392,29 +1397,25 @@ where
         builder.push_combinator(combinator);
     }
 
     Ok(Selector(builder.build(has_pseudo_element, slotted)))
 }
 
 impl<Impl: SelectorImpl> Selector<Impl> {
     /// Parse a selector, without any pseudo-element.
+    #[inline]
     pub fn parse<'i, 't, P>(
         parser: &P,
         input: &mut CssParser<'i, 't>,
     ) -> Result<Self, ParseError<'i, P::Error>>
     where
         P: Parser<'i, Impl = Impl>,
     {
-        let selector = parse_selector(parser, input)?;
-        if selector.has_pseudo_element() {
-            let e = SelectorParseErrorKind::PseudoElementInComplexSelector;
-            return Err(input.new_custom_error(e));
-        }
-        Ok(selector)
+        parse_selector(parser, input)
     }
 }
 
 /// * `Err(())`: Invalid selector, abort
 /// * `Ok(false)`: Not a type selector, could be something else. `input` was not consumed.
 /// * `Ok(true)`: Length 0 (`*|*`), 1 (`*|E` or `ns|*`) or 2 (`|E` or `ns|E`)
 fn parse_type_selector<'i, 't, P, Impl, S>(
     parser: &P,
--- a/servo/components/style/stylesheets/mod.rs
+++ b/servo/components/style/stylesheets/mod.rs
@@ -272,17 +272,16 @@ impl CssRule {
 
         let mut input = ParserInput::new(css);
         let mut input = Parser::new(&mut input);
 
         let mut guard = parent_stylesheet_contents.namespaces.write();
 
         // nested rules are in the body state
         let mut rule_parser = TopLevelRuleParser {
-            stylesheet_origin: parent_stylesheet_contents.origin,
             context,
             shared_lock: &shared_lock,
             loader,
             state,
             dom_error: None,
             namespaces: &mut *guard,
             insert_rule_context: Some(insert_rule_context),
         };
--- a/servo/components/style/stylesheets/rule_parser.rs
+++ b/servo/components/style/stylesheets/rule_parser.rs
@@ -14,17 +14,17 @@ use media_queries::MediaList;
 use parser::{Parse, ParserContext};
 use properties::parse_property_declaration_list;
 use selector_parser::{SelectorImpl, SelectorParser};
 use selectors::SelectorList;
 use servo_arc::Arc;
 use shared_lock::{Locked, SharedRwLock};
 use str::starts_with_ignore_ascii_case;
 use style_traits::{ParseError, StyleParseErrorKind};
-use stylesheets::{CssRule, CssRuleType, CssRules, Origin, RulesMutateError, StylesheetLoader};
+use stylesheets::{CssRule, CssRuleType, CssRules, RulesMutateError, StylesheetLoader};
 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;
@@ -36,18 +36,16 @@ pub struct InsertRuleContext<'a> {
     /// The rule list we're about to insert into.
     pub rule_list: &'a [CssRule],
     /// The index we're about to get inserted at.
     pub index: usize,
 }
 
 /// The parser for the top-level rules in a stylesheet.
 pub struct TopLevelRuleParser<'a> {
-    /// The origin of the stylesheet we're parsing.
-    pub stylesheet_origin: Origin,
     /// A reference to the lock we need to use to create rules.
     pub shared_lock: &'a SharedRwLock,
     /// A reference to a stylesheet loader if applicable, for `@import` rules.
     pub loader: Option<&'a StylesheetLoader>,
     /// The top-level parser context.
     ///
     /// This won't contain any namespaces, and only nested parsers created with
     /// `ParserContext::new_with_rule_type` will.
@@ -64,17 +62,16 @@ pub struct TopLevelRuleParser<'a> {
     pub namespaces: &'a mut Namespaces,
     /// The info we need insert a rule in a list.
     pub insert_rule_context: Option<InsertRuleContext<'a>>,
 }
 
 impl<'b> TopLevelRuleParser<'b> {
     fn nested<'a: 'b>(&'a self) -> NestedRuleParser<'a, 'b> {
         NestedRuleParser {
-            stylesheet_origin: self.stylesheet_origin,
             shared_lock: self.shared_lock,
             context: &self.context,
             namespaces: &self.namespaces,
         }
     }
 
     /// Returns the current state of the parser.
     pub fn state(&self) -> State {
@@ -320,32 +317,30 @@ impl<'a, 'i> QualifiedRuleParser<'i> for
                 result
             },
         )
     }
 }
 
 #[derive(Clone)] // shallow, relatively cheap .clone
 struct NestedRuleParser<'a, 'b: 'a> {
-    stylesheet_origin: Origin,
     shared_lock: &'a SharedRwLock,
     context: &'a ParserContext<'b>,
     namespaces: &'a Namespaces,
 }
 
 impl<'a, 'b> NestedRuleParser<'a, 'b> {
     fn parse_nested_rules(
         &mut self,
         input: &mut Parser,
         rule_type: CssRuleType,
     ) -> Arc<Locked<CssRules>> {
         let context = ParserContext::new_with_rule_type(self.context, rule_type, self.namespaces);
 
         let nested_parser = NestedRuleParser {
-            stylesheet_origin: self.stylesheet_origin,
             shared_lock: self.shared_lock,
             context: &context,
             namespaces: self.namespaces,
         };
 
         let mut iter = RuleListParser::new_for_nested_rule(input, nested_parser);
         let mut rules = Vec::new();
         while let Some(result) = iter.next() {
@@ -496,17 +491,17 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for Ne
             },
             AtRuleBlockPrelude::Supports(condition) => {
                 let eval_context = ParserContext::new_with_rule_type(
                     self.context,
                     CssRuleType::Style,
                     self.namespaces,
                 );
 
-                let enabled = condition.eval(&eval_context);
+                let enabled = condition.eval(&eval_context, self.namespaces);
                 Ok(CssRule::Supports(Arc::new(self.shared_lock.wrap(
                     SupportsRule {
                         condition,
                         rules: self.parse_nested_rules(input, CssRuleType::Supports),
                         enabled,
                         source_location,
                     },
                 ))))
@@ -572,17 +567,17 @@ impl<'a, 'b, 'i> QualifiedRuleParser<'i>
     type QualifiedRule = CssRule;
     type Error = StyleParseErrorKind<'i>;
 
     fn parse_prelude<'t>(
         &mut self,
         input: &mut Parser<'i, 't>,
     ) -> Result<Self::Prelude, ParseError<'i>> {
         let selector_parser = SelectorParser {
-            stylesheet_origin: self.stylesheet_origin,
+            stylesheet_origin: self.context.stylesheet_origin,
             namespaces: self.namespaces,
             url_data: Some(self.context.url_data),
         };
         SelectorList::parse(&selector_parser, input)
     }
 
     fn parse_block<'t>(
         &mut self,
--- a/servo/components/style/stylesheets/stylesheet.rs
+++ b/servo/components/style/stylesheets/stylesheet.rs
@@ -369,17 +369,16 @@ impl Stylesheet {
             None,
             ParsingMode::DEFAULT,
             quirks_mode,
             error_reporter,
             use_counters,
         );
 
         let rule_parser = TopLevelRuleParser {
-            stylesheet_origin: origin,
             shared_lock,
             loader: stylesheet_loader,
             context,
             state: State::Start,
             dom_error: None,
             insert_rule_context: None,
             namespaces,
         };
--- a/servo/components/style/stylesheets/supports_rule.rs
+++ b/servo/components/style/stylesheets/supports_rule.rs
@@ -7,25 +7,27 @@
 use cssparser::{Delimiter, Parser, SourceLocation, Token};
 use cssparser::{ParseError as CssParseError, ParserInput};
 use cssparser::parse_important;
 #[cfg(feature = "gecko")]
 use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
 use parser::ParserContext;
 use properties::{PropertyDeclaration, PropertyId, SourcePropertyDeclaration};
 use selectors::parser::SelectorParseErrorKind;
+use selector_parser::{SelectorImpl, SelectorParser};
+use selectors::parser::Selector;
 use servo_arc::Arc;
 use shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked};
 use shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
 use std::ffi::{CStr, CString};
 use std::fmt::{self, Write};
 use std::str;
 use str::CssStringWriter;
-use style_traits::{CssWriter, ParseError, ToCss};
-use stylesheets::{CssRuleType, CssRules};
+use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
+use stylesheets::{CssRuleType, CssRules, Namespaces};
 
 /// An [`@supports`][supports] rule.
 ///
 /// [supports]: https://drafts.csswg.org/css-conditional-3/#at-supports
 #[derive(Debug)]
 pub struct SupportsRule {
     /// The parsed condition
     pub condition: SupportsCondition,
@@ -82,42 +84,42 @@ pub enum SupportsCondition {
     /// `(condition)`
     Parenthesized(Box<SupportsCondition>),
     /// `(condition) and (condition) and (condition) ..`
     And(Vec<SupportsCondition>),
     /// `(condition) or (condition) or (condition) ..`
     Or(Vec<SupportsCondition>),
     /// `property-ident: value` (value can be any tokens)
     Declaration(Declaration),
+    /// A `selector()` function.
+    Selector(RawSelector),
     /// `-moz-bool-pref("pref-name")`
     /// Since we need to pass it through FFI to get the pref value,
     /// we store it as CString directly.
     MozBoolPref(CString),
     /// `(any tokens)` or `func(any tokens)`
     FutureSyntax(String),
 }
 
 impl SupportsCondition {
     /// Parse a condition
     ///
     /// <https://drafts.csswg.org/css-conditional/#supports_condition>
-    pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<SupportsCondition, ParseError<'i>> {
-        if let Ok(_) = input.try(|i| i.expect_ident_matching("not")) {
+    pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
+        if input.try(|i| i.expect_ident_matching("not")).is_ok() {
             let inner = SupportsCondition::parse_in_parens(input)?;
             return Ok(SupportsCondition::Not(Box::new(inner)));
         }
 
         let in_parens = SupportsCondition::parse_in_parens(input)?;
 
         let location = input.current_source_location();
         let (keyword, wrapper) = match input.next() {
-            Err(_) => {
-                // End of input
-                return Ok(in_parens);
-            },
+            // End of input
+            Err(..) => return Ok(in_parens),
             Ok(&Token::Ident(ref ident)) => {
                 match_ignore_ascii_case! { &ident,
                     "and" => ("and", SupportsCondition::And as fn(_) -> _),
                     "or" => ("or", SupportsCondition::Or as fn(_) -> _),
                     _ => return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())))
                 }
             },
             Ok(t) => return Err(location.new_unexpected_token_error(t.clone())),
@@ -127,75 +129,105 @@ impl SupportsCondition {
         conditions.push(in_parens);
         loop {
             conditions.push(SupportsCondition::parse_in_parens(input)?);
             if input
                 .try(|input| input.expect_ident_matching(keyword))
                 .is_err()
             {
                 // Did not find the expected keyword.
-                // If we found some other token,
-                // it will be rejected by `Parser::parse_entirely` somewhere up the stack.
+                // If we found some other token, it will be rejected by
+                // `Parser::parse_entirely` somewhere up the stack.
                 return Ok(wrapper(conditions));
             }
         }
     }
 
+    /// Parses a functional supports condition.
+    fn parse_functional<'i, 't>(
+        function: &str,
+        input: &mut Parser<'i, 't>,
+    ) -> Result<Self, ParseError<'i>> {
+        match_ignore_ascii_case!{ function,
+            // Although this is an internal syntax, it is not necessary
+            // to check parsing context as far as we accept any
+            // unexpected token as future syntax, and evaluate it to
+            // false when not in chrome / ua sheet.
+            // See https://drafts.csswg.org/css-conditional-3/#general_enclosed
+            "-moz-bool-pref" => {
+                let name = {
+                    let name = input.expect_string()?;
+                    CString::new(name.as_bytes())
+                }.map_err(|_| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))?;
+                Ok(SupportsCondition::MozBoolPref(name))
+            }
+            "selector" => {
+                let pos = input.position();
+                consume_any_value(input)?;
+                Ok(SupportsCondition::Selector(RawSelector(
+                    input.slice_from(pos).to_owned()
+                )))
+            }
+            _ => {
+                Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
+            }
+        }
+    }
+
     /// <https://drafts.csswg.org/css-conditional-3/#supports_condition_in_parens>
     fn parse_in_parens<'i, 't>(
         input: &mut Parser<'i, 't>,
-    ) -> Result<SupportsCondition, ParseError<'i>> {
+    ) -> Result<Self, ParseError<'i>> {
         // Whitespace is normally taken care of in `Parser::next`,
         // but we want to not include it in `pos` for the SupportsCondition::FutureSyntax cases.
         while input.try(Parser::expect_whitespace).is_ok() {}
         let pos = input.position();
         let location = input.current_source_location();
         // FIXME: remove clone() when lifetimes are non-lexical
         match input.next()?.clone() {
             Token::ParenthesisBlock => {
-                let nested = input
-                    .try(|input| input.parse_nested_block(|i| parse_condition_or_declaration(i)));
+                let nested = input.try(|input| {
+                    input.parse_nested_block(parse_condition_or_declaration)
+                });
                 if nested.is_ok() {
                     return nested;
                 }
             },
             Token::Function(ident) => {
-                // Although this is an internal syntax, it is not necessary to check
-                // parsing context as far as we accept any unexpected token as future
-                // syntax, and evaluate it to false when not in chrome / ua sheet.
-                // See https://drafts.csswg.org/css-conditional-3/#general_enclosed
-                if ident.eq_ignore_ascii_case("-moz-bool-pref") {
-                    if let Ok(name) = input.try(|i| {
-                        i.parse_nested_block(|i| {
-                            i.expect_string()
-                                .map(|s| s.to_string())
-                                .map_err(CssParseError::<()>::from)
-                        }).and_then(|s| CString::new(s).map_err(|_| location.new_custom_error(())))
-                    }) {
-                        return Ok(SupportsCondition::MozBoolPref(name));
-                    }
+                let nested = input.try(|input| {
+                    input.parse_nested_block(|input| {
+                        SupportsCondition::parse_functional(&ident, input)
+                    })
+                });
+                if nested.is_ok() {
+                    return nested;
                 }
             },
             t => return Err(location.new_unexpected_token_error(t)),
         }
-        input.parse_nested_block(|i| consume_any_value(i))?;
+        input.parse_nested_block(consume_any_value)?;
         Ok(SupportsCondition::FutureSyntax(
             input.slice_from(pos).to_owned(),
         ))
     }
 
     /// Evaluate a supports condition
-    pub fn eval(&self, cx: &ParserContext) -> bool {
+    pub fn eval(
+        &self,
+        cx: &ParserContext,
+        namespaces: &Namespaces,
+    ) -> bool {
         match *self {
-            SupportsCondition::Not(ref cond) => !cond.eval(cx),
-            SupportsCondition::Parenthesized(ref cond) => cond.eval(cx),
-            SupportsCondition::And(ref vec) => vec.iter().all(|c| c.eval(cx)),
-            SupportsCondition::Or(ref vec) => vec.iter().any(|c| c.eval(cx)),
+            SupportsCondition::Not(ref cond) => !cond.eval(cx, namespaces),
+            SupportsCondition::Parenthesized(ref cond) => cond.eval(cx, namespaces),
+            SupportsCondition::And(ref vec) => vec.iter().all(|c| c.eval(cx, namespaces)),
+            SupportsCondition::Or(ref vec) => vec.iter().any(|c| c.eval(cx, namespaces)),
             SupportsCondition::Declaration(ref decl) => decl.eval(cx),
             SupportsCondition::MozBoolPref(ref name) => eval_moz_bool_pref(name, cx),
+            SupportsCondition::Selector(ref selector) => selector.eval(cx, namespaces),
             SupportsCondition::FutureSyntax(_) => false,
         }
     }
 }
 
 #[cfg(feature = "gecko")]
 fn eval_moz_bool_pref(name: &CStr, cx: &ParserContext) -> bool {
     use gecko_bindings::bindings;
@@ -260,29 +292,96 @@ impl ToCss for SupportsCondition {
                 }
                 Ok(())
             },
             SupportsCondition::Declaration(ref decl) => {
                 dest.write_str("(")?;
                 decl.to_css(dest)?;
                 dest.write_str(")")
             },
+            SupportsCondition::Selector(ref selector) => {
+                dest.write_str("selector(")?;
+                selector.to_css(dest)?;
+                dest.write_str(")")
+            }
             SupportsCondition::MozBoolPref(ref name) => {
                 dest.write_str("-moz-bool-pref(")?;
                 let name =
                     str::from_utf8(name.as_bytes()).expect("Should be parsed from valid UTF-8");
                 name.to_css(dest)?;
                 dest.write_str(")")
             },
             SupportsCondition::FutureSyntax(ref s) => dest.write_str(&s),
         }
     }
 }
 
 #[derive(Clone, Debug)]
+/// A possibly-invalid CSS selector.
+pub struct RawSelector(pub String);
+
+impl ToCss for RawSelector {
+    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+    where
+        W: Write,
+    {
+        dest.write_str(&self.0)
+    }
+}
+
+impl RawSelector {
+    /// Tries to evaluate a `selector()` function.
+    pub fn eval(
+        &self,
+        context: &ParserContext,
+        namespaces: &Namespaces,
+    ) -> bool {
+        #[cfg(feature = "gecko")]
+        {
+            if unsafe { !::gecko_bindings::structs::StaticPrefs_sVarCache_layout_css_supports_selector_enabled } {
+                return false;
+            }
+        }
+
+        let mut input = ParserInput::new(&self.0);
+        let mut input = Parser::new(&mut input);
+        input.parse_entirely(|input| -> Result<(), CssParseError<()>> {
+            let parser = SelectorParser {
+                namespaces,
+                stylesheet_origin: context.stylesheet_origin,
+                url_data: Some(context.url_data),
+            };
+
+            let selector = Selector::<SelectorImpl>::parse(&parser, input)
+                .map_err(|_| input.new_custom_error(()))?;
+
+            #[cfg(feature = "gecko")]
+            {
+                use selectors::parser::Component;
+                use selector_parser::PseudoElement;
+
+                let has_any_unknown_webkit_pseudo =
+                    selector.has_pseudo_element() &&
+                    selector.iter_raw_match_order().any(|component| {
+                        matches!(
+                            *component,
+                            Component::PseudoElement(PseudoElement::UnknownWebkit(..))
+                        )
+                    });
+                if has_any_unknown_webkit_pseudo {
+                    return Err(input.new_custom_error(()));
+                }
+            }
+
+            Ok(())
+        }).is_ok()
+    }
+}
+
+#[derive(Clone, Debug)]
 /// A possibly-invalid property declaration
 pub struct Declaration(pub String);
 
 impl ToCss for Declaration {
     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
     where
         W: Write,
     {
@@ -308,26 +407,25 @@ impl Declaration {
     /// Determine if a declaration parses
     ///
     /// <https://drafts.csswg.org/css-conditional-3/#support-definition>
     pub fn eval(&self, context: &ParserContext) -> bool {
         debug_assert_eq!(context.rule_type(), CssRuleType::Style);
 
         let mut input = ParserInput::new(&self.0);
         let mut input = Parser::new(&mut input);
-        input
-            .parse_entirely(|input| -> Result<(), CssParseError<()>> {
-                let prop = input.expect_ident_cloned().unwrap();
-                input.expect_colon().unwrap();
+        input.parse_entirely(|input| -> Result<(), CssParseError<()>> {
+            let prop = input.expect_ident_cloned().unwrap();
+            input.expect_colon().unwrap();
 
-                let id =
-                    PropertyId::parse(&prop, context).map_err(|_| input.new_custom_error(()))?;
+            let id =
+                PropertyId::parse(&prop, context).map_err(|_| input.new_custom_error(()))?;
 
-                let mut declarations = SourcePropertyDeclaration::new();
-                input.parse_until_before(Delimiter::Bang, |input| {
-                    PropertyDeclaration::parse_into(&mut declarations, id, &context, input)
-                        .map_err(|_| input.new_custom_error(()))
-                })?;
-                let _ = input.try(parse_important);
-                Ok(())
-            }).is_ok()
+            let mut declarations = SourcePropertyDeclaration::new();
+            input.parse_until_before(Delimiter::Bang, |input| {
+                PropertyDeclaration::parse_into(&mut declarations, id, &context, input)
+                    .map_err(|_| input.new_custom_error(()))
+            })?;
+            let _ = input.try(parse_important);
+            Ok(())
+        }).is_ok()
     }
 }
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -4494,34 +4494,36 @@ pub unsafe extern "C" fn Servo_CSSSuppor
     ).is_ok()
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_CSSSupports(cond: *const nsACString) -> bool {
     let condition = unsafe { cond.as_ref().unwrap().as_str_unchecked() };
     let mut input = ParserInput::new(&condition);
     let mut input = Parser::new(&mut input);
-    let cond = input.parse_entirely(|i| parse_condition_or_declaration(i));
-    if let Ok(cond) = cond {
-        let url_data = unsafe { dummy_url_data() };
-        // NOTE(emilio): The supports API is not associated to any stylesheet,
-        // so the fact that there is no namespace map here is fine.
-        let context = ParserContext::new_for_cssom(
-            url_data,
-            Some(CssRuleType::Style),
-            ParsingMode::DEFAULT,
-            QuirksMode::NoQuirks,
-            None,
-            None,
-        );
-
-        cond.eval(&context)
-    } else {
-        false
-    }
+    let cond = match input.parse_entirely(parse_condition_or_declaration) {
+        Ok(c) => c,
+        Err(..) => return false,
+    };
+
+    let url_data = unsafe { dummy_url_data() };
+
+    // NOTE(emilio): The supports API is not associated to any stylesheet,
+    // so the fact that there is no namespace map here is fine.
+    let context = ParserContext::new_for_cssom(
+        url_data,
+        Some(CssRuleType::Style),
+        ParsingMode::DEFAULT,
+        QuirksMode::NoQuirks,
+        None,
+        None,
+    );
+
+    let namespaces = Default::default();
+    cond.eval(&context, &namespaces)
 }
 
 #[no_mangle]
 pub unsafe extern "C" fn Servo_NoteExplicitHints(
     element: RawGeckoElementBorrowed,
     restyle_hint: nsRestyleHint,
     change_hint: nsChangeHint,
 ) {
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-conditional/at-supports-040.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<title>CSS Conditional Test: @supports selector() with pseudo-elements.</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<link rel="help" href="https://drafts.csswg.org/css-conditional/#at-supports">
+<link rel="match" href="at-supports-001-ref.html">
+<style>
+  div {
+    background-color:red;
+    height:100px;
+    width:100px;
+  }
+  @supports selector(::before) {
+    div { background: green };
+  }
+</style>
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div></div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-conditional/at-supports-041.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<title>CSS Conditional Test: @supports selector() with -webkit- unknown pseudo-elements and negation.</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<link rel="help" href="https://drafts.csswg.org/css-conditional/#at-supports">
+<link rel="match" href="at-supports-001-ref.html">
+<style>
+  div {
+    background-color:red;
+    height:100px;
+    width:100px;
+  }
+  @supports not selector(::-webkit-unknown-pseudo) {
+    div { background: green };
+  }
+</style>
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div></div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-conditional/at-supports-042.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<title>CSS Conditional Test: @supports selector() with multiple selectors doesn't work.</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<link rel="help" href="https://drafts.csswg.org/css-conditional/#at-supports">
+<link rel="match" href="at-supports-001-ref.html">
+<style>
+  div {
+    background-color: green;
+    height: 100px;
+    width: 100px;
+  }
+  @supports selector(div, div) {
+    div { background: red };
+  }
+</style>
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div></div>
--- a/testing/web-platform/tests/css/cssom/CSS.html
+++ b/testing/web-platform/tests/css/cssom/CSS.html
@@ -30,9 +30,17 @@
     test(function () {
         // https://drafts.csswg.org/css-conditional/#dom-css-supports
         // https://drafts.csswg.org/css-conditional/#dfn-support
         assert_equals(CSS.supports("color", "red"), true, "CSS.supports: two argument form succeeds for known property");
         assert_equals(CSS.supports("unknownproperty", "blah"), false, "CSS.supports: two argument form fails for unknown property");
         assert_equals(CSS.supports("width", "blah"), false, "CSS.supports: two argument form fails for invalid value");
         assert_equals(CSS.supports("--foo", "blah"), true, "CSS.supports: two argument form succeeds for custom property");
     }, "CSS.supports, two argument form");
+    test(function () {
+        assert_equals(CSS.supports("selector(div)"), true, "CSS.supports: selector() function accepts a selector");
+        assert_equals(CSS.supports("selector(div, div)"), false, "CSS.supports: selector() function doesn't accept a selector list");
+        assert_equals(CSS.supports("selector(::-webkit-unknown-pseudo-element)"), false, "CSS.supports: selector() function rejects unknown webkit pseudo-elements.");
+        assert_equals(CSS.supports("selector(::before)"), true, "CSS.supports: selector() function accepts known pseudo-elements");
+        assert_equals(CSS.supports("selector(div + .c)"), true, "CSS.supports: selector() with simple combinators");
+        assert_equals(CSS.supports("selector(div | .c)"), false, "CSS.supports: selector() with unknown combinators");
+    }, "CSS.supports, selector function");
 </script>