Bug 1499386 - Implement @supports selector() syntax. r=heycam
authorEmilio Cobos Álvarez <emilio@crisal.io>
Wed, 17 Oct 2018 12:08:14 +0000
changeset 500137 631545ef79251ea54347ebcb76420b7c1c9ba333
parent 500136 55c7172f12a49ad69572ab2bad0d0ea76fa860ac
child 500138 bfb92e2d55e43b25ee6256c7a037ead4022c3f8d
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam
bugs1499386
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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>